##// END OF EJS Templates
auth-tokens: remove showing builtin tokens
marcink -
r1479:7da2c024 default
parent child Browse files
Show More
@@ -1,468 +1,461
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27 import datetime
27 import datetime
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
31 from pyramid.threadlocal import get_current_registry
32 from pylons import request, tmpl_context as c, url, session
32 from pylons import request, tmpl_context as c, url, session
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36 from webob.exc import HTTPBadGateway
36 from webob.exc import HTTPBadGateway
37
37
38 from rhodecode import forms
38 from rhodecode import forms
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib import auth
40 from rhodecode.lib import auth
41 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
44 from rhodecode.lib.utils import jsonify
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
46 from rhodecode.lib.ext_json import json
46 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
47 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
48 ChannelstreamException
49
49
50 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 Repository, PullRequest, UserEmailMap, User, UserFollowing)
53 from rhodecode.model.forms import UserForm
53 from rhodecode.model.forms import UserForm
54 from rhodecode.model.scm import RepoList
54 from rhodecode.model.scm import RepoList
55 from rhodecode.model.user import UserModel
55 from rhodecode.model.user import UserModel
56 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.auth_token import AuthTokenModel
57 from rhodecode.model.auth_token import AuthTokenModel
58 from rhodecode.model.meta import Session
58 from rhodecode.model.meta import Session
59 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.comment import CommentsModel
60 from rhodecode.model.comment import CommentsModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 class MyAccountController(BaseController):
65 class MyAccountController(BaseController):
66 """REST Controller styled on the Atom Publishing Protocol"""
66 """REST Controller styled on the Atom Publishing Protocol"""
67 # To properly map this controller, ensure your config/routing.py
67 # To properly map this controller, ensure your config/routing.py
68 # file has a resource setup:
68 # file has a resource setup:
69 # map.resource('setting', 'settings', controller='admin/settings',
69 # map.resource('setting', 'settings', controller='admin/settings',
70 # path_prefix='/admin', name_prefix='admin_')
70 # path_prefix='/admin', name_prefix='admin_')
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 def __before__(self):
74 def __before__(self):
75 super(MyAccountController, self).__before__()
75 super(MyAccountController, self).__before__()
76
76
77 def __load_data(self):
77 def __load_data(self):
78 c.user = User.get(c.rhodecode_user.user_id)
78 c.user = User.get(c.rhodecode_user.user_id)
79 if c.user.username == User.DEFAULT_USER:
79 if c.user.username == User.DEFAULT_USER:
80 h.flash(_("You can't edit this user since it's"
80 h.flash(_("You can't edit this user since it's"
81 " crucial for entire application"), category='warning')
81 " crucial for entire application"), category='warning')
82 return redirect(url('users'))
82 return redirect(url('users'))
83
83
84 c.auth_user = AuthUser(
84 c.auth_user = AuthUser(
85 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
85 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
86
86
87 def _load_my_repos_data(self, watched=False):
87 def _load_my_repos_data(self, watched=False):
88 if watched:
88 if watched:
89 admin = False
89 admin = False
90 follows_repos = Session().query(UserFollowing)\
90 follows_repos = Session().query(UserFollowing)\
91 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
91 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
92 .options(joinedload(UserFollowing.follows_repository))\
92 .options(joinedload(UserFollowing.follows_repository))\
93 .all()
93 .all()
94 repo_list = [x.follows_repository for x in follows_repos]
94 repo_list = [x.follows_repository for x in follows_repos]
95 else:
95 else:
96 admin = True
96 admin = True
97 repo_list = Repository.get_all_repos(
97 repo_list = Repository.get_all_repos(
98 user_id=c.rhodecode_user.user_id)
98 user_id=c.rhodecode_user.user_id)
99 repo_list = RepoList(repo_list, perm_set=[
99 repo_list = RepoList(repo_list, perm_set=[
100 'repository.read', 'repository.write', 'repository.admin'])
100 'repository.read', 'repository.write', 'repository.admin'])
101
101
102 repos_data = RepoModel().get_repos_as_dict(
102 repos_data = RepoModel().get_repos_as_dict(
103 repo_list=repo_list, admin=admin)
103 repo_list=repo_list, admin=admin)
104 # json used to render the grid
104 # json used to render the grid
105 return json.dumps(repos_data)
105 return json.dumps(repos_data)
106
106
107 @auth.CSRFRequired()
107 @auth.CSRFRequired()
108 def my_account_update(self):
108 def my_account_update(self):
109 """
109 """
110 POST /_admin/my_account Updates info of my account
110 POST /_admin/my_account Updates info of my account
111 """
111 """
112 # url('my_account')
112 # url('my_account')
113 c.active = 'profile_edit'
113 c.active = 'profile_edit'
114 self.__load_data()
114 self.__load_data()
115 c.perm_user = c.auth_user
115 c.perm_user = c.auth_user
116 c.extern_type = c.user.extern_type
116 c.extern_type = c.user.extern_type
117 c.extern_name = c.user.extern_name
117 c.extern_name = c.user.extern_name
118
118
119 defaults = c.user.get_dict()
119 defaults = c.user.get_dict()
120 update = False
120 update = False
121 _form = UserForm(edit=True,
121 _form = UserForm(edit=True,
122 old_data={'user_id': c.rhodecode_user.user_id,
122 old_data={'user_id': c.rhodecode_user.user_id,
123 'email': c.rhodecode_user.email})()
123 'email': c.rhodecode_user.email})()
124 form_result = {}
124 form_result = {}
125 try:
125 try:
126 post_data = dict(request.POST)
126 post_data = dict(request.POST)
127 post_data['new_password'] = ''
127 post_data['new_password'] = ''
128 post_data['password_confirmation'] = ''
128 post_data['password_confirmation'] = ''
129 form_result = _form.to_python(post_data)
129 form_result = _form.to_python(post_data)
130 # skip updating those attrs for my account
130 # skip updating those attrs for my account
131 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
131 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
132 'new_password', 'password_confirmation']
132 'new_password', 'password_confirmation']
133 # TODO: plugin should define if username can be updated
133 # TODO: plugin should define if username can be updated
134 if c.extern_type != "rhodecode":
134 if c.extern_type != "rhodecode":
135 # forbid updating username for external accounts
135 # forbid updating username for external accounts
136 skip_attrs.append('username')
136 skip_attrs.append('username')
137
137
138 UserModel().update_user(
138 UserModel().update_user(
139 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
139 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
140 h.flash(_('Your account was updated successfully'),
140 h.flash(_('Your account was updated successfully'),
141 category='success')
141 category='success')
142 Session().commit()
142 Session().commit()
143 update = True
143 update = True
144
144
145 except formencode.Invalid as errors:
145 except formencode.Invalid as errors:
146 return htmlfill.render(
146 return htmlfill.render(
147 render('admin/my_account/my_account.mako'),
147 render('admin/my_account/my_account.mako'),
148 defaults=errors.value,
148 defaults=errors.value,
149 errors=errors.error_dict or {},
149 errors=errors.error_dict or {},
150 prefix_error=False,
150 prefix_error=False,
151 encoding="UTF-8",
151 encoding="UTF-8",
152 force_defaults=False)
152 force_defaults=False)
153 except Exception:
153 except Exception:
154 log.exception("Exception updating user")
154 log.exception("Exception updating user")
155 h.flash(_('Error occurred during update of user %s')
155 h.flash(_('Error occurred during update of user %s')
156 % form_result.get('username'), category='error')
156 % form_result.get('username'), category='error')
157
157
158 if update:
158 if update:
159 return redirect('my_account')
159 return redirect('my_account')
160
160
161 return htmlfill.render(
161 return htmlfill.render(
162 render('admin/my_account/my_account.mako'),
162 render('admin/my_account/my_account.mako'),
163 defaults=defaults,
163 defaults=defaults,
164 encoding="UTF-8",
164 encoding="UTF-8",
165 force_defaults=False
165 force_defaults=False
166 )
166 )
167
167
168 def my_account(self):
168 def my_account(self):
169 """
169 """
170 GET /_admin/my_account Displays info about my account
170 GET /_admin/my_account Displays info about my account
171 """
171 """
172 # url('my_account')
172 # url('my_account')
173 c.active = 'profile'
173 c.active = 'profile'
174 self.__load_data()
174 self.__load_data()
175
175
176 defaults = c.user.get_dict()
176 defaults = c.user.get_dict()
177 return htmlfill.render(
177 return htmlfill.render(
178 render('admin/my_account/my_account.mako'),
178 render('admin/my_account/my_account.mako'),
179 defaults=defaults, encoding="UTF-8", force_defaults=False)
179 defaults=defaults, encoding="UTF-8", force_defaults=False)
180
180
181 def my_account_edit(self):
181 def my_account_edit(self):
182 """
182 """
183 GET /_admin/my_account/edit Displays edit form of my account
183 GET /_admin/my_account/edit Displays edit form of my account
184 """
184 """
185 c.active = 'profile_edit'
185 c.active = 'profile_edit'
186 self.__load_data()
186 self.__load_data()
187 c.perm_user = c.auth_user
187 c.perm_user = c.auth_user
188 c.extern_type = c.user.extern_type
188 c.extern_type = c.user.extern_type
189 c.extern_name = c.user.extern_name
189 c.extern_name = c.user.extern_name
190
190
191 defaults = c.user.get_dict()
191 defaults = c.user.get_dict()
192 return htmlfill.render(
192 return htmlfill.render(
193 render('admin/my_account/my_account.mako'),
193 render('admin/my_account/my_account.mako'),
194 defaults=defaults,
194 defaults=defaults,
195 encoding="UTF-8",
195 encoding="UTF-8",
196 force_defaults=False
196 force_defaults=False
197 )
197 )
198
198
199 @auth.CSRFRequired(except_methods=['GET'])
199 @auth.CSRFRequired(except_methods=['GET'])
200 def my_account_password(self):
200 def my_account_password(self):
201 c.active = 'password'
201 c.active = 'password'
202 self.__load_data()
202 self.__load_data()
203 c.extern_type = c.user.extern_type
203 c.extern_type = c.user.extern_type
204
204
205 schema = user_schema.ChangePasswordSchema().bind(
205 schema = user_schema.ChangePasswordSchema().bind(
206 username=c.rhodecode_user.username)
206 username=c.rhodecode_user.username)
207
207
208 form = forms.Form(schema,
208 form = forms.Form(schema,
209 buttons=(forms.buttons.save, forms.buttons.reset))
209 buttons=(forms.buttons.save, forms.buttons.reset))
210
210
211 if request.method == 'POST' and c.extern_type == 'rhodecode':
211 if request.method == 'POST' and c.extern_type == 'rhodecode':
212 controls = request.POST.items()
212 controls = request.POST.items()
213 try:
213 try:
214 valid_data = form.validate(controls)
214 valid_data = form.validate(controls)
215 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
215 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
216 instance = c.rhodecode_user.get_instance()
216 instance = c.rhodecode_user.get_instance()
217 instance.update_userdata(force_password_change=False)
217 instance.update_userdata(force_password_change=False)
218 Session().commit()
218 Session().commit()
219 except forms.ValidationFailure as e:
219 except forms.ValidationFailure as e:
220 request.session.flash(
220 request.session.flash(
221 _('Error occurred during update of user password'),
221 _('Error occurred during update of user password'),
222 queue='error')
222 queue='error')
223 form = e
223 form = e
224 except Exception:
224 except Exception:
225 log.exception("Exception updating password")
225 log.exception("Exception updating password")
226 request.session.flash(
226 request.session.flash(
227 _('Error occurred during update of user password'),
227 _('Error occurred during update of user password'),
228 queue='error')
228 queue='error')
229 else:
229 else:
230 session.setdefault('rhodecode_user', {}).update(
230 session.setdefault('rhodecode_user', {}).update(
231 {'password': md5(instance.password)})
231 {'password': md5(instance.password)})
232 session.save()
232 session.save()
233 request.session.flash(
233 request.session.flash(
234 _("Successfully updated password"), queue='success')
234 _("Successfully updated password"), queue='success')
235 return redirect(url('my_account_password'))
235 return redirect(url('my_account_password'))
236
236
237 c.form = form
237 c.form = form
238 return render('admin/my_account/my_account.mako')
238 return render('admin/my_account/my_account.mako')
239
239
240 def my_account_repos(self):
240 def my_account_repos(self):
241 c.active = 'repos'
241 c.active = 'repos'
242 self.__load_data()
242 self.__load_data()
243
243
244 # json used to render the grid
244 # json used to render the grid
245 c.data = self._load_my_repos_data()
245 c.data = self._load_my_repos_data()
246 return render('admin/my_account/my_account.mako')
246 return render('admin/my_account/my_account.mako')
247
247
248 def my_account_watched(self):
248 def my_account_watched(self):
249 c.active = 'watched'
249 c.active = 'watched'
250 self.__load_data()
250 self.__load_data()
251
251
252 # json used to render the grid
252 # json used to render the grid
253 c.data = self._load_my_repos_data(watched=True)
253 c.data = self._load_my_repos_data(watched=True)
254 return render('admin/my_account/my_account.mako')
254 return render('admin/my_account/my_account.mako')
255
255
256 def my_account_perms(self):
256 def my_account_perms(self):
257 c.active = 'perms'
257 c.active = 'perms'
258 self.__load_data()
258 self.__load_data()
259 c.perm_user = c.auth_user
259 c.perm_user = c.auth_user
260
260
261 return render('admin/my_account/my_account.mako')
261 return render('admin/my_account/my_account.mako')
262
262
263 def my_account_emails(self):
263 def my_account_emails(self):
264 c.active = 'emails'
264 c.active = 'emails'
265 self.__load_data()
265 self.__load_data()
266
266
267 c.user_email_map = UserEmailMap.query()\
267 c.user_email_map = UserEmailMap.query()\
268 .filter(UserEmailMap.user == c.user).all()
268 .filter(UserEmailMap.user == c.user).all()
269 return render('admin/my_account/my_account.mako')
269 return render('admin/my_account/my_account.mako')
270
270
271 @auth.CSRFRequired()
271 @auth.CSRFRequired()
272 def my_account_emails_add(self):
272 def my_account_emails_add(self):
273 email = request.POST.get('new_email')
273 email = request.POST.get('new_email')
274
274
275 try:
275 try:
276 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
276 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
277 Session().commit()
277 Session().commit()
278 h.flash(_("Added new email address `%s` for user account") % email,
278 h.flash(_("Added new email address `%s` for user account") % email,
279 category='success')
279 category='success')
280 except formencode.Invalid as error:
280 except formencode.Invalid as error:
281 msg = error.error_dict['email']
281 msg = error.error_dict['email']
282 h.flash(msg, category='error')
282 h.flash(msg, category='error')
283 except Exception:
283 except Exception:
284 log.exception("Exception in my_account_emails")
284 log.exception("Exception in my_account_emails")
285 h.flash(_('An error occurred during email saving'),
285 h.flash(_('An error occurred during email saving'),
286 category='error')
286 category='error')
287 return redirect(url('my_account_emails'))
287 return redirect(url('my_account_emails'))
288
288
289 @auth.CSRFRequired()
289 @auth.CSRFRequired()
290 def my_account_emails_delete(self):
290 def my_account_emails_delete(self):
291 email_id = request.POST.get('del_email_id')
291 email_id = request.POST.get('del_email_id')
292 user_model = UserModel()
292 user_model = UserModel()
293 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
293 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
294 Session().commit()
294 Session().commit()
295 h.flash(_("Removed email address from user account"),
295 h.flash(_("Removed email address from user account"),
296 category='success')
296 category='success')
297 return redirect(url('my_account_emails'))
297 return redirect(url('my_account_emails'))
298
298
299 def _extract_ordering(self, request):
299 def _extract_ordering(self, request):
300 column_index = safe_int(request.GET.get('order[0][column]'))
300 column_index = safe_int(request.GET.get('order[0][column]'))
301 order_dir = request.GET.get('order[0][dir]', 'desc')
301 order_dir = request.GET.get('order[0][dir]', 'desc')
302 order_by = request.GET.get(
302 order_by = request.GET.get(
303 'columns[%s][data][sort]' % column_index, 'name_raw')
303 'columns[%s][data][sort]' % column_index, 'name_raw')
304 return order_by, order_dir
304 return order_by, order_dir
305
305
306 def _get_pull_requests_list(self, statuses):
306 def _get_pull_requests_list(self, statuses):
307 start = safe_int(request.GET.get('start'), 0)
307 start = safe_int(request.GET.get('start'), 0)
308 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
308 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
309 order_by, order_dir = self._extract_ordering(request)
309 order_by, order_dir = self._extract_ordering(request)
310
310
311 pull_requests = PullRequestModel().get_im_participating_in(
311 pull_requests = PullRequestModel().get_im_participating_in(
312 user_id=c.rhodecode_user.user_id,
312 user_id=c.rhodecode_user.user_id,
313 statuses=statuses,
313 statuses=statuses,
314 offset=start, length=length, order_by=order_by,
314 offset=start, length=length, order_by=order_by,
315 order_dir=order_dir)
315 order_dir=order_dir)
316
316
317 pull_requests_total_count = PullRequestModel().count_im_participating_in(
317 pull_requests_total_count = PullRequestModel().count_im_participating_in(
318 user_id=c.rhodecode_user.user_id, statuses=statuses)
318 user_id=c.rhodecode_user.user_id, statuses=statuses)
319
319
320 from rhodecode.lib.utils import PartialRenderer
320 from rhodecode.lib.utils import PartialRenderer
321 _render = PartialRenderer('data_table/_dt_elements.mako')
321 _render = PartialRenderer('data_table/_dt_elements.mako')
322 data = []
322 data = []
323 for pr in pull_requests:
323 for pr in pull_requests:
324 repo_id = pr.target_repo_id
324 repo_id = pr.target_repo_id
325 comments = CommentsModel().get_all_comments(
325 comments = CommentsModel().get_all_comments(
326 repo_id, pull_request=pr)
326 repo_id, pull_request=pr)
327 owned = pr.user_id == c.rhodecode_user.user_id
327 owned = pr.user_id == c.rhodecode_user.user_id
328 status = pr.calculated_review_status()
328 status = pr.calculated_review_status()
329
329
330 data.append({
330 data.append({
331 'target_repo': _render('pullrequest_target_repo',
331 'target_repo': _render('pullrequest_target_repo',
332 pr.target_repo.repo_name),
332 pr.target_repo.repo_name),
333 'name': _render('pullrequest_name',
333 'name': _render('pullrequest_name',
334 pr.pull_request_id, pr.target_repo.repo_name,
334 pr.pull_request_id, pr.target_repo.repo_name,
335 short=True),
335 short=True),
336 'name_raw': pr.pull_request_id,
336 'name_raw': pr.pull_request_id,
337 'status': _render('pullrequest_status', status),
337 'status': _render('pullrequest_status', status),
338 'title': _render(
338 'title': _render(
339 'pullrequest_title', pr.title, pr.description),
339 'pullrequest_title', pr.title, pr.description),
340 'description': h.escape(pr.description),
340 'description': h.escape(pr.description),
341 'updated_on': _render('pullrequest_updated_on',
341 'updated_on': _render('pullrequest_updated_on',
342 h.datetime_to_time(pr.updated_on)),
342 h.datetime_to_time(pr.updated_on)),
343 'updated_on_raw': h.datetime_to_time(pr.updated_on),
343 'updated_on_raw': h.datetime_to_time(pr.updated_on),
344 'created_on': _render('pullrequest_updated_on',
344 'created_on': _render('pullrequest_updated_on',
345 h.datetime_to_time(pr.created_on)),
345 h.datetime_to_time(pr.created_on)),
346 'created_on_raw': h.datetime_to_time(pr.created_on),
346 'created_on_raw': h.datetime_to_time(pr.created_on),
347 'author': _render('pullrequest_author',
347 'author': _render('pullrequest_author',
348 pr.author.full_contact, ),
348 pr.author.full_contact, ),
349 'author_raw': pr.author.full_name,
349 'author_raw': pr.author.full_name,
350 'comments': _render('pullrequest_comments', len(comments)),
350 'comments': _render('pullrequest_comments', len(comments)),
351 'comments_raw': len(comments),
351 'comments_raw': len(comments),
352 'closed': pr.is_closed(),
352 'closed': pr.is_closed(),
353 'owned': owned
353 'owned': owned
354 })
354 })
355 # json used to render the grid
355 # json used to render the grid
356 data = ({
356 data = ({
357 'data': data,
357 'data': data,
358 'recordsTotal': pull_requests_total_count,
358 'recordsTotal': pull_requests_total_count,
359 'recordsFiltered': pull_requests_total_count,
359 'recordsFiltered': pull_requests_total_count,
360 })
360 })
361 return data
361 return data
362
362
363 def my_account_pullrequests(self):
363 def my_account_pullrequests(self):
364 c.active = 'pullrequests'
364 c.active = 'pullrequests'
365 self.__load_data()
365 self.__load_data()
366 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
366 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
367
367
368 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
368 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
369 if c.show_closed:
369 if c.show_closed:
370 statuses += [PullRequest.STATUS_CLOSED]
370 statuses += [PullRequest.STATUS_CLOSED]
371 data = self._get_pull_requests_list(statuses)
371 data = self._get_pull_requests_list(statuses)
372 if not request.is_xhr:
372 if not request.is_xhr:
373 c.data_participate = json.dumps(data['data'])
373 c.data_participate = json.dumps(data['data'])
374 c.records_total_participate = data['recordsTotal']
374 c.records_total_participate = data['recordsTotal']
375 return render('admin/my_account/my_account.mako')
375 return render('admin/my_account/my_account.mako')
376 else:
376 else:
377 return json.dumps(data)
377 return json.dumps(data)
378
378
379 def my_account_auth_tokens(self):
379 def my_account_auth_tokens(self):
380 c.active = 'auth_tokens'
380 c.active = 'auth_tokens'
381 self.__load_data()
381 self.__load_data()
382 show_expired = True
382 show_expired = True
383 c.lifetime_values = [
383 c.lifetime_values = [
384 (str(-1), _('forever')),
384 (str(-1), _('forever')),
385 (str(5), _('5 minutes')),
385 (str(5), _('5 minutes')),
386 (str(60), _('1 hour')),
386 (str(60), _('1 hour')),
387 (str(60 * 24), _('1 day')),
387 (str(60 * 24), _('1 day')),
388 (str(60 * 24 * 30), _('1 month')),
388 (str(60 * 24 * 30), _('1 month')),
389 ]
389 ]
390 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
390 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
391 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
391 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
392 for x in AuthTokenModel.cls.ROLES]
392 for x in AuthTokenModel.cls.ROLES]
393 c.role_options = [(c.role_values, _("Role"))]
393 c.role_options = [(c.role_values, _("Role"))]
394 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
394 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
395 c.rhodecode_user.user_id, show_expired=show_expired)
395 c.rhodecode_user.user_id, show_expired=show_expired)
396 return render('admin/my_account/my_account.mako')
396 return render('admin/my_account/my_account.mako')
397
397
398 @auth.CSRFRequired()
398 @auth.CSRFRequired()
399 def my_account_auth_tokens_add(self):
399 def my_account_auth_tokens_add(self):
400 lifetime = safe_int(request.POST.get('lifetime'), -1)
400 lifetime = safe_int(request.POST.get('lifetime'), -1)
401 description = request.POST.get('description')
401 description = request.POST.get('description')
402 role = request.POST.get('role')
402 role = request.POST.get('role')
403 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
403 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
404 role)
404 role)
405 Session().commit()
405 Session().commit()
406 h.flash(_("Auth token successfully created"), category='success')
406 h.flash(_("Auth token successfully created"), category='success')
407 return redirect(url('my_account_auth_tokens'))
407 return redirect(url('my_account_auth_tokens'))
408
408
409 @auth.CSRFRequired()
409 @auth.CSRFRequired()
410 def my_account_auth_tokens_delete(self):
410 def my_account_auth_tokens_delete(self):
411 auth_token = request.POST.get('del_auth_token')
411 del_auth_token = request.POST.get('del_auth_token')
412 user_id = c.rhodecode_user.user_id
412
413 if request.POST.get('del_auth_token_builtin'):
413 if del_auth_token:
414 user = User.get(user_id)
414 AuthTokenModel().delete(del_auth_token, c.rhodecode_user.user_id)
415 if user:
416 user.api_key = generate_auth_token(user.username)
417 Session().add(user)
418 Session().commit()
419 h.flash(_("Auth token successfully reset"), category='success')
420 elif auth_token:
421 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
422 Session().commit()
415 Session().commit()
423 h.flash(_("Auth token successfully deleted"), category='success')
416 h.flash(_("Auth token successfully deleted"), category='success')
424
417
425 return redirect(url('my_account_auth_tokens'))
418 return redirect(url('my_account_auth_tokens'))
426
419
427 def my_notifications(self):
420 def my_notifications(self):
428 c.active = 'notifications'
421 c.active = 'notifications'
429 return render('admin/my_account/my_account.mako')
422 return render('admin/my_account/my_account.mako')
430
423
431 @auth.CSRFRequired()
424 @auth.CSRFRequired()
432 @jsonify
425 @jsonify
433 def my_notifications_toggle_visibility(self):
426 def my_notifications_toggle_visibility(self):
434 user = c.rhodecode_user.get_instance()
427 user = c.rhodecode_user.get_instance()
435 new_status = not user.user_data.get('notification_status', True)
428 new_status = not user.user_data.get('notification_status', True)
436 user.update_userdata(notification_status=new_status)
429 user.update_userdata(notification_status=new_status)
437 Session().commit()
430 Session().commit()
438 return user.user_data['notification_status']
431 return user.user_data['notification_status']
439
432
440 @auth.CSRFRequired()
433 @auth.CSRFRequired()
441 @jsonify
434 @jsonify
442 def my_account_notifications_test_channelstream(self):
435 def my_account_notifications_test_channelstream(self):
443 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
436 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
444 c.rhodecode_user.username, datetime.datetime.now())
437 c.rhodecode_user.username, datetime.datetime.now())
445 payload = {
438 payload = {
446 'type': 'message',
439 'type': 'message',
447 'timestamp': datetime.datetime.utcnow(),
440 'timestamp': datetime.datetime.utcnow(),
448 'user': 'system',
441 'user': 'system',
449 #'channel': 'broadcast',
442 #'channel': 'broadcast',
450 'pm_users': [c.rhodecode_user.username],
443 'pm_users': [c.rhodecode_user.username],
451 'message': {
444 'message': {
452 'message': message,
445 'message': message,
453 'level': 'info',
446 'level': 'info',
454 'topic': '/notifications'
447 'topic': '/notifications'
455 }
448 }
456 }
449 }
457
450
458 registry = get_current_registry()
451 registry = get_current_registry()
459 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
452 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
460 channelstream_config = rhodecode_plugins.get('channelstream', {})
453 channelstream_config = rhodecode_plugins.get('channelstream', {})
461
454
462 try:
455 try:
463 channelstream_request(channelstream_config, [payload], '/message')
456 channelstream_request(channelstream_config, [payload], '/message')
464 except ChannelstreamException as e:
457 except ChannelstreamException as e:
465 log.exception('Failed to send channelstream data')
458 log.exception('Failed to send channelstream data')
466 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
459 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
467 return {"response": 'Channelstream data sent. '
460 return {"response": 'Channelstream data sent. '
468 'You should see a new live message now.'}
461 'You should see a new live message now.'}
@@ -1,748 +1,741
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Users crud controller for pylons
22 Users crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 from formencode import htmlfill
28 from formencode import htmlfill
29 from pylons import request, tmpl_context as c, url, config
29 from pylons import request, tmpl_context as c, url, config
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.lib.exceptions import (
34 from rhodecode.lib.exceptions import (
35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 UserOwnsUserGroupsException, UserCreationError)
36 UserOwnsUserGroupsException, UserCreationError)
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import auth
38 from rhodecode.lib import auth
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.auth_token import AuthTokenModel
43
43
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
46 from rhodecode.model.forms import (
46 from rhodecode.model.forms import (
47 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
47 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.permission import PermissionModel
52 from rhodecode.lib.utils import action_logger
52 from rhodecode.lib.utils import action_logger
53 from rhodecode.lib.ext_json import json
53 from rhodecode.lib.ext_json import json
54 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
54 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class UsersController(BaseController):
59 class UsersController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
60 """REST Controller styled on the Atom Publishing Protocol"""
61
61
62 @LoginRequired()
62 @LoginRequired()
63 def __before__(self):
63 def __before__(self):
64 super(UsersController, self).__before__()
64 super(UsersController, self).__before__()
65 c.available_permissions = config['available_permissions']
65 c.available_permissions = config['available_permissions']
66 c.allowed_languages = [
66 c.allowed_languages = [
67 ('en', 'English (en)'),
67 ('en', 'English (en)'),
68 ('de', 'German (de)'),
68 ('de', 'German (de)'),
69 ('fr', 'French (fr)'),
69 ('fr', 'French (fr)'),
70 ('it', 'Italian (it)'),
70 ('it', 'Italian (it)'),
71 ('ja', 'Japanese (ja)'),
71 ('ja', 'Japanese (ja)'),
72 ('pl', 'Polish (pl)'),
72 ('pl', 'Polish (pl)'),
73 ('pt', 'Portuguese (pt)'),
73 ('pt', 'Portuguese (pt)'),
74 ('ru', 'Russian (ru)'),
74 ('ru', 'Russian (ru)'),
75 ('zh', 'Chinese (zh)'),
75 ('zh', 'Chinese (zh)'),
76 ]
76 ]
77 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
77 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
78
78
79 @HasPermissionAllDecorator('hg.admin')
79 @HasPermissionAllDecorator('hg.admin')
80 def index(self):
80 def index(self):
81 """GET /users: All items in the collection"""
81 """GET /users: All items in the collection"""
82 # url('users')
82 # url('users')
83
83
84 from rhodecode.lib.utils import PartialRenderer
84 from rhodecode.lib.utils import PartialRenderer
85 _render = PartialRenderer('data_table/_dt_elements.mako')
85 _render = PartialRenderer('data_table/_dt_elements.mako')
86
86
87 def username(user_id, username):
87 def username(user_id, username):
88 return _render("user_name", user_id, username)
88 return _render("user_name", user_id, username)
89
89
90 def user_actions(user_id, username):
90 def user_actions(user_id, username):
91 return _render("user_actions", user_id, username)
91 return _render("user_actions", user_id, username)
92
92
93 # json generate
93 # json generate
94 c.users_list = User.query()\
94 c.users_list = User.query()\
95 .filter(User.username != User.DEFAULT_USER) \
95 .filter(User.username != User.DEFAULT_USER) \
96 .all()
96 .all()
97
97
98 users_data = []
98 users_data = []
99 for user in c.users_list:
99 for user in c.users_list:
100 users_data.append({
100 users_data.append({
101 "username": h.gravatar_with_user(user.username),
101 "username": h.gravatar_with_user(user.username),
102 "username_raw": user.username,
102 "username_raw": user.username,
103 "email": user.email,
103 "email": user.email,
104 "first_name": h.escape(user.name),
104 "first_name": h.escape(user.name),
105 "last_name": h.escape(user.lastname),
105 "last_name": h.escape(user.lastname),
106 "last_login": h.format_date(user.last_login),
106 "last_login": h.format_date(user.last_login),
107 "last_login_raw": datetime_to_time(user.last_login),
107 "last_login_raw": datetime_to_time(user.last_login),
108 "last_activity": h.format_date(
108 "last_activity": h.format_date(
109 h.time_to_datetime(user.user_data.get('last_activity', 0))),
109 h.time_to_datetime(user.user_data.get('last_activity', 0))),
110 "last_activity_raw": user.user_data.get('last_activity', 0),
110 "last_activity_raw": user.user_data.get('last_activity', 0),
111 "active": h.bool2icon(user.active),
111 "active": h.bool2icon(user.active),
112 "active_raw": user.active,
112 "active_raw": user.active,
113 "admin": h.bool2icon(user.admin),
113 "admin": h.bool2icon(user.admin),
114 "admin_raw": user.admin,
114 "admin_raw": user.admin,
115 "extern_type": user.extern_type,
115 "extern_type": user.extern_type,
116 "extern_name": user.extern_name,
116 "extern_name": user.extern_name,
117 "action": user_actions(user.user_id, user.username),
117 "action": user_actions(user.user_id, user.username),
118 })
118 })
119
119
120
120
121 c.data = json.dumps(users_data)
121 c.data = json.dumps(users_data)
122 return render('admin/users/users.mako')
122 return render('admin/users/users.mako')
123
123
124 def _get_personal_repo_group_template_vars(self):
124 def _get_personal_repo_group_template_vars(self):
125 DummyUser = AttributeDict({
125 DummyUser = AttributeDict({
126 'username': '${username}',
126 'username': '${username}',
127 'user_id': '${user_id}',
127 'user_id': '${user_id}',
128 })
128 })
129 c.default_create_repo_group = RepoGroupModel() \
129 c.default_create_repo_group = RepoGroupModel() \
130 .get_default_create_personal_repo_group()
130 .get_default_create_personal_repo_group()
131 c.personal_repo_group_name = RepoGroupModel() \
131 c.personal_repo_group_name = RepoGroupModel() \
132 .get_personal_group_name(DummyUser)
132 .get_personal_group_name(DummyUser)
133
133
134 @HasPermissionAllDecorator('hg.admin')
134 @HasPermissionAllDecorator('hg.admin')
135 @auth.CSRFRequired()
135 @auth.CSRFRequired()
136 def create(self):
136 def create(self):
137 """POST /users: Create a new item"""
137 """POST /users: Create a new item"""
138 # url('users')
138 # url('users')
139 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
139 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
140 user_model = UserModel()
140 user_model = UserModel()
141 user_form = UserForm()()
141 user_form = UserForm()()
142 try:
142 try:
143 form_result = user_form.to_python(dict(request.POST))
143 form_result = user_form.to_python(dict(request.POST))
144 user = user_model.create(form_result)
144 user = user_model.create(form_result)
145 Session().flush()
145 Session().flush()
146 username = form_result['username']
146 username = form_result['username']
147 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
147 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
148 None, self.ip_addr, self.sa)
148 None, self.ip_addr, self.sa)
149
149
150 user_link = h.link_to(h.escape(username),
150 user_link = h.link_to(h.escape(username),
151 url('edit_user',
151 url('edit_user',
152 user_id=user.user_id))
152 user_id=user.user_id))
153 h.flash(h.literal(_('Created user %(user_link)s')
153 h.flash(h.literal(_('Created user %(user_link)s')
154 % {'user_link': user_link}), category='success')
154 % {'user_link': user_link}), category='success')
155 Session().commit()
155 Session().commit()
156 except formencode.Invalid as errors:
156 except formencode.Invalid as errors:
157 self._get_personal_repo_group_template_vars()
157 self._get_personal_repo_group_template_vars()
158 return htmlfill.render(
158 return htmlfill.render(
159 render('admin/users/user_add.mako'),
159 render('admin/users/user_add.mako'),
160 defaults=errors.value,
160 defaults=errors.value,
161 errors=errors.error_dict or {},
161 errors=errors.error_dict or {},
162 prefix_error=False,
162 prefix_error=False,
163 encoding="UTF-8",
163 encoding="UTF-8",
164 force_defaults=False)
164 force_defaults=False)
165 except UserCreationError as e:
165 except UserCreationError as e:
166 h.flash(e, 'error')
166 h.flash(e, 'error')
167 except Exception:
167 except Exception:
168 log.exception("Exception creation of user")
168 log.exception("Exception creation of user")
169 h.flash(_('Error occurred during creation of user %s')
169 h.flash(_('Error occurred during creation of user %s')
170 % request.POST.get('username'), category='error')
170 % request.POST.get('username'), category='error')
171 return redirect(url('users'))
171 return redirect(url('users'))
172
172
173 @HasPermissionAllDecorator('hg.admin')
173 @HasPermissionAllDecorator('hg.admin')
174 def new(self):
174 def new(self):
175 """GET /users/new: Form to create a new item"""
175 """GET /users/new: Form to create a new item"""
176 # url('new_user')
176 # url('new_user')
177 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
177 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
178 self._get_personal_repo_group_template_vars()
178 self._get_personal_repo_group_template_vars()
179 return render('admin/users/user_add.mako')
179 return render('admin/users/user_add.mako')
180
180
181 @HasPermissionAllDecorator('hg.admin')
181 @HasPermissionAllDecorator('hg.admin')
182 @auth.CSRFRequired()
182 @auth.CSRFRequired()
183 def update(self, user_id):
183 def update(self, user_id):
184 """PUT /users/user_id: Update an existing item"""
184 """PUT /users/user_id: Update an existing item"""
185 # Forms posted to this method should contain a hidden field:
185 # Forms posted to this method should contain a hidden field:
186 # <input type="hidden" name="_method" value="PUT" />
186 # <input type="hidden" name="_method" value="PUT" />
187 # Or using helpers:
187 # Or using helpers:
188 # h.form(url('update_user', user_id=ID),
188 # h.form(url('update_user', user_id=ID),
189 # method='put')
189 # method='put')
190 # url('user', user_id=ID)
190 # url('user', user_id=ID)
191 user_id = safe_int(user_id)
191 user_id = safe_int(user_id)
192 c.user = User.get_or_404(user_id)
192 c.user = User.get_or_404(user_id)
193 c.active = 'profile'
193 c.active = 'profile'
194 c.extern_type = c.user.extern_type
194 c.extern_type = c.user.extern_type
195 c.extern_name = c.user.extern_name
195 c.extern_name = c.user.extern_name
196 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
196 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
197 available_languages = [x[0] for x in c.allowed_languages]
197 available_languages = [x[0] for x in c.allowed_languages]
198 _form = UserForm(edit=True, available_languages=available_languages,
198 _form = UserForm(edit=True, available_languages=available_languages,
199 old_data={'user_id': user_id,
199 old_data={'user_id': user_id,
200 'email': c.user.email})()
200 'email': c.user.email})()
201 form_result = {}
201 form_result = {}
202 try:
202 try:
203 form_result = _form.to_python(dict(request.POST))
203 form_result = _form.to_python(dict(request.POST))
204 skip_attrs = ['extern_type', 'extern_name']
204 skip_attrs = ['extern_type', 'extern_name']
205 # TODO: plugin should define if username can be updated
205 # TODO: plugin should define if username can be updated
206 if c.extern_type != "rhodecode":
206 if c.extern_type != "rhodecode":
207 # forbid updating username for external accounts
207 # forbid updating username for external accounts
208 skip_attrs.append('username')
208 skip_attrs.append('username')
209
209
210 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
210 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
211 usr = form_result['username']
211 usr = form_result['username']
212 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
212 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
213 None, self.ip_addr, self.sa)
213 None, self.ip_addr, self.sa)
214 h.flash(_('User updated successfully'), category='success')
214 h.flash(_('User updated successfully'), category='success')
215 Session().commit()
215 Session().commit()
216 except formencode.Invalid as errors:
216 except formencode.Invalid as errors:
217 defaults = errors.value
217 defaults = errors.value
218 e = errors.error_dict or {}
218 e = errors.error_dict or {}
219
219
220 return htmlfill.render(
220 return htmlfill.render(
221 render('admin/users/user_edit.mako'),
221 render('admin/users/user_edit.mako'),
222 defaults=defaults,
222 defaults=defaults,
223 errors=e,
223 errors=e,
224 prefix_error=False,
224 prefix_error=False,
225 encoding="UTF-8",
225 encoding="UTF-8",
226 force_defaults=False)
226 force_defaults=False)
227 except UserCreationError as e:
227 except UserCreationError as e:
228 h.flash(e, 'error')
228 h.flash(e, 'error')
229 except Exception:
229 except Exception:
230 log.exception("Exception updating user")
230 log.exception("Exception updating user")
231 h.flash(_('Error occurred during update of user %s')
231 h.flash(_('Error occurred during update of user %s')
232 % form_result.get('username'), category='error')
232 % form_result.get('username'), category='error')
233 return redirect(url('edit_user', user_id=user_id))
233 return redirect(url('edit_user', user_id=user_id))
234
234
235 @HasPermissionAllDecorator('hg.admin')
235 @HasPermissionAllDecorator('hg.admin')
236 @auth.CSRFRequired()
236 @auth.CSRFRequired()
237 def delete(self, user_id):
237 def delete(self, user_id):
238 """DELETE /users/user_id: Delete an existing item"""
238 """DELETE /users/user_id: Delete an existing item"""
239 # Forms posted to this method should contain a hidden field:
239 # Forms posted to this method should contain a hidden field:
240 # <input type="hidden" name="_method" value="DELETE" />
240 # <input type="hidden" name="_method" value="DELETE" />
241 # Or using helpers:
241 # Or using helpers:
242 # h.form(url('delete_user', user_id=ID),
242 # h.form(url('delete_user', user_id=ID),
243 # method='delete')
243 # method='delete')
244 # url('user', user_id=ID)
244 # url('user', user_id=ID)
245 user_id = safe_int(user_id)
245 user_id = safe_int(user_id)
246 c.user = User.get_or_404(user_id)
246 c.user = User.get_or_404(user_id)
247
247
248 _repos = c.user.repositories
248 _repos = c.user.repositories
249 _repo_groups = c.user.repository_groups
249 _repo_groups = c.user.repository_groups
250 _user_groups = c.user.user_groups
250 _user_groups = c.user.user_groups
251
251
252 handle_repos = None
252 handle_repos = None
253 handle_repo_groups = None
253 handle_repo_groups = None
254 handle_user_groups = None
254 handle_user_groups = None
255 # dummy call for flash of handle
255 # dummy call for flash of handle
256 set_handle_flash_repos = lambda: None
256 set_handle_flash_repos = lambda: None
257 set_handle_flash_repo_groups = lambda: None
257 set_handle_flash_repo_groups = lambda: None
258 set_handle_flash_user_groups = lambda: None
258 set_handle_flash_user_groups = lambda: None
259
259
260 if _repos and request.POST.get('user_repos'):
260 if _repos and request.POST.get('user_repos'):
261 do = request.POST['user_repos']
261 do = request.POST['user_repos']
262 if do == 'detach':
262 if do == 'detach':
263 handle_repos = 'detach'
263 handle_repos = 'detach'
264 set_handle_flash_repos = lambda: h.flash(
264 set_handle_flash_repos = lambda: h.flash(
265 _('Detached %s repositories') % len(_repos),
265 _('Detached %s repositories') % len(_repos),
266 category='success')
266 category='success')
267 elif do == 'delete':
267 elif do == 'delete':
268 handle_repos = 'delete'
268 handle_repos = 'delete'
269 set_handle_flash_repos = lambda: h.flash(
269 set_handle_flash_repos = lambda: h.flash(
270 _('Deleted %s repositories') % len(_repos),
270 _('Deleted %s repositories') % len(_repos),
271 category='success')
271 category='success')
272
272
273 if _repo_groups and request.POST.get('user_repo_groups'):
273 if _repo_groups and request.POST.get('user_repo_groups'):
274 do = request.POST['user_repo_groups']
274 do = request.POST['user_repo_groups']
275 if do == 'detach':
275 if do == 'detach':
276 handle_repo_groups = 'detach'
276 handle_repo_groups = 'detach'
277 set_handle_flash_repo_groups = lambda: h.flash(
277 set_handle_flash_repo_groups = lambda: h.flash(
278 _('Detached %s repository groups') % len(_repo_groups),
278 _('Detached %s repository groups') % len(_repo_groups),
279 category='success')
279 category='success')
280 elif do == 'delete':
280 elif do == 'delete':
281 handle_repo_groups = 'delete'
281 handle_repo_groups = 'delete'
282 set_handle_flash_repo_groups = lambda: h.flash(
282 set_handle_flash_repo_groups = lambda: h.flash(
283 _('Deleted %s repository groups') % len(_repo_groups),
283 _('Deleted %s repository groups') % len(_repo_groups),
284 category='success')
284 category='success')
285
285
286 if _user_groups and request.POST.get('user_user_groups'):
286 if _user_groups and request.POST.get('user_user_groups'):
287 do = request.POST['user_user_groups']
287 do = request.POST['user_user_groups']
288 if do == 'detach':
288 if do == 'detach':
289 handle_user_groups = 'detach'
289 handle_user_groups = 'detach'
290 set_handle_flash_user_groups = lambda: h.flash(
290 set_handle_flash_user_groups = lambda: h.flash(
291 _('Detached %s user groups') % len(_user_groups),
291 _('Detached %s user groups') % len(_user_groups),
292 category='success')
292 category='success')
293 elif do == 'delete':
293 elif do == 'delete':
294 handle_user_groups = 'delete'
294 handle_user_groups = 'delete'
295 set_handle_flash_user_groups = lambda: h.flash(
295 set_handle_flash_user_groups = lambda: h.flash(
296 _('Deleted %s user groups') % len(_user_groups),
296 _('Deleted %s user groups') % len(_user_groups),
297 category='success')
297 category='success')
298
298
299 try:
299 try:
300 UserModel().delete(c.user, handle_repos=handle_repos,
300 UserModel().delete(c.user, handle_repos=handle_repos,
301 handle_repo_groups=handle_repo_groups,
301 handle_repo_groups=handle_repo_groups,
302 handle_user_groups=handle_user_groups)
302 handle_user_groups=handle_user_groups)
303 Session().commit()
303 Session().commit()
304 set_handle_flash_repos()
304 set_handle_flash_repos()
305 set_handle_flash_repo_groups()
305 set_handle_flash_repo_groups()
306 set_handle_flash_user_groups()
306 set_handle_flash_user_groups()
307 h.flash(_('Successfully deleted user'), category='success')
307 h.flash(_('Successfully deleted user'), category='success')
308 except (UserOwnsReposException, UserOwnsRepoGroupsException,
308 except (UserOwnsReposException, UserOwnsRepoGroupsException,
309 UserOwnsUserGroupsException, DefaultUserException) as e:
309 UserOwnsUserGroupsException, DefaultUserException) as e:
310 h.flash(e, category='warning')
310 h.flash(e, category='warning')
311 except Exception:
311 except Exception:
312 log.exception("Exception during deletion of user")
312 log.exception("Exception during deletion of user")
313 h.flash(_('An error occurred during deletion of user'),
313 h.flash(_('An error occurred during deletion of user'),
314 category='error')
314 category='error')
315 return redirect(url('users'))
315 return redirect(url('users'))
316
316
317 @HasPermissionAllDecorator('hg.admin')
317 @HasPermissionAllDecorator('hg.admin')
318 @auth.CSRFRequired()
318 @auth.CSRFRequired()
319 def reset_password(self, user_id):
319 def reset_password(self, user_id):
320 """
320 """
321 toggle reset password flag for this user
321 toggle reset password flag for this user
322
322
323 :param user_id:
323 :param user_id:
324 """
324 """
325 user_id = safe_int(user_id)
325 user_id = safe_int(user_id)
326 c.user = User.get_or_404(user_id)
326 c.user = User.get_or_404(user_id)
327 try:
327 try:
328 old_value = c.user.user_data.get('force_password_change')
328 old_value = c.user.user_data.get('force_password_change')
329 c.user.update_userdata(force_password_change=not old_value)
329 c.user.update_userdata(force_password_change=not old_value)
330 Session().commit()
330 Session().commit()
331 if old_value:
331 if old_value:
332 msg = _('Force password change disabled for user')
332 msg = _('Force password change disabled for user')
333 else:
333 else:
334 msg = _('Force password change enabled for user')
334 msg = _('Force password change enabled for user')
335 h.flash(msg, category='success')
335 h.flash(msg, category='success')
336 except Exception:
336 except Exception:
337 log.exception("Exception during password reset for user")
337 log.exception("Exception during password reset for user")
338 h.flash(_('An error occurred during password reset for user'),
338 h.flash(_('An error occurred during password reset for user'),
339 category='error')
339 category='error')
340
340
341 return redirect(url('edit_user_advanced', user_id=user_id))
341 return redirect(url('edit_user_advanced', user_id=user_id))
342
342
343 @HasPermissionAllDecorator('hg.admin')
343 @HasPermissionAllDecorator('hg.admin')
344 @auth.CSRFRequired()
344 @auth.CSRFRequired()
345 def create_personal_repo_group(self, user_id):
345 def create_personal_repo_group(self, user_id):
346 """
346 """
347 Create personal repository group for this user
347 Create personal repository group for this user
348
348
349 :param user_id:
349 :param user_id:
350 """
350 """
351 from rhodecode.model.repo_group import RepoGroupModel
351 from rhodecode.model.repo_group import RepoGroupModel
352
352
353 user_id = safe_int(user_id)
353 user_id = safe_int(user_id)
354 c.user = User.get_or_404(user_id)
354 c.user = User.get_or_404(user_id)
355 personal_repo_group = RepoGroup.get_user_personal_repo_group(
355 personal_repo_group = RepoGroup.get_user_personal_repo_group(
356 c.user.user_id)
356 c.user.user_id)
357 if personal_repo_group:
357 if personal_repo_group:
358 return redirect(url('edit_user_advanced', user_id=user_id))
358 return redirect(url('edit_user_advanced', user_id=user_id))
359
359
360 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
360 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
361 c.user)
361 c.user)
362 named_personal_group = RepoGroup.get_by_group_name(
362 named_personal_group = RepoGroup.get_by_group_name(
363 personal_repo_group_name)
363 personal_repo_group_name)
364 try:
364 try:
365
365
366 if named_personal_group and named_personal_group.user_id == c.user.user_id:
366 if named_personal_group and named_personal_group.user_id == c.user.user_id:
367 # migrate the same named group, and mark it as personal
367 # migrate the same named group, and mark it as personal
368 named_personal_group.personal = True
368 named_personal_group.personal = True
369 Session().add(named_personal_group)
369 Session().add(named_personal_group)
370 Session().commit()
370 Session().commit()
371 msg = _('Linked repository group `%s` as personal' % (
371 msg = _('Linked repository group `%s` as personal' % (
372 personal_repo_group_name,))
372 personal_repo_group_name,))
373 h.flash(msg, category='success')
373 h.flash(msg, category='success')
374 elif not named_personal_group:
374 elif not named_personal_group:
375 RepoGroupModel().create_personal_repo_group(c.user)
375 RepoGroupModel().create_personal_repo_group(c.user)
376
376
377 msg = _('Created repository group `%s`' % (
377 msg = _('Created repository group `%s`' % (
378 personal_repo_group_name,))
378 personal_repo_group_name,))
379 h.flash(msg, category='success')
379 h.flash(msg, category='success')
380 else:
380 else:
381 msg = _('Repository group `%s` is already taken' % (
381 msg = _('Repository group `%s` is already taken' % (
382 personal_repo_group_name,))
382 personal_repo_group_name,))
383 h.flash(msg, category='warning')
383 h.flash(msg, category='warning')
384 except Exception:
384 except Exception:
385 log.exception("Exception during repository group creation")
385 log.exception("Exception during repository group creation")
386 msg = _(
386 msg = _(
387 'An error occurred during repository group creation for user')
387 'An error occurred during repository group creation for user')
388 h.flash(msg, category='error')
388 h.flash(msg, category='error')
389 Session().rollback()
389 Session().rollback()
390
390
391 return redirect(url('edit_user_advanced', user_id=user_id))
391 return redirect(url('edit_user_advanced', user_id=user_id))
392
392
393 @HasPermissionAllDecorator('hg.admin')
393 @HasPermissionAllDecorator('hg.admin')
394 def show(self, user_id):
394 def show(self, user_id):
395 """GET /users/user_id: Show a specific item"""
395 """GET /users/user_id: Show a specific item"""
396 # url('user', user_id=ID)
396 # url('user', user_id=ID)
397 User.get_or_404(-1)
397 User.get_or_404(-1)
398
398
399 @HasPermissionAllDecorator('hg.admin')
399 @HasPermissionAllDecorator('hg.admin')
400 def edit(self, user_id):
400 def edit(self, user_id):
401 """GET /users/user_id/edit: Form to edit an existing item"""
401 """GET /users/user_id/edit: Form to edit an existing item"""
402 # url('edit_user', user_id=ID)
402 # url('edit_user', user_id=ID)
403 user_id = safe_int(user_id)
403 user_id = safe_int(user_id)
404 c.user = User.get_or_404(user_id)
404 c.user = User.get_or_404(user_id)
405 if c.user.username == User.DEFAULT_USER:
405 if c.user.username == User.DEFAULT_USER:
406 h.flash(_("You can't edit this user"), category='warning')
406 h.flash(_("You can't edit this user"), category='warning')
407 return redirect(url('users'))
407 return redirect(url('users'))
408
408
409 c.active = 'profile'
409 c.active = 'profile'
410 c.extern_type = c.user.extern_type
410 c.extern_type = c.user.extern_type
411 c.extern_name = c.user.extern_name
411 c.extern_name = c.user.extern_name
412 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
412 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
413
413
414 defaults = c.user.get_dict()
414 defaults = c.user.get_dict()
415 defaults.update({'language': c.user.user_data.get('language')})
415 defaults.update({'language': c.user.user_data.get('language')})
416 return htmlfill.render(
416 return htmlfill.render(
417 render('admin/users/user_edit.mako'),
417 render('admin/users/user_edit.mako'),
418 defaults=defaults,
418 defaults=defaults,
419 encoding="UTF-8",
419 encoding="UTF-8",
420 force_defaults=False)
420 force_defaults=False)
421
421
422 @HasPermissionAllDecorator('hg.admin')
422 @HasPermissionAllDecorator('hg.admin')
423 def edit_advanced(self, user_id):
423 def edit_advanced(self, user_id):
424 user_id = safe_int(user_id)
424 user_id = safe_int(user_id)
425 user = c.user = User.get_or_404(user_id)
425 user = c.user = User.get_or_404(user_id)
426 if user.username == User.DEFAULT_USER:
426 if user.username == User.DEFAULT_USER:
427 h.flash(_("You can't edit this user"), category='warning')
427 h.flash(_("You can't edit this user"), category='warning')
428 return redirect(url('users'))
428 return redirect(url('users'))
429
429
430 c.active = 'advanced'
430 c.active = 'advanced'
431 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
431 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
432 c.personal_repo_group = c.perm_user.personal_repo_group
432 c.personal_repo_group = c.perm_user.personal_repo_group
433 c.personal_repo_group_name = RepoGroupModel()\
433 c.personal_repo_group_name = RepoGroupModel()\
434 .get_personal_group_name(user)
434 .get_personal_group_name(user)
435 c.first_admin = User.get_first_super_admin()
435 c.first_admin = User.get_first_super_admin()
436 defaults = user.get_dict()
436 defaults = user.get_dict()
437
437
438 # Interim workaround if the user participated on any pull requests as a
438 # Interim workaround if the user participated on any pull requests as a
439 # reviewer.
439 # reviewer.
440 has_review = bool(PullRequestReviewers.query().filter(
440 has_review = bool(PullRequestReviewers.query().filter(
441 PullRequestReviewers.user_id == user_id).first())
441 PullRequestReviewers.user_id == user_id).first())
442 c.can_delete_user = not has_review
442 c.can_delete_user = not has_review
443 c.can_delete_user_message = _(
443 c.can_delete_user_message = _(
444 'The user participates as reviewer in pull requests and '
444 'The user participates as reviewer in pull requests and '
445 'cannot be deleted. You can set the user to '
445 'cannot be deleted. You can set the user to '
446 '"inactive" instead of deleting it.') if has_review else ''
446 '"inactive" instead of deleting it.') if has_review else ''
447
447
448 return htmlfill.render(
448 return htmlfill.render(
449 render('admin/users/user_edit.mako'),
449 render('admin/users/user_edit.mako'),
450 defaults=defaults,
450 defaults=defaults,
451 encoding="UTF-8",
451 encoding="UTF-8",
452 force_defaults=False)
452 force_defaults=False)
453
453
454 @HasPermissionAllDecorator('hg.admin')
454 @HasPermissionAllDecorator('hg.admin')
455 def edit_auth_tokens(self, user_id):
455 def edit_auth_tokens(self, user_id):
456 user_id = safe_int(user_id)
456 user_id = safe_int(user_id)
457 c.user = User.get_or_404(user_id)
457 c.user = User.get_or_404(user_id)
458 if c.user.username == User.DEFAULT_USER:
458 if c.user.username == User.DEFAULT_USER:
459 h.flash(_("You can't edit this user"), category='warning')
459 h.flash(_("You can't edit this user"), category='warning')
460 return redirect(url('users'))
460 return redirect(url('users'))
461
461
462 c.active = 'auth_tokens'
462 c.active = 'auth_tokens'
463 show_expired = True
463 show_expired = True
464 c.lifetime_values = [
464 c.lifetime_values = [
465 (str(-1), _('forever')),
465 (str(-1), _('forever')),
466 (str(5), _('5 minutes')),
466 (str(5), _('5 minutes')),
467 (str(60), _('1 hour')),
467 (str(60), _('1 hour')),
468 (str(60 * 24), _('1 day')),
468 (str(60 * 24), _('1 day')),
469 (str(60 * 24 * 30), _('1 month')),
469 (str(60 * 24 * 30), _('1 month')),
470 ]
470 ]
471 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
471 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
472 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
472 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
473 for x in AuthTokenModel.cls.ROLES]
473 for x in AuthTokenModel.cls.ROLES]
474 c.role_options = [(c.role_values, _("Role"))]
474 c.role_options = [(c.role_values, _("Role"))]
475 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
475 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
476 c.user.user_id, show_expired=show_expired)
476 c.user.user_id, show_expired=show_expired)
477 defaults = c.user.get_dict()
477 defaults = c.user.get_dict()
478 return htmlfill.render(
478 return htmlfill.render(
479 render('admin/users/user_edit.mako'),
479 render('admin/users/user_edit.mako'),
480 defaults=defaults,
480 defaults=defaults,
481 encoding="UTF-8",
481 encoding="UTF-8",
482 force_defaults=False)
482 force_defaults=False)
483
483
484 @HasPermissionAllDecorator('hg.admin')
484 @HasPermissionAllDecorator('hg.admin')
485 @auth.CSRFRequired()
485 @auth.CSRFRequired()
486 def add_auth_token(self, user_id):
486 def add_auth_token(self, user_id):
487 user_id = safe_int(user_id)
487 user_id = safe_int(user_id)
488 c.user = User.get_or_404(user_id)
488 c.user = User.get_or_404(user_id)
489 if c.user.username == User.DEFAULT_USER:
489 if c.user.username == User.DEFAULT_USER:
490 h.flash(_("You can't edit this user"), category='warning')
490 h.flash(_("You can't edit this user"), category='warning')
491 return redirect(url('users'))
491 return redirect(url('users'))
492
492
493 lifetime = safe_int(request.POST.get('lifetime'), -1)
493 lifetime = safe_int(request.POST.get('lifetime'), -1)
494 description = request.POST.get('description')
494 description = request.POST.get('description')
495 role = request.POST.get('role')
495 role = request.POST.get('role')
496 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
496 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
497 Session().commit()
497 Session().commit()
498 h.flash(_("Auth token successfully created"), category='success')
498 h.flash(_("Auth token successfully created"), category='success')
499 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
499 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
500
500
501 @HasPermissionAllDecorator('hg.admin')
501 @HasPermissionAllDecorator('hg.admin')
502 @auth.CSRFRequired()
502 @auth.CSRFRequired()
503 def delete_auth_token(self, user_id):
503 def delete_auth_token(self, user_id):
504 user_id = safe_int(user_id)
504 user_id = safe_int(user_id)
505 c.user = User.get_or_404(user_id)
505 c.user = User.get_or_404(user_id)
506 if c.user.username == User.DEFAULT_USER:
506 if c.user.username == User.DEFAULT_USER:
507 h.flash(_("You can't edit this user"), category='warning')
507 h.flash(_("You can't edit this user"), category='warning')
508 return redirect(url('users'))
508 return redirect(url('users'))
509
509
510 auth_token = request.POST.get('del_auth_token')
510 del_auth_token = request.POST.get('del_auth_token')
511 if request.POST.get('del_auth_token_builtin'):
511 if del_auth_token:
512 user = User.get(c.user.user_id)
512 AuthTokenModel().delete(del_auth_token, c.user.user_id)
513 if user:
514 user.api_key = generate_auth_token(user.username)
515 Session().add(user)
516 Session().commit()
517 h.flash(_("Auth token successfully reset"), category='success')
518 elif auth_token:
519 AuthTokenModel().delete(auth_token, c.user.user_id)
520 Session().commit()
513 Session().commit()
521 h.flash(_("Auth token successfully deleted"), category='success')
514 h.flash(_("Auth token successfully deleted"), category='success')
522
515
523 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
516 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
524
517
525 @HasPermissionAllDecorator('hg.admin')
518 @HasPermissionAllDecorator('hg.admin')
526 def edit_global_perms(self, user_id):
519 def edit_global_perms(self, user_id):
527 user_id = safe_int(user_id)
520 user_id = safe_int(user_id)
528 c.user = User.get_or_404(user_id)
521 c.user = User.get_or_404(user_id)
529 if c.user.username == User.DEFAULT_USER:
522 if c.user.username == User.DEFAULT_USER:
530 h.flash(_("You can't edit this user"), category='warning')
523 h.flash(_("You can't edit this user"), category='warning')
531 return redirect(url('users'))
524 return redirect(url('users'))
532
525
533 c.active = 'global_perms'
526 c.active = 'global_perms'
534
527
535 c.default_user = User.get_default_user()
528 c.default_user = User.get_default_user()
536 defaults = c.user.get_dict()
529 defaults = c.user.get_dict()
537 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
530 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
538 defaults.update(c.default_user.get_default_perms())
531 defaults.update(c.default_user.get_default_perms())
539 defaults.update(c.user.get_default_perms())
532 defaults.update(c.user.get_default_perms())
540
533
541 return htmlfill.render(
534 return htmlfill.render(
542 render('admin/users/user_edit.mako'),
535 render('admin/users/user_edit.mako'),
543 defaults=defaults,
536 defaults=defaults,
544 encoding="UTF-8",
537 encoding="UTF-8",
545 force_defaults=False)
538 force_defaults=False)
546
539
547 @HasPermissionAllDecorator('hg.admin')
540 @HasPermissionAllDecorator('hg.admin')
548 @auth.CSRFRequired()
541 @auth.CSRFRequired()
549 def update_global_perms(self, user_id):
542 def update_global_perms(self, user_id):
550 """PUT /users_perm/user_id: Update an existing item"""
543 """PUT /users_perm/user_id: Update an existing item"""
551 # url('user_perm', user_id=ID, method='put')
544 # url('user_perm', user_id=ID, method='put')
552 user_id = safe_int(user_id)
545 user_id = safe_int(user_id)
553 user = User.get_or_404(user_id)
546 user = User.get_or_404(user_id)
554 c.active = 'global_perms'
547 c.active = 'global_perms'
555 try:
548 try:
556 # first stage that verifies the checkbox
549 # first stage that verifies the checkbox
557 _form = UserIndividualPermissionsForm()
550 _form = UserIndividualPermissionsForm()
558 form_result = _form.to_python(dict(request.POST))
551 form_result = _form.to_python(dict(request.POST))
559 inherit_perms = form_result['inherit_default_permissions']
552 inherit_perms = form_result['inherit_default_permissions']
560 user.inherit_default_permissions = inherit_perms
553 user.inherit_default_permissions = inherit_perms
561 Session().add(user)
554 Session().add(user)
562
555
563 if not inherit_perms:
556 if not inherit_perms:
564 # only update the individual ones if we un check the flag
557 # only update the individual ones if we un check the flag
565 _form = UserPermissionsForm(
558 _form = UserPermissionsForm(
566 [x[0] for x in c.repo_create_choices],
559 [x[0] for x in c.repo_create_choices],
567 [x[0] for x in c.repo_create_on_write_choices],
560 [x[0] for x in c.repo_create_on_write_choices],
568 [x[0] for x in c.repo_group_create_choices],
561 [x[0] for x in c.repo_group_create_choices],
569 [x[0] for x in c.user_group_create_choices],
562 [x[0] for x in c.user_group_create_choices],
570 [x[0] for x in c.fork_choices],
563 [x[0] for x in c.fork_choices],
571 [x[0] for x in c.inherit_default_permission_choices])()
564 [x[0] for x in c.inherit_default_permission_choices])()
572
565
573 form_result = _form.to_python(dict(request.POST))
566 form_result = _form.to_python(dict(request.POST))
574 form_result.update({'perm_user_id': user.user_id})
567 form_result.update({'perm_user_id': user.user_id})
575
568
576 PermissionModel().update_user_permissions(form_result)
569 PermissionModel().update_user_permissions(form_result)
577
570
578 Session().commit()
571 Session().commit()
579 h.flash(_('User global permissions updated successfully'),
572 h.flash(_('User global permissions updated successfully'),
580 category='success')
573 category='success')
581
574
582 Session().commit()
575 Session().commit()
583 except formencode.Invalid as errors:
576 except formencode.Invalid as errors:
584 defaults = errors.value
577 defaults = errors.value
585 c.user = user
578 c.user = user
586 return htmlfill.render(
579 return htmlfill.render(
587 render('admin/users/user_edit.mako'),
580 render('admin/users/user_edit.mako'),
588 defaults=defaults,
581 defaults=defaults,
589 errors=errors.error_dict or {},
582 errors=errors.error_dict or {},
590 prefix_error=False,
583 prefix_error=False,
591 encoding="UTF-8",
584 encoding="UTF-8",
592 force_defaults=False)
585 force_defaults=False)
593 except Exception:
586 except Exception:
594 log.exception("Exception during permissions saving")
587 log.exception("Exception during permissions saving")
595 h.flash(_('An error occurred during permissions saving'),
588 h.flash(_('An error occurred during permissions saving'),
596 category='error')
589 category='error')
597 return redirect(url('edit_user_global_perms', user_id=user_id))
590 return redirect(url('edit_user_global_perms', user_id=user_id))
598
591
599 @HasPermissionAllDecorator('hg.admin')
592 @HasPermissionAllDecorator('hg.admin')
600 def edit_perms_summary(self, user_id):
593 def edit_perms_summary(self, user_id):
601 user_id = safe_int(user_id)
594 user_id = safe_int(user_id)
602 c.user = User.get_or_404(user_id)
595 c.user = User.get_or_404(user_id)
603 if c.user.username == User.DEFAULT_USER:
596 if c.user.username == User.DEFAULT_USER:
604 h.flash(_("You can't edit this user"), category='warning')
597 h.flash(_("You can't edit this user"), category='warning')
605 return redirect(url('users'))
598 return redirect(url('users'))
606
599
607 c.active = 'perms_summary'
600 c.active = 'perms_summary'
608 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
601 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
609
602
610 return render('admin/users/user_edit.mako')
603 return render('admin/users/user_edit.mako')
611
604
612 @HasPermissionAllDecorator('hg.admin')
605 @HasPermissionAllDecorator('hg.admin')
613 def edit_emails(self, user_id):
606 def edit_emails(self, user_id):
614 user_id = safe_int(user_id)
607 user_id = safe_int(user_id)
615 c.user = User.get_or_404(user_id)
608 c.user = User.get_or_404(user_id)
616 if c.user.username == User.DEFAULT_USER:
609 if c.user.username == User.DEFAULT_USER:
617 h.flash(_("You can't edit this user"), category='warning')
610 h.flash(_("You can't edit this user"), category='warning')
618 return redirect(url('users'))
611 return redirect(url('users'))
619
612
620 c.active = 'emails'
613 c.active = 'emails'
621 c.user_email_map = UserEmailMap.query() \
614 c.user_email_map = UserEmailMap.query() \
622 .filter(UserEmailMap.user == c.user).all()
615 .filter(UserEmailMap.user == c.user).all()
623
616
624 defaults = c.user.get_dict()
617 defaults = c.user.get_dict()
625 return htmlfill.render(
618 return htmlfill.render(
626 render('admin/users/user_edit.mako'),
619 render('admin/users/user_edit.mako'),
627 defaults=defaults,
620 defaults=defaults,
628 encoding="UTF-8",
621 encoding="UTF-8",
629 force_defaults=False)
622 force_defaults=False)
630
623
631 @HasPermissionAllDecorator('hg.admin')
624 @HasPermissionAllDecorator('hg.admin')
632 @auth.CSRFRequired()
625 @auth.CSRFRequired()
633 def add_email(self, user_id):
626 def add_email(self, user_id):
634 """POST /user_emails:Add an existing item"""
627 """POST /user_emails:Add an existing item"""
635 # url('user_emails', user_id=ID, method='put')
628 # url('user_emails', user_id=ID, method='put')
636 user_id = safe_int(user_id)
629 user_id = safe_int(user_id)
637 c.user = User.get_or_404(user_id)
630 c.user = User.get_or_404(user_id)
638
631
639 email = request.POST.get('new_email')
632 email = request.POST.get('new_email')
640 user_model = UserModel()
633 user_model = UserModel()
641
634
642 try:
635 try:
643 user_model.add_extra_email(user_id, email)
636 user_model.add_extra_email(user_id, email)
644 Session().commit()
637 Session().commit()
645 h.flash(_("Added new email address `%s` for user account") % email,
638 h.flash(_("Added new email address `%s` for user account") % email,
646 category='success')
639 category='success')
647 except formencode.Invalid as error:
640 except formencode.Invalid as error:
648 msg = error.error_dict['email']
641 msg = error.error_dict['email']
649 h.flash(msg, category='error')
642 h.flash(msg, category='error')
650 except Exception:
643 except Exception:
651 log.exception("Exception during email saving")
644 log.exception("Exception during email saving")
652 h.flash(_('An error occurred during email saving'),
645 h.flash(_('An error occurred during email saving'),
653 category='error')
646 category='error')
654 return redirect(url('edit_user_emails', user_id=user_id))
647 return redirect(url('edit_user_emails', user_id=user_id))
655
648
656 @HasPermissionAllDecorator('hg.admin')
649 @HasPermissionAllDecorator('hg.admin')
657 @auth.CSRFRequired()
650 @auth.CSRFRequired()
658 def delete_email(self, user_id):
651 def delete_email(self, user_id):
659 """DELETE /user_emails_delete/user_id: Delete an existing item"""
652 """DELETE /user_emails_delete/user_id: Delete an existing item"""
660 # url('user_emails_delete', user_id=ID, method='delete')
653 # url('user_emails_delete', user_id=ID, method='delete')
661 user_id = safe_int(user_id)
654 user_id = safe_int(user_id)
662 c.user = User.get_or_404(user_id)
655 c.user = User.get_or_404(user_id)
663 email_id = request.POST.get('del_email_id')
656 email_id = request.POST.get('del_email_id')
664 user_model = UserModel()
657 user_model = UserModel()
665 user_model.delete_extra_email(user_id, email_id)
658 user_model.delete_extra_email(user_id, email_id)
666 Session().commit()
659 Session().commit()
667 h.flash(_("Removed email address from user account"), category='success')
660 h.flash(_("Removed email address from user account"), category='success')
668 return redirect(url('edit_user_emails', user_id=user_id))
661 return redirect(url('edit_user_emails', user_id=user_id))
669
662
670 @HasPermissionAllDecorator('hg.admin')
663 @HasPermissionAllDecorator('hg.admin')
671 def edit_ips(self, user_id):
664 def edit_ips(self, user_id):
672 user_id = safe_int(user_id)
665 user_id = safe_int(user_id)
673 c.user = User.get_or_404(user_id)
666 c.user = User.get_or_404(user_id)
674 if c.user.username == User.DEFAULT_USER:
667 if c.user.username == User.DEFAULT_USER:
675 h.flash(_("You can't edit this user"), category='warning')
668 h.flash(_("You can't edit this user"), category='warning')
676 return redirect(url('users'))
669 return redirect(url('users'))
677
670
678 c.active = 'ips'
671 c.active = 'ips'
679 c.user_ip_map = UserIpMap.query() \
672 c.user_ip_map = UserIpMap.query() \
680 .filter(UserIpMap.user == c.user).all()
673 .filter(UserIpMap.user == c.user).all()
681
674
682 c.inherit_default_ips = c.user.inherit_default_permissions
675 c.inherit_default_ips = c.user.inherit_default_permissions
683 c.default_user_ip_map = UserIpMap.query() \
676 c.default_user_ip_map = UserIpMap.query() \
684 .filter(UserIpMap.user == User.get_default_user()).all()
677 .filter(UserIpMap.user == User.get_default_user()).all()
685
678
686 defaults = c.user.get_dict()
679 defaults = c.user.get_dict()
687 return htmlfill.render(
680 return htmlfill.render(
688 render('admin/users/user_edit.mako'),
681 render('admin/users/user_edit.mako'),
689 defaults=defaults,
682 defaults=defaults,
690 encoding="UTF-8",
683 encoding="UTF-8",
691 force_defaults=False)
684 force_defaults=False)
692
685
693 @HasPermissionAllDecorator('hg.admin')
686 @HasPermissionAllDecorator('hg.admin')
694 @auth.CSRFRequired()
687 @auth.CSRFRequired()
695 def add_ip(self, user_id):
688 def add_ip(self, user_id):
696 """POST /user_ips:Add an existing item"""
689 """POST /user_ips:Add an existing item"""
697 # url('user_ips', user_id=ID, method='put')
690 # url('user_ips', user_id=ID, method='put')
698
691
699 user_id = safe_int(user_id)
692 user_id = safe_int(user_id)
700 c.user = User.get_or_404(user_id)
693 c.user = User.get_or_404(user_id)
701 user_model = UserModel()
694 user_model = UserModel()
702 try:
695 try:
703 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
696 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
704 except Exception as e:
697 except Exception as e:
705 ip_list = []
698 ip_list = []
706 log.exception("Exception during ip saving")
699 log.exception("Exception during ip saving")
707 h.flash(_('An error occurred during ip saving:%s' % (e,)),
700 h.flash(_('An error occurred during ip saving:%s' % (e,)),
708 category='error')
701 category='error')
709
702
710 desc = request.POST.get('description')
703 desc = request.POST.get('description')
711 added = []
704 added = []
712 for ip in ip_list:
705 for ip in ip_list:
713 try:
706 try:
714 user_model.add_extra_ip(user_id, ip, desc)
707 user_model.add_extra_ip(user_id, ip, desc)
715 Session().commit()
708 Session().commit()
716 added.append(ip)
709 added.append(ip)
717 except formencode.Invalid as error:
710 except formencode.Invalid as error:
718 msg = error.error_dict['ip']
711 msg = error.error_dict['ip']
719 h.flash(msg, category='error')
712 h.flash(msg, category='error')
720 except Exception:
713 except Exception:
721 log.exception("Exception during ip saving")
714 log.exception("Exception during ip saving")
722 h.flash(_('An error occurred during ip saving'),
715 h.flash(_('An error occurred during ip saving'),
723 category='error')
716 category='error')
724 if added:
717 if added:
725 h.flash(
718 h.flash(
726 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
719 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
727 category='success')
720 category='success')
728 if 'default_user' in request.POST:
721 if 'default_user' in request.POST:
729 return redirect(url('admin_permissions_ips'))
722 return redirect(url('admin_permissions_ips'))
730 return redirect(url('edit_user_ips', user_id=user_id))
723 return redirect(url('edit_user_ips', user_id=user_id))
731
724
732 @HasPermissionAllDecorator('hg.admin')
725 @HasPermissionAllDecorator('hg.admin')
733 @auth.CSRFRequired()
726 @auth.CSRFRequired()
734 def delete_ip(self, user_id):
727 def delete_ip(self, user_id):
735 """DELETE /user_ips_delete/user_id: Delete an existing item"""
728 """DELETE /user_ips_delete/user_id: Delete an existing item"""
736 # url('user_ips_delete', user_id=ID, method='delete')
729 # url('user_ips_delete', user_id=ID, method='delete')
737 user_id = safe_int(user_id)
730 user_id = safe_int(user_id)
738 c.user = User.get_or_404(user_id)
731 c.user = User.get_or_404(user_id)
739
732
740 ip_id = request.POST.get('del_ip_id')
733 ip_id = request.POST.get('del_ip_id')
741 user_model = UserModel()
734 user_model = UserModel()
742 user_model.delete_extra_ip(user_id, ip_id)
735 user_model.delete_extra_ip(user_id, ip_id)
743 Session().commit()
736 Session().commit()
744 h.flash(_("Removed ip address from user whitelist"), category='success')
737 h.flash(_("Removed ip address from user whitelist"), category='success')
745
738
746 if 'default_user' in request.POST:
739 if 'default_user' in request.POST:
747 return redirect(url('admin_permissions_ips'))
740 return redirect(url('admin_permissions_ips'))
748 return redirect(url('edit_user_ips', user_id=user_id))
741 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,3909 +1,3902
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
354 HOOK_PUSH = 'changegroup.push_logger'
354 HOOK_PUSH = 'changegroup.push_logger'
355
355
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # git part is currently hardcoded.
357 # git part is currently hardcoded.
358
358
359 # SVN PATTERNS
359 # SVN PATTERNS
360 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_TAG_ID = 'vcs_svn_tag'
361 SVN_TAG_ID = 'vcs_svn_tag'
362
362
363 ui_id = Column(
363 ui_id = Column(
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 primary_key=True)
365 primary_key=True)
366 ui_section = Column(
366 ui_section = Column(
367 "ui_section", String(255), nullable=True, unique=None, default=None)
367 "ui_section", String(255), nullable=True, unique=None, default=None)
368 ui_key = Column(
368 ui_key = Column(
369 "ui_key", String(255), nullable=True, unique=None, default=None)
369 "ui_key", String(255), nullable=True, unique=None, default=None)
370 ui_value = Column(
370 ui_value = Column(
371 "ui_value", String(255), nullable=True, unique=None, default=None)
371 "ui_value", String(255), nullable=True, unique=None, default=None)
372 ui_active = Column(
372 ui_active = Column(
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374
374
375 def __repr__(self):
375 def __repr__(self):
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 self.ui_key, self.ui_value)
377 self.ui_key, self.ui_value)
378
378
379
379
380 class RepoRhodeCodeSetting(Base, BaseModel):
380 class RepoRhodeCodeSetting(Base, BaseModel):
381 __tablename__ = 'repo_rhodecode_settings'
381 __tablename__ = 'repo_rhodecode_settings'
382 __table_args__ = (
382 __table_args__ = (
383 UniqueConstraint(
383 UniqueConstraint(
384 'app_settings_name', 'repository_id',
384 'app_settings_name', 'repository_id',
385 name='uq_repo_rhodecode_setting_name_repo_id'),
385 name='uq_repo_rhodecode_setting_name_repo_id'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 repository_id = Column(
390 repository_id = Column(
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 nullable=False)
392 nullable=False)
393 app_settings_id = Column(
393 app_settings_id = Column(
394 "app_settings_id", Integer(), nullable=False, unique=True,
394 "app_settings_id", Integer(), nullable=False, unique=True,
395 default=None, primary_key=True)
395 default=None, primary_key=True)
396 app_settings_name = Column(
396 app_settings_name = Column(
397 "app_settings_name", String(255), nullable=True, unique=None,
397 "app_settings_name", String(255), nullable=True, unique=None,
398 default=None)
398 default=None)
399 _app_settings_value = Column(
399 _app_settings_value = Column(
400 "app_settings_value", String(4096), nullable=True, unique=None,
400 "app_settings_value", String(4096), nullable=True, unique=None,
401 default=None)
401 default=None)
402 _app_settings_type = Column(
402 _app_settings_type = Column(
403 "app_settings_type", String(255), nullable=True, unique=None,
403 "app_settings_type", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405
405
406 repository = relationship('Repository')
406 repository = relationship('Repository')
407
407
408 def __init__(self, repository_id, key='', val='', type='unicode'):
408 def __init__(self, repository_id, key='', val='', type='unicode'):
409 self.repository_id = repository_id
409 self.repository_id = repository_id
410 self.app_settings_name = key
410 self.app_settings_name = key
411 self.app_settings_type = type
411 self.app_settings_type = type
412 self.app_settings_value = val
412 self.app_settings_value = val
413
413
414 @validates('_app_settings_value')
414 @validates('_app_settings_value')
415 def validate_settings_value(self, key, val):
415 def validate_settings_value(self, key, val):
416 assert type(val) == unicode
416 assert type(val) == unicode
417 return val
417 return val
418
418
419 @hybrid_property
419 @hybrid_property
420 def app_settings_value(self):
420 def app_settings_value(self):
421 v = self._app_settings_value
421 v = self._app_settings_value
422 type_ = self.app_settings_type
422 type_ = self.app_settings_type
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 return converter(v)
425 return converter(v)
426
426
427 @app_settings_value.setter
427 @app_settings_value.setter
428 def app_settings_value(self, val):
428 def app_settings_value(self, val):
429 """
429 """
430 Setter that will always make sure we use unicode in app_settings_value
430 Setter that will always make sure we use unicode in app_settings_value
431
431
432 :param val:
432 :param val:
433 """
433 """
434 self._app_settings_value = safe_unicode(val)
434 self._app_settings_value = safe_unicode(val)
435
435
436 @hybrid_property
436 @hybrid_property
437 def app_settings_type(self):
437 def app_settings_type(self):
438 return self._app_settings_type
438 return self._app_settings_type
439
439
440 @app_settings_type.setter
440 @app_settings_type.setter
441 def app_settings_type(self, val):
441 def app_settings_type(self, val):
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 if val not in SETTINGS_TYPES:
443 if val not in SETTINGS_TYPES:
444 raise Exception('type must be one of %s got %s'
444 raise Exception('type must be one of %s got %s'
445 % (SETTINGS_TYPES.keys(), val))
445 % (SETTINGS_TYPES.keys(), val))
446 self._app_settings_type = val
446 self._app_settings_type = val
447
447
448 def __unicode__(self):
448 def __unicode__(self):
449 return u"<%s('%s:%s:%s[%s]')>" % (
449 return u"<%s('%s:%s:%s[%s]')>" % (
450 self.__class__.__name__, self.repository.repo_name,
450 self.__class__.__name__, self.repository.repo_name,
451 self.app_settings_name, self.app_settings_value,
451 self.app_settings_name, self.app_settings_value,
452 self.app_settings_type
452 self.app_settings_type
453 )
453 )
454
454
455
455
456 class RepoRhodeCodeUi(Base, BaseModel):
456 class RepoRhodeCodeUi(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_ui'
457 __tablename__ = 'repo_rhodecode_ui'
458 __table_args__ = (
458 __table_args__ = (
459 UniqueConstraint(
459 UniqueConstraint(
460 'repository_id', 'ui_section', 'ui_key',
460 'repository_id', 'ui_section', 'ui_key',
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 )
464 )
465
465
466 repository_id = Column(
466 repository_id = Column(
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 nullable=False)
468 nullable=False)
469 ui_id = Column(
469 ui_id = Column(
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 primary_key=True)
471 primary_key=True)
472 ui_section = Column(
472 ui_section = Column(
473 "ui_section", String(255), nullable=True, unique=None, default=None)
473 "ui_section", String(255), nullable=True, unique=None, default=None)
474 ui_key = Column(
474 ui_key = Column(
475 "ui_key", String(255), nullable=True, unique=None, default=None)
475 "ui_key", String(255), nullable=True, unique=None, default=None)
476 ui_value = Column(
476 ui_value = Column(
477 "ui_value", String(255), nullable=True, unique=None, default=None)
477 "ui_value", String(255), nullable=True, unique=None, default=None)
478 ui_active = Column(
478 ui_active = Column(
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480
480
481 repository = relationship('Repository')
481 repository = relationship('Repository')
482
482
483 def __repr__(self):
483 def __repr__(self):
484 return '<%s[%s:%s]%s=>%s]>' % (
484 return '<%s[%s:%s]%s=>%s]>' % (
485 self.__class__.__name__, self.repository.repo_name,
485 self.__class__.__name__, self.repository.repo_name,
486 self.ui_section, self.ui_key, self.ui_value)
486 self.ui_section, self.ui_key, self.ui_value)
487
487
488
488
489 class User(Base, BaseModel):
489 class User(Base, BaseModel):
490 __tablename__ = 'users'
490 __tablename__ = 'users'
491 __table_args__ = (
491 __table_args__ = (
492 UniqueConstraint('username'), UniqueConstraint('email'),
492 UniqueConstraint('username'), UniqueConstraint('email'),
493 Index('u_username_idx', 'username'),
493 Index('u_username_idx', 'username'),
494 Index('u_email_idx', 'email'),
494 Index('u_email_idx', 'email'),
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 )
497 )
498 DEFAULT_USER = 'default'
498 DEFAULT_USER = 'default'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501
501
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517
517
518 user_log = relationship('UserLog')
518 user_log = relationship('UserLog')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520
520
521 repositories = relationship('Repository')
521 repositories = relationship('Repository')
522 repository_groups = relationship('RepoGroup')
522 repository_groups = relationship('RepoGroup')
523 user_groups = relationship('UserGroup')
523 user_groups = relationship('UserGroup')
524
524
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527
527
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531
531
532 group_member = relationship('UserGroupMember', cascade='all')
532 group_member = relationship('UserGroupMember', cascade='all')
533
533
534 notifications = relationship('UserNotification', cascade='all')
534 notifications = relationship('UserNotification', cascade='all')
535 # notifications assigned to this user
535 # notifications assigned to this user
536 user_created_notifications = relationship('Notification', cascade='all')
536 user_created_notifications = relationship('Notification', cascade='all')
537 # comments created by this user
537 # comments created by this user
538 user_comments = relationship('ChangesetComment', cascade='all')
538 user_comments = relationship('ChangesetComment', cascade='all')
539 # user profile extra info
539 # user profile extra info
540 user_emails = relationship('UserEmailMap', cascade='all')
540 user_emails = relationship('UserEmailMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 # gists
543 # gists
544 user_gists = relationship('Gist', cascade='all')
544 user_gists = relationship('Gist', cascade='all')
545 # user pull requests
545 # user pull requests
546 user_pull_requests = relationship('PullRequest', cascade='all')
546 user_pull_requests = relationship('PullRequest', cascade='all')
547 # external identities
547 # external identities
548 extenal_identities = relationship(
548 extenal_identities = relationship(
549 'ExternalIdentity',
549 'ExternalIdentity',
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 cascade='all')
551 cascade='all')
552
552
553 def __unicode__(self):
553 def __unicode__(self):
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 self.user_id, self.username)
555 self.user_id, self.username)
556
556
557 @hybrid_property
557 @hybrid_property
558 def email(self):
558 def email(self):
559 return self._email
559 return self._email
560
560
561 @email.setter
561 @email.setter
562 def email(self, val):
562 def email(self, val):
563 self._email = val.lower() if val else None
563 self._email = val.lower() if val else None
564
564
565 @property
565 @property
566 def firstname(self):
566 def firstname(self):
567 # alias for future
567 # alias for future
568 return self.name
568 return self.name
569
569
570 @property
570 @property
571 def emails(self):
571 def emails(self):
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 return [self.email] + [x.email for x in other]
573 return [self.email] + [x.email for x in other]
574
574
575 @property
575 @property
576 def auth_tokens(self):
576 def auth_tokens(self):
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578
578
579 @property
579 @property
580 def extra_auth_tokens(self):
580 def extra_auth_tokens(self):
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582
582
583 @property
583 @property
584 def feed_token(self):
584 def feed_token(self):
585 return self.get_feed_token()
585 return self.get_feed_token()
586
586
587 def get_feed_token(self):
587 def get_feed_token(self):
588 feed_tokens = UserApiKeys.query()\
588 feed_tokens = UserApiKeys.query()\
589 .filter(UserApiKeys.user == self)\
589 .filter(UserApiKeys.user == self)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
591 .all()
591 .all()
592 if feed_tokens:
592 if feed_tokens:
593 return feed_tokens[0].api_key
593 return feed_tokens[0].api_key
594 return 'NO_FEED_TOKEN_AVAILABLE'
594 return 'NO_FEED_TOKEN_AVAILABLE'
595
595
596 @classmethod
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
601 if role:
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
604 return tokens.all()
605
605
606 def authenticate_by_token(self, auth_token, roles=None,
606 def authenticate_by_token(self, auth_token, roles=None,
607 include_builtin_token=False):
607 include_builtin_token=False):
608 from rhodecode.lib import auth
608 from rhodecode.lib import auth
609
609
610 log.debug('Trying to authenticate user: %s via auth-token, '
610 log.debug('Trying to authenticate user: %s via auth-token, '
611 'and roles: %s', self, roles)
611 'and roles: %s', self, roles)
612
612
613 if not auth_token:
613 if not auth_token:
614 return False
614 return False
615
615
616 crypto_backend = auth.crypto_backend()
616 crypto_backend = auth.crypto_backend()
617
617
618 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
618 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
619 tokens_q = UserApiKeys.query()\
619 tokens_q = UserApiKeys.query()\
620 .filter(UserApiKeys.user_id == self.user_id)\
620 .filter(UserApiKeys.user_id == self.user_id)\
621 .filter(or_(UserApiKeys.expires == -1,
621 .filter(or_(UserApiKeys.expires == -1,
622 UserApiKeys.expires >= time.time()))
622 UserApiKeys.expires >= time.time()))
623
623
624 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
624 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
625
625
626 maybe_builtin = []
626 maybe_builtin = []
627 if include_builtin_token:
627 if include_builtin_token:
628 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
628 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
629
629
630 plain_tokens = []
630 plain_tokens = []
631 hash_tokens = []
631 hash_tokens = []
632
632
633 for token in tokens_q.all() + maybe_builtin:
633 for token in tokens_q.all() + maybe_builtin:
634 if token.api_key.startswith(crypto_backend.ENC_PREF):
634 if token.api_key.startswith(crypto_backend.ENC_PREF):
635 hash_tokens.append(token.api_key)
635 hash_tokens.append(token.api_key)
636 else:
636 else:
637 plain_tokens.append(token.api_key)
637 plain_tokens.append(token.api_key)
638
638
639 is_plain_match = auth_token in plain_tokens
639 is_plain_match = auth_token in plain_tokens
640 if is_plain_match:
640 if is_plain_match:
641 return True
641 return True
642
642
643 for hashed in hash_tokens:
643 for hashed in hash_tokens:
644 # marcink: this is expensive to calculate, but the most secure
644 # marcink: this is expensive to calculate, but the most secure
645 match = crypto_backend.hash_check(auth_token, hashed)
645 match = crypto_backend.hash_check(auth_token, hashed)
646 if match:
646 if match:
647 return True
647 return True
648
648
649 return False
649 return False
650
650
651 @property
651 @property
652 def builtin_token_roles(self):
653 roles = [
654 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
655 ]
656 return map(UserApiKeys._get_role_name, roles)
657
658 @property
659 def ip_addresses(self):
652 def ip_addresses(self):
660 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
653 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
661 return [x.ip_addr for x in ret]
654 return [x.ip_addr for x in ret]
662
655
663 @property
656 @property
664 def username_and_name(self):
657 def username_and_name(self):
665 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
658 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
666
659
667 @property
660 @property
668 def username_or_name_or_email(self):
661 def username_or_name_or_email(self):
669 full_name = self.full_name if self.full_name is not ' ' else None
662 full_name = self.full_name if self.full_name is not ' ' else None
670 return self.username or full_name or self.email
663 return self.username or full_name or self.email
671
664
672 @property
665 @property
673 def full_name(self):
666 def full_name(self):
674 return '%s %s' % (self.firstname, self.lastname)
667 return '%s %s' % (self.firstname, self.lastname)
675
668
676 @property
669 @property
677 def full_name_or_username(self):
670 def full_name_or_username(self):
678 return ('%s %s' % (self.firstname, self.lastname)
671 return ('%s %s' % (self.firstname, self.lastname)
679 if (self.firstname and self.lastname) else self.username)
672 if (self.firstname and self.lastname) else self.username)
680
673
681 @property
674 @property
682 def full_contact(self):
675 def full_contact(self):
683 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
676 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
684
677
685 @property
678 @property
686 def short_contact(self):
679 def short_contact(self):
687 return '%s %s' % (self.firstname, self.lastname)
680 return '%s %s' % (self.firstname, self.lastname)
688
681
689 @property
682 @property
690 def is_admin(self):
683 def is_admin(self):
691 return self.admin
684 return self.admin
692
685
693 @property
686 @property
694 def AuthUser(self):
687 def AuthUser(self):
695 """
688 """
696 Returns instance of AuthUser for this user
689 Returns instance of AuthUser for this user
697 """
690 """
698 from rhodecode.lib.auth import AuthUser
691 from rhodecode.lib.auth import AuthUser
699 return AuthUser(user_id=self.user_id, api_key=self.api_key,
692 return AuthUser(user_id=self.user_id, api_key=self.api_key,
700 username=self.username)
693 username=self.username)
701
694
702 @hybrid_property
695 @hybrid_property
703 def user_data(self):
696 def user_data(self):
704 if not self._user_data:
697 if not self._user_data:
705 return {}
698 return {}
706
699
707 try:
700 try:
708 return json.loads(self._user_data)
701 return json.loads(self._user_data)
709 except TypeError:
702 except TypeError:
710 return {}
703 return {}
711
704
712 @user_data.setter
705 @user_data.setter
713 def user_data(self, val):
706 def user_data(self, val):
714 if not isinstance(val, dict):
707 if not isinstance(val, dict):
715 raise Exception('user_data must be dict, got %s' % type(val))
708 raise Exception('user_data must be dict, got %s' % type(val))
716 try:
709 try:
717 self._user_data = json.dumps(val)
710 self._user_data = json.dumps(val)
718 except Exception:
711 except Exception:
719 log.error(traceback.format_exc())
712 log.error(traceback.format_exc())
720
713
721 @classmethod
714 @classmethod
722 def get_by_username(cls, username, case_insensitive=False,
715 def get_by_username(cls, username, case_insensitive=False,
723 cache=False, identity_cache=False):
716 cache=False, identity_cache=False):
724 session = Session()
717 session = Session()
725
718
726 if case_insensitive:
719 if case_insensitive:
727 q = cls.query().filter(
720 q = cls.query().filter(
728 func.lower(cls.username) == func.lower(username))
721 func.lower(cls.username) == func.lower(username))
729 else:
722 else:
730 q = cls.query().filter(cls.username == username)
723 q = cls.query().filter(cls.username == username)
731
724
732 if cache:
725 if cache:
733 if identity_cache:
726 if identity_cache:
734 val = cls.identity_cache(session, 'username', username)
727 val = cls.identity_cache(session, 'username', username)
735 if val:
728 if val:
736 return val
729 return val
737 else:
730 else:
738 q = q.options(
731 q = q.options(
739 FromCache("sql_cache_short",
732 FromCache("sql_cache_short",
740 "get_user_by_name_%s" % _hash_key(username)))
733 "get_user_by_name_%s" % _hash_key(username)))
741
734
742 return q.scalar()
735 return q.scalar()
743
736
744 @classmethod
737 @classmethod
745 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
738 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
746 q = cls.query().filter(cls.api_key == auth_token)
739 q = cls.query().filter(cls.api_key == auth_token)
747
740
748 if cache:
741 if cache:
749 q = q.options(FromCache("sql_cache_short",
742 q = q.options(FromCache("sql_cache_short",
750 "get_auth_token_%s" % auth_token))
743 "get_auth_token_%s" % auth_token))
751 res = q.scalar()
744 res = q.scalar()
752
745
753 if fallback and not res:
746 if fallback and not res:
754 #fallback to additional keys
747 #fallback to additional keys
755 _res = UserApiKeys.query()\
748 _res = UserApiKeys.query()\
756 .filter(UserApiKeys.api_key == auth_token)\
749 .filter(UserApiKeys.api_key == auth_token)\
757 .filter(or_(UserApiKeys.expires == -1,
750 .filter(or_(UserApiKeys.expires == -1,
758 UserApiKeys.expires >= time.time()))\
751 UserApiKeys.expires >= time.time()))\
759 .first()
752 .first()
760 if _res:
753 if _res:
761 res = _res.user
754 res = _res.user
762 return res
755 return res
763
756
764 @classmethod
757 @classmethod
765 def get_by_email(cls, email, case_insensitive=False, cache=False):
758 def get_by_email(cls, email, case_insensitive=False, cache=False):
766
759
767 if case_insensitive:
760 if case_insensitive:
768 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
761 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
769
762
770 else:
763 else:
771 q = cls.query().filter(cls.email == email)
764 q = cls.query().filter(cls.email == email)
772
765
773 if cache:
766 if cache:
774 q = q.options(FromCache("sql_cache_short",
767 q = q.options(FromCache("sql_cache_short",
775 "get_email_key_%s" % _hash_key(email)))
768 "get_email_key_%s" % _hash_key(email)))
776
769
777 ret = q.scalar()
770 ret = q.scalar()
778 if ret is None:
771 if ret is None:
779 q = UserEmailMap.query()
772 q = UserEmailMap.query()
780 # try fetching in alternate email map
773 # try fetching in alternate email map
781 if case_insensitive:
774 if case_insensitive:
782 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
775 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
783 else:
776 else:
784 q = q.filter(UserEmailMap.email == email)
777 q = q.filter(UserEmailMap.email == email)
785 q = q.options(joinedload(UserEmailMap.user))
778 q = q.options(joinedload(UserEmailMap.user))
786 if cache:
779 if cache:
787 q = q.options(FromCache("sql_cache_short",
780 q = q.options(FromCache("sql_cache_short",
788 "get_email_map_key_%s" % email))
781 "get_email_map_key_%s" % email))
789 ret = getattr(q.scalar(), 'user', None)
782 ret = getattr(q.scalar(), 'user', None)
790
783
791 return ret
784 return ret
792
785
793 @classmethod
786 @classmethod
794 def get_from_cs_author(cls, author):
787 def get_from_cs_author(cls, author):
795 """
788 """
796 Tries to get User objects out of commit author string
789 Tries to get User objects out of commit author string
797
790
798 :param author:
791 :param author:
799 """
792 """
800 from rhodecode.lib.helpers import email, author_name
793 from rhodecode.lib.helpers import email, author_name
801 # Valid email in the attribute passed, see if they're in the system
794 # Valid email in the attribute passed, see if they're in the system
802 _email = email(author)
795 _email = email(author)
803 if _email:
796 if _email:
804 user = cls.get_by_email(_email, case_insensitive=True)
797 user = cls.get_by_email(_email, case_insensitive=True)
805 if user:
798 if user:
806 return user
799 return user
807 # Maybe we can match by username?
800 # Maybe we can match by username?
808 _author = author_name(author)
801 _author = author_name(author)
809 user = cls.get_by_username(_author, case_insensitive=True)
802 user = cls.get_by_username(_author, case_insensitive=True)
810 if user:
803 if user:
811 return user
804 return user
812
805
813 def update_userdata(self, **kwargs):
806 def update_userdata(self, **kwargs):
814 usr = self
807 usr = self
815 old = usr.user_data
808 old = usr.user_data
816 old.update(**kwargs)
809 old.update(**kwargs)
817 usr.user_data = old
810 usr.user_data = old
818 Session().add(usr)
811 Session().add(usr)
819 log.debug('updated userdata with ', kwargs)
812 log.debug('updated userdata with ', kwargs)
820
813
821 def update_lastlogin(self):
814 def update_lastlogin(self):
822 """Update user lastlogin"""
815 """Update user lastlogin"""
823 self.last_login = datetime.datetime.now()
816 self.last_login = datetime.datetime.now()
824 Session().add(self)
817 Session().add(self)
825 log.debug('updated user %s lastlogin', self.username)
818 log.debug('updated user %s lastlogin', self.username)
826
819
827 def update_lastactivity(self):
820 def update_lastactivity(self):
828 """Update user lastactivity"""
821 """Update user lastactivity"""
829 usr = self
822 usr = self
830 old = usr.user_data
823 old = usr.user_data
831 old.update({'last_activity': time.time()})
824 old.update({'last_activity': time.time()})
832 usr.user_data = old
825 usr.user_data = old
833 Session().add(usr)
826 Session().add(usr)
834 log.debug('updated user %s lastactivity', usr.username)
827 log.debug('updated user %s lastactivity', usr.username)
835
828
836 def update_password(self, new_password, change_api_key=False):
829 def update_password(self, new_password, change_api_key=False):
837 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
830 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
838
831
839 self.password = get_crypt_password(new_password)
832 self.password = get_crypt_password(new_password)
840 if change_api_key:
833 if change_api_key:
841 self.api_key = generate_auth_token(self.username)
834 self.api_key = generate_auth_token(self.username)
842 Session().add(self)
835 Session().add(self)
843
836
844 @classmethod
837 @classmethod
845 def get_first_super_admin(cls):
838 def get_first_super_admin(cls):
846 user = User.query().filter(User.admin == true()).first()
839 user = User.query().filter(User.admin == true()).first()
847 if user is None:
840 if user is None:
848 raise Exception('FATAL: Missing administrative account!')
841 raise Exception('FATAL: Missing administrative account!')
849 return user
842 return user
850
843
851 @classmethod
844 @classmethod
852 def get_all_super_admins(cls):
845 def get_all_super_admins(cls):
853 """
846 """
854 Returns all admin accounts sorted by username
847 Returns all admin accounts sorted by username
855 """
848 """
856 return User.query().filter(User.admin == true())\
849 return User.query().filter(User.admin == true())\
857 .order_by(User.username.asc()).all()
850 .order_by(User.username.asc()).all()
858
851
859 @classmethod
852 @classmethod
860 def get_default_user(cls, cache=False):
853 def get_default_user(cls, cache=False):
861 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
854 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
862 if user is None:
855 if user is None:
863 raise Exception('FATAL: Missing default account!')
856 raise Exception('FATAL: Missing default account!')
864 return user
857 return user
865
858
866 def _get_default_perms(self, user, suffix=''):
859 def _get_default_perms(self, user, suffix=''):
867 from rhodecode.model.permission import PermissionModel
860 from rhodecode.model.permission import PermissionModel
868 return PermissionModel().get_default_perms(user.user_perms, suffix)
861 return PermissionModel().get_default_perms(user.user_perms, suffix)
869
862
870 def get_default_perms(self, suffix=''):
863 def get_default_perms(self, suffix=''):
871 return self._get_default_perms(self, suffix)
864 return self._get_default_perms(self, suffix)
872
865
873 def get_api_data(self, include_secrets=False, details='full'):
866 def get_api_data(self, include_secrets=False, details='full'):
874 """
867 """
875 Common function for generating user related data for API
868 Common function for generating user related data for API
876
869
877 :param include_secrets: By default secrets in the API data will be replaced
870 :param include_secrets: By default secrets in the API data will be replaced
878 by a placeholder value to prevent exposing this data by accident. In case
871 by a placeholder value to prevent exposing this data by accident. In case
879 this data shall be exposed, set this flag to ``True``.
872 this data shall be exposed, set this flag to ``True``.
880
873
881 :param details: details can be 'basic|full' basic gives only a subset of
874 :param details: details can be 'basic|full' basic gives only a subset of
882 the available user information that includes user_id, name and emails.
875 the available user information that includes user_id, name and emails.
883 """
876 """
884 user = self
877 user = self
885 user_data = self.user_data
878 user_data = self.user_data
886 data = {
879 data = {
887 'user_id': user.user_id,
880 'user_id': user.user_id,
888 'username': user.username,
881 'username': user.username,
889 'firstname': user.name,
882 'firstname': user.name,
890 'lastname': user.lastname,
883 'lastname': user.lastname,
891 'email': user.email,
884 'email': user.email,
892 'emails': user.emails,
885 'emails': user.emails,
893 }
886 }
894 if details == 'basic':
887 if details == 'basic':
895 return data
888 return data
896
889
897 api_key_length = 40
890 api_key_length = 40
898 api_key_replacement = '*' * api_key_length
891 api_key_replacement = '*' * api_key_length
899
892
900 extras = {
893 extras = {
901 'api_key': api_key_replacement,
894 'api_key': api_key_replacement,
902 'api_keys': [api_key_replacement],
895 'api_keys': [api_key_replacement],
903 'active': user.active,
896 'active': user.active,
904 'admin': user.admin,
897 'admin': user.admin,
905 'extern_type': user.extern_type,
898 'extern_type': user.extern_type,
906 'extern_name': user.extern_name,
899 'extern_name': user.extern_name,
907 'last_login': user.last_login,
900 'last_login': user.last_login,
908 'ip_addresses': user.ip_addresses,
901 'ip_addresses': user.ip_addresses,
909 'language': user_data.get('language')
902 'language': user_data.get('language')
910 }
903 }
911 data.update(extras)
904 data.update(extras)
912
905
913 if include_secrets:
906 if include_secrets:
914 data['api_key'] = user.api_key
907 data['api_key'] = user.api_key
915 data['api_keys'] = user.auth_tokens
908 data['api_keys'] = user.auth_tokens
916 return data
909 return data
917
910
918 def __json__(self):
911 def __json__(self):
919 data = {
912 data = {
920 'full_name': self.full_name,
913 'full_name': self.full_name,
921 'full_name_or_username': self.full_name_or_username,
914 'full_name_or_username': self.full_name_or_username,
922 'short_contact': self.short_contact,
915 'short_contact': self.short_contact,
923 'full_contact': self.full_contact,
916 'full_contact': self.full_contact,
924 }
917 }
925 data.update(self.get_api_data())
918 data.update(self.get_api_data())
926 return data
919 return data
927
920
928
921
929 class UserApiKeys(Base, BaseModel):
922 class UserApiKeys(Base, BaseModel):
930 __tablename__ = 'user_api_keys'
923 __tablename__ = 'user_api_keys'
931 __table_args__ = (
924 __table_args__ = (
932 Index('uak_api_key_idx', 'api_key'),
925 Index('uak_api_key_idx', 'api_key'),
933 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
926 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
934 UniqueConstraint('api_key'),
927 UniqueConstraint('api_key'),
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
929 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
937 )
930 )
938 __mapper_args__ = {}
931 __mapper_args__ = {}
939
932
940 # ApiKey role
933 # ApiKey role
941 ROLE_ALL = 'token_role_all'
934 ROLE_ALL = 'token_role_all'
942 ROLE_HTTP = 'token_role_http'
935 ROLE_HTTP = 'token_role_http'
943 ROLE_VCS = 'token_role_vcs'
936 ROLE_VCS = 'token_role_vcs'
944 ROLE_API = 'token_role_api'
937 ROLE_API = 'token_role_api'
945 ROLE_FEED = 'token_role_feed'
938 ROLE_FEED = 'token_role_feed'
946 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
939 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
947
940
948 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
950 api_key = Column("api_key", String(255), nullable=False, unique=True)
943 api_key = Column("api_key", String(255), nullable=False, unique=True)
951 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
944 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
952 expires = Column('expires', Float(53), nullable=False)
945 expires = Column('expires', Float(53), nullable=False)
953 role = Column('role', String(255), nullable=True)
946 role = Column('role', String(255), nullable=True)
954 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
947 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
955
948
956 user = relationship('User', lazy='joined')
949 user = relationship('User', lazy='joined')
957
950
958 @classmethod
951 @classmethod
959 def _get_role_name(cls, role):
952 def _get_role_name(cls, role):
960 return {
953 return {
961 cls.ROLE_ALL: _('all'),
954 cls.ROLE_ALL: _('all'),
962 cls.ROLE_HTTP: _('http/web interface'),
955 cls.ROLE_HTTP: _('http/web interface'),
963 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
956 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
964 cls.ROLE_API: _('api calls'),
957 cls.ROLE_API: _('api calls'),
965 cls.ROLE_FEED: _('feed access'),
958 cls.ROLE_FEED: _('feed access'),
966 }.get(role, role)
959 }.get(role, role)
967
960
968 @property
961 @property
969 def expired(self):
962 def expired(self):
970 if self.expires == -1:
963 if self.expires == -1:
971 return False
964 return False
972 return time.time() > self.expires
965 return time.time() > self.expires
973
966
974 @property
967 @property
975 def role_humanized(self):
968 def role_humanized(self):
976 return self._get_role_name(self.role)
969 return self._get_role_name(self.role)
977
970
978
971
979 class UserEmailMap(Base, BaseModel):
972 class UserEmailMap(Base, BaseModel):
980 __tablename__ = 'user_email_map'
973 __tablename__ = 'user_email_map'
981 __table_args__ = (
974 __table_args__ = (
982 Index('uem_email_idx', 'email'),
975 Index('uem_email_idx', 'email'),
983 UniqueConstraint('email'),
976 UniqueConstraint('email'),
984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
977 {'extend_existing': True, 'mysql_engine': 'InnoDB',
985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
978 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
986 )
979 )
987 __mapper_args__ = {}
980 __mapper_args__ = {}
988
981
989 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
982 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
990 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
983 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
991 _email = Column("email", String(255), nullable=True, unique=False, default=None)
984 _email = Column("email", String(255), nullable=True, unique=False, default=None)
992 user = relationship('User', lazy='joined')
985 user = relationship('User', lazy='joined')
993
986
994 @validates('_email')
987 @validates('_email')
995 def validate_email(self, key, email):
988 def validate_email(self, key, email):
996 # check if this email is not main one
989 # check if this email is not main one
997 main_email = Session().query(User).filter(User.email == email).scalar()
990 main_email = Session().query(User).filter(User.email == email).scalar()
998 if main_email is not None:
991 if main_email is not None:
999 raise AttributeError('email %s is present is user table' % email)
992 raise AttributeError('email %s is present is user table' % email)
1000 return email
993 return email
1001
994
1002 @hybrid_property
995 @hybrid_property
1003 def email(self):
996 def email(self):
1004 return self._email
997 return self._email
1005
998
1006 @email.setter
999 @email.setter
1007 def email(self, val):
1000 def email(self, val):
1008 self._email = val.lower() if val else None
1001 self._email = val.lower() if val else None
1009
1002
1010
1003
1011 class UserIpMap(Base, BaseModel):
1004 class UserIpMap(Base, BaseModel):
1012 __tablename__ = 'user_ip_map'
1005 __tablename__ = 'user_ip_map'
1013 __table_args__ = (
1006 __table_args__ = (
1014 UniqueConstraint('user_id', 'ip_addr'),
1007 UniqueConstraint('user_id', 'ip_addr'),
1015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1008 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1016 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1009 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1017 )
1010 )
1018 __mapper_args__ = {}
1011 __mapper_args__ = {}
1019
1012
1020 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1013 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1021 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1014 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1022 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1015 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1023 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1016 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1024 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1017 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1025 user = relationship('User', lazy='joined')
1018 user = relationship('User', lazy='joined')
1026
1019
1027 @classmethod
1020 @classmethod
1028 def _get_ip_range(cls, ip_addr):
1021 def _get_ip_range(cls, ip_addr):
1029 net = ipaddress.ip_network(ip_addr, strict=False)
1022 net = ipaddress.ip_network(ip_addr, strict=False)
1030 return [str(net.network_address), str(net.broadcast_address)]
1023 return [str(net.network_address), str(net.broadcast_address)]
1031
1024
1032 def __json__(self):
1025 def __json__(self):
1033 return {
1026 return {
1034 'ip_addr': self.ip_addr,
1027 'ip_addr': self.ip_addr,
1035 'ip_range': self._get_ip_range(self.ip_addr),
1028 'ip_range': self._get_ip_range(self.ip_addr),
1036 }
1029 }
1037
1030
1038 def __unicode__(self):
1031 def __unicode__(self):
1039 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1032 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1040 self.user_id, self.ip_addr)
1033 self.user_id, self.ip_addr)
1041
1034
1042 class UserLog(Base, BaseModel):
1035 class UserLog(Base, BaseModel):
1043 __tablename__ = 'user_logs'
1036 __tablename__ = 'user_logs'
1044 __table_args__ = (
1037 __table_args__ = (
1045 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1046 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1039 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1047 )
1040 )
1048 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1041 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1050 username = Column("username", String(255), nullable=True, unique=None, default=None)
1043 username = Column("username", String(255), nullable=True, unique=None, default=None)
1051 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1044 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1052 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1045 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1053 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1046 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1054 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1047 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1055 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1048 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1056
1049
1057 def __unicode__(self):
1050 def __unicode__(self):
1058 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1059 self.repository_name,
1052 self.repository_name,
1060 self.action)
1053 self.action)
1061
1054
1062 @property
1055 @property
1063 def action_as_day(self):
1056 def action_as_day(self):
1064 return datetime.date(*self.action_date.timetuple()[:3])
1057 return datetime.date(*self.action_date.timetuple()[:3])
1065
1058
1066 user = relationship('User')
1059 user = relationship('User')
1067 repository = relationship('Repository', cascade='')
1060 repository = relationship('Repository', cascade='')
1068
1061
1069
1062
1070 class UserGroup(Base, BaseModel):
1063 class UserGroup(Base, BaseModel):
1071 __tablename__ = 'users_groups'
1064 __tablename__ = 'users_groups'
1072 __table_args__ = (
1065 __table_args__ = (
1073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1067 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1075 )
1068 )
1076
1069
1077 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1070 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1078 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1071 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1079 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1072 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1080 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1073 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1081 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1074 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1075 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1083 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1076 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1084 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1077 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1085
1078
1086 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1079 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1087 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1080 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1088 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1081 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1089 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1082 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1090 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1083 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1091 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1084 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1092
1085
1093 user = relationship('User')
1086 user = relationship('User')
1094
1087
1095 @hybrid_property
1088 @hybrid_property
1096 def group_data(self):
1089 def group_data(self):
1097 if not self._group_data:
1090 if not self._group_data:
1098 return {}
1091 return {}
1099
1092
1100 try:
1093 try:
1101 return json.loads(self._group_data)
1094 return json.loads(self._group_data)
1102 except TypeError:
1095 except TypeError:
1103 return {}
1096 return {}
1104
1097
1105 @group_data.setter
1098 @group_data.setter
1106 def group_data(self, val):
1099 def group_data(self, val):
1107 try:
1100 try:
1108 self._group_data = json.dumps(val)
1101 self._group_data = json.dumps(val)
1109 except Exception:
1102 except Exception:
1110 log.error(traceback.format_exc())
1103 log.error(traceback.format_exc())
1111
1104
1112 def __unicode__(self):
1105 def __unicode__(self):
1113 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1106 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1114 self.users_group_id,
1107 self.users_group_id,
1115 self.users_group_name)
1108 self.users_group_name)
1116
1109
1117 @classmethod
1110 @classmethod
1118 def get_by_group_name(cls, group_name, cache=False,
1111 def get_by_group_name(cls, group_name, cache=False,
1119 case_insensitive=False):
1112 case_insensitive=False):
1120 if case_insensitive:
1113 if case_insensitive:
1121 q = cls.query().filter(func.lower(cls.users_group_name) ==
1114 q = cls.query().filter(func.lower(cls.users_group_name) ==
1122 func.lower(group_name))
1115 func.lower(group_name))
1123
1116
1124 else:
1117 else:
1125 q = cls.query().filter(cls.users_group_name == group_name)
1118 q = cls.query().filter(cls.users_group_name == group_name)
1126 if cache:
1119 if cache:
1127 q = q.options(FromCache(
1120 q = q.options(FromCache(
1128 "sql_cache_short",
1121 "sql_cache_short",
1129 "get_group_%s" % _hash_key(group_name)))
1122 "get_group_%s" % _hash_key(group_name)))
1130 return q.scalar()
1123 return q.scalar()
1131
1124
1132 @classmethod
1125 @classmethod
1133 def get(cls, user_group_id, cache=False):
1126 def get(cls, user_group_id, cache=False):
1134 user_group = cls.query()
1127 user_group = cls.query()
1135 if cache:
1128 if cache:
1136 user_group = user_group.options(FromCache("sql_cache_short",
1129 user_group = user_group.options(FromCache("sql_cache_short",
1137 "get_users_group_%s" % user_group_id))
1130 "get_users_group_%s" % user_group_id))
1138 return user_group.get(user_group_id)
1131 return user_group.get(user_group_id)
1139
1132
1140 def permissions(self, with_admins=True, with_owner=True):
1133 def permissions(self, with_admins=True, with_owner=True):
1141 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1134 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1142 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1135 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1143 joinedload(UserUserGroupToPerm.user),
1136 joinedload(UserUserGroupToPerm.user),
1144 joinedload(UserUserGroupToPerm.permission),)
1137 joinedload(UserUserGroupToPerm.permission),)
1145
1138
1146 # get owners and admins and permissions. We do a trick of re-writing
1139 # get owners and admins and permissions. We do a trick of re-writing
1147 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1140 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1148 # has a global reference and changing one object propagates to all
1141 # has a global reference and changing one object propagates to all
1149 # others. This means if admin is also an owner admin_row that change
1142 # others. This means if admin is also an owner admin_row that change
1150 # would propagate to both objects
1143 # would propagate to both objects
1151 perm_rows = []
1144 perm_rows = []
1152 for _usr in q.all():
1145 for _usr in q.all():
1153 usr = AttributeDict(_usr.user.get_dict())
1146 usr = AttributeDict(_usr.user.get_dict())
1154 usr.permission = _usr.permission.permission_name
1147 usr.permission = _usr.permission.permission_name
1155 perm_rows.append(usr)
1148 perm_rows.append(usr)
1156
1149
1157 # filter the perm rows by 'default' first and then sort them by
1150 # filter the perm rows by 'default' first and then sort them by
1158 # admin,write,read,none permissions sorted again alphabetically in
1151 # admin,write,read,none permissions sorted again alphabetically in
1159 # each group
1152 # each group
1160 perm_rows = sorted(perm_rows, key=display_sort)
1153 perm_rows = sorted(perm_rows, key=display_sort)
1161
1154
1162 _admin_perm = 'usergroup.admin'
1155 _admin_perm = 'usergroup.admin'
1163 owner_row = []
1156 owner_row = []
1164 if with_owner:
1157 if with_owner:
1165 usr = AttributeDict(self.user.get_dict())
1158 usr = AttributeDict(self.user.get_dict())
1166 usr.owner_row = True
1159 usr.owner_row = True
1167 usr.permission = _admin_perm
1160 usr.permission = _admin_perm
1168 owner_row.append(usr)
1161 owner_row.append(usr)
1169
1162
1170 super_admin_rows = []
1163 super_admin_rows = []
1171 if with_admins:
1164 if with_admins:
1172 for usr in User.get_all_super_admins():
1165 for usr in User.get_all_super_admins():
1173 # if this admin is also owner, don't double the record
1166 # if this admin is also owner, don't double the record
1174 if usr.user_id == owner_row[0].user_id:
1167 if usr.user_id == owner_row[0].user_id:
1175 owner_row[0].admin_row = True
1168 owner_row[0].admin_row = True
1176 else:
1169 else:
1177 usr = AttributeDict(usr.get_dict())
1170 usr = AttributeDict(usr.get_dict())
1178 usr.admin_row = True
1171 usr.admin_row = True
1179 usr.permission = _admin_perm
1172 usr.permission = _admin_perm
1180 super_admin_rows.append(usr)
1173 super_admin_rows.append(usr)
1181
1174
1182 return super_admin_rows + owner_row + perm_rows
1175 return super_admin_rows + owner_row + perm_rows
1183
1176
1184 def permission_user_groups(self):
1177 def permission_user_groups(self):
1185 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1178 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1186 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1179 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1187 joinedload(UserGroupUserGroupToPerm.target_user_group),
1180 joinedload(UserGroupUserGroupToPerm.target_user_group),
1188 joinedload(UserGroupUserGroupToPerm.permission),)
1181 joinedload(UserGroupUserGroupToPerm.permission),)
1189
1182
1190 perm_rows = []
1183 perm_rows = []
1191 for _user_group in q.all():
1184 for _user_group in q.all():
1192 usr = AttributeDict(_user_group.user_group.get_dict())
1185 usr = AttributeDict(_user_group.user_group.get_dict())
1193 usr.permission = _user_group.permission.permission_name
1186 usr.permission = _user_group.permission.permission_name
1194 perm_rows.append(usr)
1187 perm_rows.append(usr)
1195
1188
1196 return perm_rows
1189 return perm_rows
1197
1190
1198 def _get_default_perms(self, user_group, suffix=''):
1191 def _get_default_perms(self, user_group, suffix=''):
1199 from rhodecode.model.permission import PermissionModel
1192 from rhodecode.model.permission import PermissionModel
1200 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1193 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1201
1194
1202 def get_default_perms(self, suffix=''):
1195 def get_default_perms(self, suffix=''):
1203 return self._get_default_perms(self, suffix)
1196 return self._get_default_perms(self, suffix)
1204
1197
1205 def get_api_data(self, with_group_members=True, include_secrets=False):
1198 def get_api_data(self, with_group_members=True, include_secrets=False):
1206 """
1199 """
1207 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1200 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1208 basically forwarded.
1201 basically forwarded.
1209
1202
1210 """
1203 """
1211 user_group = self
1204 user_group = self
1212
1205
1213 data = {
1206 data = {
1214 'users_group_id': user_group.users_group_id,
1207 'users_group_id': user_group.users_group_id,
1215 'group_name': user_group.users_group_name,
1208 'group_name': user_group.users_group_name,
1216 'group_description': user_group.user_group_description,
1209 'group_description': user_group.user_group_description,
1217 'active': user_group.users_group_active,
1210 'active': user_group.users_group_active,
1218 'owner': user_group.user.username,
1211 'owner': user_group.user.username,
1219 }
1212 }
1220 if with_group_members:
1213 if with_group_members:
1221 users = []
1214 users = []
1222 for user in user_group.members:
1215 for user in user_group.members:
1223 user = user.user
1216 user = user.user
1224 users.append(user.get_api_data(include_secrets=include_secrets))
1217 users.append(user.get_api_data(include_secrets=include_secrets))
1225 data['users'] = users
1218 data['users'] = users
1226
1219
1227 return data
1220 return data
1228
1221
1229
1222
1230 class UserGroupMember(Base, BaseModel):
1223 class UserGroupMember(Base, BaseModel):
1231 __tablename__ = 'users_groups_members'
1224 __tablename__ = 'users_groups_members'
1232 __table_args__ = (
1225 __table_args__ = (
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1226 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1227 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1235 )
1228 )
1236
1229
1237 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1230 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1231 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1239 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1232 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1240
1233
1241 user = relationship('User', lazy='joined')
1234 user = relationship('User', lazy='joined')
1242 users_group = relationship('UserGroup')
1235 users_group = relationship('UserGroup')
1243
1236
1244 def __init__(self, gr_id='', u_id=''):
1237 def __init__(self, gr_id='', u_id=''):
1245 self.users_group_id = gr_id
1238 self.users_group_id = gr_id
1246 self.user_id = u_id
1239 self.user_id = u_id
1247
1240
1248
1241
1249 class RepositoryField(Base, BaseModel):
1242 class RepositoryField(Base, BaseModel):
1250 __tablename__ = 'repositories_fields'
1243 __tablename__ = 'repositories_fields'
1251 __table_args__ = (
1244 __table_args__ = (
1252 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1245 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1253 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1246 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1254 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1247 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1255 )
1248 )
1256 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1249 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1257
1250
1258 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1259 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1252 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1260 field_key = Column("field_key", String(250))
1253 field_key = Column("field_key", String(250))
1261 field_label = Column("field_label", String(1024), nullable=False)
1254 field_label = Column("field_label", String(1024), nullable=False)
1262 field_value = Column("field_value", String(10000), nullable=False)
1255 field_value = Column("field_value", String(10000), nullable=False)
1263 field_desc = Column("field_desc", String(1024), nullable=False)
1256 field_desc = Column("field_desc", String(1024), nullable=False)
1264 field_type = Column("field_type", String(255), nullable=False, unique=None)
1257 field_type = Column("field_type", String(255), nullable=False, unique=None)
1265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1258 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1266
1259
1267 repository = relationship('Repository')
1260 repository = relationship('Repository')
1268
1261
1269 @property
1262 @property
1270 def field_key_prefixed(self):
1263 def field_key_prefixed(self):
1271 return 'ex_%s' % self.field_key
1264 return 'ex_%s' % self.field_key
1272
1265
1273 @classmethod
1266 @classmethod
1274 def un_prefix_key(cls, key):
1267 def un_prefix_key(cls, key):
1275 if key.startswith(cls.PREFIX):
1268 if key.startswith(cls.PREFIX):
1276 return key[len(cls.PREFIX):]
1269 return key[len(cls.PREFIX):]
1277 return key
1270 return key
1278
1271
1279 @classmethod
1272 @classmethod
1280 def get_by_key_name(cls, key, repo):
1273 def get_by_key_name(cls, key, repo):
1281 row = cls.query()\
1274 row = cls.query()\
1282 .filter(cls.repository == repo)\
1275 .filter(cls.repository == repo)\
1283 .filter(cls.field_key == key).scalar()
1276 .filter(cls.field_key == key).scalar()
1284 return row
1277 return row
1285
1278
1286
1279
1287 class Repository(Base, BaseModel):
1280 class Repository(Base, BaseModel):
1288 __tablename__ = 'repositories'
1281 __tablename__ = 'repositories'
1289 __table_args__ = (
1282 __table_args__ = (
1290 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1283 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1284 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1292 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1285 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1293 )
1286 )
1294 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1287 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1295 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1288 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1296
1289
1297 STATE_CREATED = 'repo_state_created'
1290 STATE_CREATED = 'repo_state_created'
1298 STATE_PENDING = 'repo_state_pending'
1291 STATE_PENDING = 'repo_state_pending'
1299 STATE_ERROR = 'repo_state_error'
1292 STATE_ERROR = 'repo_state_error'
1300
1293
1301 LOCK_AUTOMATIC = 'lock_auto'
1294 LOCK_AUTOMATIC = 'lock_auto'
1302 LOCK_API = 'lock_api'
1295 LOCK_API = 'lock_api'
1303 LOCK_WEB = 'lock_web'
1296 LOCK_WEB = 'lock_web'
1304 LOCK_PULL = 'lock_pull'
1297 LOCK_PULL = 'lock_pull'
1305
1298
1306 NAME_SEP = URL_SEP
1299 NAME_SEP = URL_SEP
1307
1300
1308 repo_id = Column(
1301 repo_id = Column(
1309 "repo_id", Integer(), nullable=False, unique=True, default=None,
1302 "repo_id", Integer(), nullable=False, unique=True, default=None,
1310 primary_key=True)
1303 primary_key=True)
1311 _repo_name = Column(
1304 _repo_name = Column(
1312 "repo_name", Text(), nullable=False, default=None)
1305 "repo_name", Text(), nullable=False, default=None)
1313 _repo_name_hash = Column(
1306 _repo_name_hash = Column(
1314 "repo_name_hash", String(255), nullable=False, unique=True)
1307 "repo_name_hash", String(255), nullable=False, unique=True)
1315 repo_state = Column("repo_state", String(255), nullable=True)
1308 repo_state = Column("repo_state", String(255), nullable=True)
1316
1309
1317 clone_uri = Column(
1310 clone_uri = Column(
1318 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1311 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1319 default=None)
1312 default=None)
1320 repo_type = Column(
1313 repo_type = Column(
1321 "repo_type", String(255), nullable=False, unique=False, default=None)
1314 "repo_type", String(255), nullable=False, unique=False, default=None)
1322 user_id = Column(
1315 user_id = Column(
1323 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1316 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1324 unique=False, default=None)
1317 unique=False, default=None)
1325 private = Column(
1318 private = Column(
1326 "private", Boolean(), nullable=True, unique=None, default=None)
1319 "private", Boolean(), nullable=True, unique=None, default=None)
1327 enable_statistics = Column(
1320 enable_statistics = Column(
1328 "statistics", Boolean(), nullable=True, unique=None, default=True)
1321 "statistics", Boolean(), nullable=True, unique=None, default=True)
1329 enable_downloads = Column(
1322 enable_downloads = Column(
1330 "downloads", Boolean(), nullable=True, unique=None, default=True)
1323 "downloads", Boolean(), nullable=True, unique=None, default=True)
1331 description = Column(
1324 description = Column(
1332 "description", String(10000), nullable=True, unique=None, default=None)
1325 "description", String(10000), nullable=True, unique=None, default=None)
1333 created_on = Column(
1326 created_on = Column(
1334 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1327 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1335 default=datetime.datetime.now)
1328 default=datetime.datetime.now)
1336 updated_on = Column(
1329 updated_on = Column(
1337 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1330 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1338 default=datetime.datetime.now)
1331 default=datetime.datetime.now)
1339 _landing_revision = Column(
1332 _landing_revision = Column(
1340 "landing_revision", String(255), nullable=False, unique=False,
1333 "landing_revision", String(255), nullable=False, unique=False,
1341 default=None)
1334 default=None)
1342 enable_locking = Column(
1335 enable_locking = Column(
1343 "enable_locking", Boolean(), nullable=False, unique=None,
1336 "enable_locking", Boolean(), nullable=False, unique=None,
1344 default=False)
1337 default=False)
1345 _locked = Column(
1338 _locked = Column(
1346 "locked", String(255), nullable=True, unique=False, default=None)
1339 "locked", String(255), nullable=True, unique=False, default=None)
1347 _changeset_cache = Column(
1340 _changeset_cache = Column(
1348 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1341 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1349
1342
1350 fork_id = Column(
1343 fork_id = Column(
1351 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1344 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1352 nullable=True, unique=False, default=None)
1345 nullable=True, unique=False, default=None)
1353 group_id = Column(
1346 group_id = Column(
1354 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1347 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1355 unique=False, default=None)
1348 unique=False, default=None)
1356
1349
1357 user = relationship('User', lazy='joined')
1350 user = relationship('User', lazy='joined')
1358 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1351 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1359 group = relationship('RepoGroup', lazy='joined')
1352 group = relationship('RepoGroup', lazy='joined')
1360 repo_to_perm = relationship(
1353 repo_to_perm = relationship(
1361 'UserRepoToPerm', cascade='all',
1354 'UserRepoToPerm', cascade='all',
1362 order_by='UserRepoToPerm.repo_to_perm_id')
1355 order_by='UserRepoToPerm.repo_to_perm_id')
1363 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1356 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1364 stats = relationship('Statistics', cascade='all', uselist=False)
1357 stats = relationship('Statistics', cascade='all', uselist=False)
1365
1358
1366 followers = relationship(
1359 followers = relationship(
1367 'UserFollowing',
1360 'UserFollowing',
1368 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1361 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1369 cascade='all')
1362 cascade='all')
1370 extra_fields = relationship(
1363 extra_fields = relationship(
1371 'RepositoryField', cascade="all, delete, delete-orphan")
1364 'RepositoryField', cascade="all, delete, delete-orphan")
1372 logs = relationship('UserLog')
1365 logs = relationship('UserLog')
1373 comments = relationship(
1366 comments = relationship(
1374 'ChangesetComment', cascade="all, delete, delete-orphan")
1367 'ChangesetComment', cascade="all, delete, delete-orphan")
1375 pull_requests_source = relationship(
1368 pull_requests_source = relationship(
1376 'PullRequest',
1369 'PullRequest',
1377 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1370 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1378 cascade="all, delete, delete-orphan")
1371 cascade="all, delete, delete-orphan")
1379 pull_requests_target = relationship(
1372 pull_requests_target = relationship(
1380 'PullRequest',
1373 'PullRequest',
1381 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1374 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1382 cascade="all, delete, delete-orphan")
1375 cascade="all, delete, delete-orphan")
1383 ui = relationship('RepoRhodeCodeUi', cascade="all")
1376 ui = relationship('RepoRhodeCodeUi', cascade="all")
1384 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1377 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1385 integrations = relationship('Integration',
1378 integrations = relationship('Integration',
1386 cascade="all, delete, delete-orphan")
1379 cascade="all, delete, delete-orphan")
1387
1380
1388 def __unicode__(self):
1381 def __unicode__(self):
1389 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1382 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1390 safe_unicode(self.repo_name))
1383 safe_unicode(self.repo_name))
1391
1384
1392 @hybrid_property
1385 @hybrid_property
1393 def landing_rev(self):
1386 def landing_rev(self):
1394 # always should return [rev_type, rev]
1387 # always should return [rev_type, rev]
1395 if self._landing_revision:
1388 if self._landing_revision:
1396 _rev_info = self._landing_revision.split(':')
1389 _rev_info = self._landing_revision.split(':')
1397 if len(_rev_info) < 2:
1390 if len(_rev_info) < 2:
1398 _rev_info.insert(0, 'rev')
1391 _rev_info.insert(0, 'rev')
1399 return [_rev_info[0], _rev_info[1]]
1392 return [_rev_info[0], _rev_info[1]]
1400 return [None, None]
1393 return [None, None]
1401
1394
1402 @landing_rev.setter
1395 @landing_rev.setter
1403 def landing_rev(self, val):
1396 def landing_rev(self, val):
1404 if ':' not in val:
1397 if ':' not in val:
1405 raise ValueError('value must be delimited with `:` and consist '
1398 raise ValueError('value must be delimited with `:` and consist '
1406 'of <rev_type>:<rev>, got %s instead' % val)
1399 'of <rev_type>:<rev>, got %s instead' % val)
1407 self._landing_revision = val
1400 self._landing_revision = val
1408
1401
1409 @hybrid_property
1402 @hybrid_property
1410 def locked(self):
1403 def locked(self):
1411 if self._locked:
1404 if self._locked:
1412 user_id, timelocked, reason = self._locked.split(':')
1405 user_id, timelocked, reason = self._locked.split(':')
1413 lock_values = int(user_id), timelocked, reason
1406 lock_values = int(user_id), timelocked, reason
1414 else:
1407 else:
1415 lock_values = [None, None, None]
1408 lock_values = [None, None, None]
1416 return lock_values
1409 return lock_values
1417
1410
1418 @locked.setter
1411 @locked.setter
1419 def locked(self, val):
1412 def locked(self, val):
1420 if val and isinstance(val, (list, tuple)):
1413 if val and isinstance(val, (list, tuple)):
1421 self._locked = ':'.join(map(str, val))
1414 self._locked = ':'.join(map(str, val))
1422 else:
1415 else:
1423 self._locked = None
1416 self._locked = None
1424
1417
1425 @hybrid_property
1418 @hybrid_property
1426 def changeset_cache(self):
1419 def changeset_cache(self):
1427 from rhodecode.lib.vcs.backends.base import EmptyCommit
1420 from rhodecode.lib.vcs.backends.base import EmptyCommit
1428 dummy = EmptyCommit().__json__()
1421 dummy = EmptyCommit().__json__()
1429 if not self._changeset_cache:
1422 if not self._changeset_cache:
1430 return dummy
1423 return dummy
1431 try:
1424 try:
1432 return json.loads(self._changeset_cache)
1425 return json.loads(self._changeset_cache)
1433 except TypeError:
1426 except TypeError:
1434 return dummy
1427 return dummy
1435 except Exception:
1428 except Exception:
1436 log.error(traceback.format_exc())
1429 log.error(traceback.format_exc())
1437 return dummy
1430 return dummy
1438
1431
1439 @changeset_cache.setter
1432 @changeset_cache.setter
1440 def changeset_cache(self, val):
1433 def changeset_cache(self, val):
1441 try:
1434 try:
1442 self._changeset_cache = json.dumps(val)
1435 self._changeset_cache = json.dumps(val)
1443 except Exception:
1436 except Exception:
1444 log.error(traceback.format_exc())
1437 log.error(traceback.format_exc())
1445
1438
1446 @hybrid_property
1439 @hybrid_property
1447 def repo_name(self):
1440 def repo_name(self):
1448 return self._repo_name
1441 return self._repo_name
1449
1442
1450 @repo_name.setter
1443 @repo_name.setter
1451 def repo_name(self, value):
1444 def repo_name(self, value):
1452 self._repo_name = value
1445 self._repo_name = value
1453 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1446 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1454
1447
1455 @classmethod
1448 @classmethod
1456 def normalize_repo_name(cls, repo_name):
1449 def normalize_repo_name(cls, repo_name):
1457 """
1450 """
1458 Normalizes os specific repo_name to the format internally stored inside
1451 Normalizes os specific repo_name to the format internally stored inside
1459 database using URL_SEP
1452 database using URL_SEP
1460
1453
1461 :param cls:
1454 :param cls:
1462 :param repo_name:
1455 :param repo_name:
1463 """
1456 """
1464 return cls.NAME_SEP.join(repo_name.split(os.sep))
1457 return cls.NAME_SEP.join(repo_name.split(os.sep))
1465
1458
1466 @classmethod
1459 @classmethod
1467 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1460 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1468 session = Session()
1461 session = Session()
1469 q = session.query(cls).filter(cls.repo_name == repo_name)
1462 q = session.query(cls).filter(cls.repo_name == repo_name)
1470
1463
1471 if cache:
1464 if cache:
1472 if identity_cache:
1465 if identity_cache:
1473 val = cls.identity_cache(session, 'repo_name', repo_name)
1466 val = cls.identity_cache(session, 'repo_name', repo_name)
1474 if val:
1467 if val:
1475 return val
1468 return val
1476 else:
1469 else:
1477 q = q.options(
1470 q = q.options(
1478 FromCache("sql_cache_short",
1471 FromCache("sql_cache_short",
1479 "get_repo_by_name_%s" % _hash_key(repo_name)))
1472 "get_repo_by_name_%s" % _hash_key(repo_name)))
1480
1473
1481 return q.scalar()
1474 return q.scalar()
1482
1475
1483 @classmethod
1476 @classmethod
1484 def get_by_full_path(cls, repo_full_path):
1477 def get_by_full_path(cls, repo_full_path):
1485 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1478 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1486 repo_name = cls.normalize_repo_name(repo_name)
1479 repo_name = cls.normalize_repo_name(repo_name)
1487 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1480 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1488
1481
1489 @classmethod
1482 @classmethod
1490 def get_repo_forks(cls, repo_id):
1483 def get_repo_forks(cls, repo_id):
1491 return cls.query().filter(Repository.fork_id == repo_id)
1484 return cls.query().filter(Repository.fork_id == repo_id)
1492
1485
1493 @classmethod
1486 @classmethod
1494 def base_path(cls):
1487 def base_path(cls):
1495 """
1488 """
1496 Returns base path when all repos are stored
1489 Returns base path when all repos are stored
1497
1490
1498 :param cls:
1491 :param cls:
1499 """
1492 """
1500 q = Session().query(RhodeCodeUi)\
1493 q = Session().query(RhodeCodeUi)\
1501 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1494 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1502 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1495 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1503 return q.one().ui_value
1496 return q.one().ui_value
1504
1497
1505 @classmethod
1498 @classmethod
1506 def is_valid(cls, repo_name):
1499 def is_valid(cls, repo_name):
1507 """
1500 """
1508 returns True if given repo name is a valid filesystem repository
1501 returns True if given repo name is a valid filesystem repository
1509
1502
1510 :param cls:
1503 :param cls:
1511 :param repo_name:
1504 :param repo_name:
1512 """
1505 """
1513 from rhodecode.lib.utils import is_valid_repo
1506 from rhodecode.lib.utils import is_valid_repo
1514
1507
1515 return is_valid_repo(repo_name, cls.base_path())
1508 return is_valid_repo(repo_name, cls.base_path())
1516
1509
1517 @classmethod
1510 @classmethod
1518 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1511 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1519 case_insensitive=True):
1512 case_insensitive=True):
1520 q = Repository.query()
1513 q = Repository.query()
1521
1514
1522 if not isinstance(user_id, Optional):
1515 if not isinstance(user_id, Optional):
1523 q = q.filter(Repository.user_id == user_id)
1516 q = q.filter(Repository.user_id == user_id)
1524
1517
1525 if not isinstance(group_id, Optional):
1518 if not isinstance(group_id, Optional):
1526 q = q.filter(Repository.group_id == group_id)
1519 q = q.filter(Repository.group_id == group_id)
1527
1520
1528 if case_insensitive:
1521 if case_insensitive:
1529 q = q.order_by(func.lower(Repository.repo_name))
1522 q = q.order_by(func.lower(Repository.repo_name))
1530 else:
1523 else:
1531 q = q.order_by(Repository.repo_name)
1524 q = q.order_by(Repository.repo_name)
1532 return q.all()
1525 return q.all()
1533
1526
1534 @property
1527 @property
1535 def forks(self):
1528 def forks(self):
1536 """
1529 """
1537 Return forks of this repo
1530 Return forks of this repo
1538 """
1531 """
1539 return Repository.get_repo_forks(self.repo_id)
1532 return Repository.get_repo_forks(self.repo_id)
1540
1533
1541 @property
1534 @property
1542 def parent(self):
1535 def parent(self):
1543 """
1536 """
1544 Returns fork parent
1537 Returns fork parent
1545 """
1538 """
1546 return self.fork
1539 return self.fork
1547
1540
1548 @property
1541 @property
1549 def just_name(self):
1542 def just_name(self):
1550 return self.repo_name.split(self.NAME_SEP)[-1]
1543 return self.repo_name.split(self.NAME_SEP)[-1]
1551
1544
1552 @property
1545 @property
1553 def groups_with_parents(self):
1546 def groups_with_parents(self):
1554 groups = []
1547 groups = []
1555 if self.group is None:
1548 if self.group is None:
1556 return groups
1549 return groups
1557
1550
1558 cur_gr = self.group
1551 cur_gr = self.group
1559 groups.insert(0, cur_gr)
1552 groups.insert(0, cur_gr)
1560 while 1:
1553 while 1:
1561 gr = getattr(cur_gr, 'parent_group', None)
1554 gr = getattr(cur_gr, 'parent_group', None)
1562 cur_gr = cur_gr.parent_group
1555 cur_gr = cur_gr.parent_group
1563 if gr is None:
1556 if gr is None:
1564 break
1557 break
1565 groups.insert(0, gr)
1558 groups.insert(0, gr)
1566
1559
1567 return groups
1560 return groups
1568
1561
1569 @property
1562 @property
1570 def groups_and_repo(self):
1563 def groups_and_repo(self):
1571 return self.groups_with_parents, self
1564 return self.groups_with_parents, self
1572
1565
1573 @LazyProperty
1566 @LazyProperty
1574 def repo_path(self):
1567 def repo_path(self):
1575 """
1568 """
1576 Returns base full path for that repository means where it actually
1569 Returns base full path for that repository means where it actually
1577 exists on a filesystem
1570 exists on a filesystem
1578 """
1571 """
1579 q = Session().query(RhodeCodeUi).filter(
1572 q = Session().query(RhodeCodeUi).filter(
1580 RhodeCodeUi.ui_key == self.NAME_SEP)
1573 RhodeCodeUi.ui_key == self.NAME_SEP)
1581 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1574 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1582 return q.one().ui_value
1575 return q.one().ui_value
1583
1576
1584 @property
1577 @property
1585 def repo_full_path(self):
1578 def repo_full_path(self):
1586 p = [self.repo_path]
1579 p = [self.repo_path]
1587 # we need to split the name by / since this is how we store the
1580 # we need to split the name by / since this is how we store the
1588 # names in the database, but that eventually needs to be converted
1581 # names in the database, but that eventually needs to be converted
1589 # into a valid system path
1582 # into a valid system path
1590 p += self.repo_name.split(self.NAME_SEP)
1583 p += self.repo_name.split(self.NAME_SEP)
1591 return os.path.join(*map(safe_unicode, p))
1584 return os.path.join(*map(safe_unicode, p))
1592
1585
1593 @property
1586 @property
1594 def cache_keys(self):
1587 def cache_keys(self):
1595 """
1588 """
1596 Returns associated cache keys for that repo
1589 Returns associated cache keys for that repo
1597 """
1590 """
1598 return CacheKey.query()\
1591 return CacheKey.query()\
1599 .filter(CacheKey.cache_args == self.repo_name)\
1592 .filter(CacheKey.cache_args == self.repo_name)\
1600 .order_by(CacheKey.cache_key)\
1593 .order_by(CacheKey.cache_key)\
1601 .all()
1594 .all()
1602
1595
1603 def get_new_name(self, repo_name):
1596 def get_new_name(self, repo_name):
1604 """
1597 """
1605 returns new full repository name based on assigned group and new new
1598 returns new full repository name based on assigned group and new new
1606
1599
1607 :param group_name:
1600 :param group_name:
1608 """
1601 """
1609 path_prefix = self.group.full_path_splitted if self.group else []
1602 path_prefix = self.group.full_path_splitted if self.group else []
1610 return self.NAME_SEP.join(path_prefix + [repo_name])
1603 return self.NAME_SEP.join(path_prefix + [repo_name])
1611
1604
1612 @property
1605 @property
1613 def _config(self):
1606 def _config(self):
1614 """
1607 """
1615 Returns db based config object.
1608 Returns db based config object.
1616 """
1609 """
1617 from rhodecode.lib.utils import make_db_config
1610 from rhodecode.lib.utils import make_db_config
1618 return make_db_config(clear_session=False, repo=self)
1611 return make_db_config(clear_session=False, repo=self)
1619
1612
1620 def permissions(self, with_admins=True, with_owner=True):
1613 def permissions(self, with_admins=True, with_owner=True):
1621 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1614 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1622 q = q.options(joinedload(UserRepoToPerm.repository),
1615 q = q.options(joinedload(UserRepoToPerm.repository),
1623 joinedload(UserRepoToPerm.user),
1616 joinedload(UserRepoToPerm.user),
1624 joinedload(UserRepoToPerm.permission),)
1617 joinedload(UserRepoToPerm.permission),)
1625
1618
1626 # get owners and admins and permissions. We do a trick of re-writing
1619 # get owners and admins and permissions. We do a trick of re-writing
1627 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1620 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1628 # has a global reference and changing one object propagates to all
1621 # has a global reference and changing one object propagates to all
1629 # others. This means if admin is also an owner admin_row that change
1622 # others. This means if admin is also an owner admin_row that change
1630 # would propagate to both objects
1623 # would propagate to both objects
1631 perm_rows = []
1624 perm_rows = []
1632 for _usr in q.all():
1625 for _usr in q.all():
1633 usr = AttributeDict(_usr.user.get_dict())
1626 usr = AttributeDict(_usr.user.get_dict())
1634 usr.permission = _usr.permission.permission_name
1627 usr.permission = _usr.permission.permission_name
1635 perm_rows.append(usr)
1628 perm_rows.append(usr)
1636
1629
1637 # filter the perm rows by 'default' first and then sort them by
1630 # filter the perm rows by 'default' first and then sort them by
1638 # admin,write,read,none permissions sorted again alphabetically in
1631 # admin,write,read,none permissions sorted again alphabetically in
1639 # each group
1632 # each group
1640 perm_rows = sorted(perm_rows, key=display_sort)
1633 perm_rows = sorted(perm_rows, key=display_sort)
1641
1634
1642 _admin_perm = 'repository.admin'
1635 _admin_perm = 'repository.admin'
1643 owner_row = []
1636 owner_row = []
1644 if with_owner:
1637 if with_owner:
1645 usr = AttributeDict(self.user.get_dict())
1638 usr = AttributeDict(self.user.get_dict())
1646 usr.owner_row = True
1639 usr.owner_row = True
1647 usr.permission = _admin_perm
1640 usr.permission = _admin_perm
1648 owner_row.append(usr)
1641 owner_row.append(usr)
1649
1642
1650 super_admin_rows = []
1643 super_admin_rows = []
1651 if with_admins:
1644 if with_admins:
1652 for usr in User.get_all_super_admins():
1645 for usr in User.get_all_super_admins():
1653 # if this admin is also owner, don't double the record
1646 # if this admin is also owner, don't double the record
1654 if usr.user_id == owner_row[0].user_id:
1647 if usr.user_id == owner_row[0].user_id:
1655 owner_row[0].admin_row = True
1648 owner_row[0].admin_row = True
1656 else:
1649 else:
1657 usr = AttributeDict(usr.get_dict())
1650 usr = AttributeDict(usr.get_dict())
1658 usr.admin_row = True
1651 usr.admin_row = True
1659 usr.permission = _admin_perm
1652 usr.permission = _admin_perm
1660 super_admin_rows.append(usr)
1653 super_admin_rows.append(usr)
1661
1654
1662 return super_admin_rows + owner_row + perm_rows
1655 return super_admin_rows + owner_row + perm_rows
1663
1656
1664 def permission_user_groups(self):
1657 def permission_user_groups(self):
1665 q = UserGroupRepoToPerm.query().filter(
1658 q = UserGroupRepoToPerm.query().filter(
1666 UserGroupRepoToPerm.repository == self)
1659 UserGroupRepoToPerm.repository == self)
1667 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1660 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1668 joinedload(UserGroupRepoToPerm.users_group),
1661 joinedload(UserGroupRepoToPerm.users_group),
1669 joinedload(UserGroupRepoToPerm.permission),)
1662 joinedload(UserGroupRepoToPerm.permission),)
1670
1663
1671 perm_rows = []
1664 perm_rows = []
1672 for _user_group in q.all():
1665 for _user_group in q.all():
1673 usr = AttributeDict(_user_group.users_group.get_dict())
1666 usr = AttributeDict(_user_group.users_group.get_dict())
1674 usr.permission = _user_group.permission.permission_name
1667 usr.permission = _user_group.permission.permission_name
1675 perm_rows.append(usr)
1668 perm_rows.append(usr)
1676
1669
1677 return perm_rows
1670 return perm_rows
1678
1671
1679 def get_api_data(self, include_secrets=False):
1672 def get_api_data(self, include_secrets=False):
1680 """
1673 """
1681 Common function for generating repo api data
1674 Common function for generating repo api data
1682
1675
1683 :param include_secrets: See :meth:`User.get_api_data`.
1676 :param include_secrets: See :meth:`User.get_api_data`.
1684
1677
1685 """
1678 """
1686 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1679 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1687 # move this methods on models level.
1680 # move this methods on models level.
1688 from rhodecode.model.settings import SettingsModel
1681 from rhodecode.model.settings import SettingsModel
1689
1682
1690 repo = self
1683 repo = self
1691 _user_id, _time, _reason = self.locked
1684 _user_id, _time, _reason = self.locked
1692
1685
1693 data = {
1686 data = {
1694 'repo_id': repo.repo_id,
1687 'repo_id': repo.repo_id,
1695 'repo_name': repo.repo_name,
1688 'repo_name': repo.repo_name,
1696 'repo_type': repo.repo_type,
1689 'repo_type': repo.repo_type,
1697 'clone_uri': repo.clone_uri or '',
1690 'clone_uri': repo.clone_uri or '',
1698 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1691 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1699 'private': repo.private,
1692 'private': repo.private,
1700 'created_on': repo.created_on,
1693 'created_on': repo.created_on,
1701 'description': repo.description,
1694 'description': repo.description,
1702 'landing_rev': repo.landing_rev,
1695 'landing_rev': repo.landing_rev,
1703 'owner': repo.user.username,
1696 'owner': repo.user.username,
1704 'fork_of': repo.fork.repo_name if repo.fork else None,
1697 'fork_of': repo.fork.repo_name if repo.fork else None,
1705 'enable_statistics': repo.enable_statistics,
1698 'enable_statistics': repo.enable_statistics,
1706 'enable_locking': repo.enable_locking,
1699 'enable_locking': repo.enable_locking,
1707 'enable_downloads': repo.enable_downloads,
1700 'enable_downloads': repo.enable_downloads,
1708 'last_changeset': repo.changeset_cache,
1701 'last_changeset': repo.changeset_cache,
1709 'locked_by': User.get(_user_id).get_api_data(
1702 'locked_by': User.get(_user_id).get_api_data(
1710 include_secrets=include_secrets) if _user_id else None,
1703 include_secrets=include_secrets) if _user_id else None,
1711 'locked_date': time_to_datetime(_time) if _time else None,
1704 'locked_date': time_to_datetime(_time) if _time else None,
1712 'lock_reason': _reason if _reason else None,
1705 'lock_reason': _reason if _reason else None,
1713 }
1706 }
1714
1707
1715 # TODO: mikhail: should be per-repo settings here
1708 # TODO: mikhail: should be per-repo settings here
1716 rc_config = SettingsModel().get_all_settings()
1709 rc_config = SettingsModel().get_all_settings()
1717 repository_fields = str2bool(
1710 repository_fields = str2bool(
1718 rc_config.get('rhodecode_repository_fields'))
1711 rc_config.get('rhodecode_repository_fields'))
1719 if repository_fields:
1712 if repository_fields:
1720 for f in self.extra_fields:
1713 for f in self.extra_fields:
1721 data[f.field_key_prefixed] = f.field_value
1714 data[f.field_key_prefixed] = f.field_value
1722
1715
1723 return data
1716 return data
1724
1717
1725 @classmethod
1718 @classmethod
1726 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1719 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1727 if not lock_time:
1720 if not lock_time:
1728 lock_time = time.time()
1721 lock_time = time.time()
1729 if not lock_reason:
1722 if not lock_reason:
1730 lock_reason = cls.LOCK_AUTOMATIC
1723 lock_reason = cls.LOCK_AUTOMATIC
1731 repo.locked = [user_id, lock_time, lock_reason]
1724 repo.locked = [user_id, lock_time, lock_reason]
1732 Session().add(repo)
1725 Session().add(repo)
1733 Session().commit()
1726 Session().commit()
1734
1727
1735 @classmethod
1728 @classmethod
1736 def unlock(cls, repo):
1729 def unlock(cls, repo):
1737 repo.locked = None
1730 repo.locked = None
1738 Session().add(repo)
1731 Session().add(repo)
1739 Session().commit()
1732 Session().commit()
1740
1733
1741 @classmethod
1734 @classmethod
1742 def getlock(cls, repo):
1735 def getlock(cls, repo):
1743 return repo.locked
1736 return repo.locked
1744
1737
1745 def is_user_lock(self, user_id):
1738 def is_user_lock(self, user_id):
1746 if self.lock[0]:
1739 if self.lock[0]:
1747 lock_user_id = safe_int(self.lock[0])
1740 lock_user_id = safe_int(self.lock[0])
1748 user_id = safe_int(user_id)
1741 user_id = safe_int(user_id)
1749 # both are ints, and they are equal
1742 # both are ints, and they are equal
1750 return all([lock_user_id, user_id]) and lock_user_id == user_id
1743 return all([lock_user_id, user_id]) and lock_user_id == user_id
1751
1744
1752 return False
1745 return False
1753
1746
1754 def get_locking_state(self, action, user_id, only_when_enabled=True):
1747 def get_locking_state(self, action, user_id, only_when_enabled=True):
1755 """
1748 """
1756 Checks locking on this repository, if locking is enabled and lock is
1749 Checks locking on this repository, if locking is enabled and lock is
1757 present returns a tuple of make_lock, locked, locked_by.
1750 present returns a tuple of make_lock, locked, locked_by.
1758 make_lock can have 3 states None (do nothing) True, make lock
1751 make_lock can have 3 states None (do nothing) True, make lock
1759 False release lock, This value is later propagated to hooks, which
1752 False release lock, This value is later propagated to hooks, which
1760 do the locking. Think about this as signals passed to hooks what to do.
1753 do the locking. Think about this as signals passed to hooks what to do.
1761
1754
1762 """
1755 """
1763 # TODO: johbo: This is part of the business logic and should be moved
1756 # TODO: johbo: This is part of the business logic and should be moved
1764 # into the RepositoryModel.
1757 # into the RepositoryModel.
1765
1758
1766 if action not in ('push', 'pull'):
1759 if action not in ('push', 'pull'):
1767 raise ValueError("Invalid action value: %s" % repr(action))
1760 raise ValueError("Invalid action value: %s" % repr(action))
1768
1761
1769 # defines if locked error should be thrown to user
1762 # defines if locked error should be thrown to user
1770 currently_locked = False
1763 currently_locked = False
1771 # defines if new lock should be made, tri-state
1764 # defines if new lock should be made, tri-state
1772 make_lock = None
1765 make_lock = None
1773 repo = self
1766 repo = self
1774 user = User.get(user_id)
1767 user = User.get(user_id)
1775
1768
1776 lock_info = repo.locked
1769 lock_info = repo.locked
1777
1770
1778 if repo and (repo.enable_locking or not only_when_enabled):
1771 if repo and (repo.enable_locking or not only_when_enabled):
1779 if action == 'push':
1772 if action == 'push':
1780 # check if it's already locked !, if it is compare users
1773 # check if it's already locked !, if it is compare users
1781 locked_by_user_id = lock_info[0]
1774 locked_by_user_id = lock_info[0]
1782 if user.user_id == locked_by_user_id:
1775 if user.user_id == locked_by_user_id:
1783 log.debug(
1776 log.debug(
1784 'Got `push` action from user %s, now unlocking', user)
1777 'Got `push` action from user %s, now unlocking', user)
1785 # unlock if we have push from user who locked
1778 # unlock if we have push from user who locked
1786 make_lock = False
1779 make_lock = False
1787 else:
1780 else:
1788 # we're not the same user who locked, ban with
1781 # we're not the same user who locked, ban with
1789 # code defined in settings (default is 423 HTTP Locked) !
1782 # code defined in settings (default is 423 HTTP Locked) !
1790 log.debug('Repo %s is currently locked by %s', repo, user)
1783 log.debug('Repo %s is currently locked by %s', repo, user)
1791 currently_locked = True
1784 currently_locked = True
1792 elif action == 'pull':
1785 elif action == 'pull':
1793 # [0] user [1] date
1786 # [0] user [1] date
1794 if lock_info[0] and lock_info[1]:
1787 if lock_info[0] and lock_info[1]:
1795 log.debug('Repo %s is currently locked by %s', repo, user)
1788 log.debug('Repo %s is currently locked by %s', repo, user)
1796 currently_locked = True
1789 currently_locked = True
1797 else:
1790 else:
1798 log.debug('Setting lock on repo %s by %s', repo, user)
1791 log.debug('Setting lock on repo %s by %s', repo, user)
1799 make_lock = True
1792 make_lock = True
1800
1793
1801 else:
1794 else:
1802 log.debug('Repository %s do not have locking enabled', repo)
1795 log.debug('Repository %s do not have locking enabled', repo)
1803
1796
1804 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1797 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1805 make_lock, currently_locked, lock_info)
1798 make_lock, currently_locked, lock_info)
1806
1799
1807 from rhodecode.lib.auth import HasRepoPermissionAny
1800 from rhodecode.lib.auth import HasRepoPermissionAny
1808 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1801 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1809 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1802 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1810 # if we don't have at least write permission we cannot make a lock
1803 # if we don't have at least write permission we cannot make a lock
1811 log.debug('lock state reset back to FALSE due to lack '
1804 log.debug('lock state reset back to FALSE due to lack '
1812 'of at least read permission')
1805 'of at least read permission')
1813 make_lock = False
1806 make_lock = False
1814
1807
1815 return make_lock, currently_locked, lock_info
1808 return make_lock, currently_locked, lock_info
1816
1809
1817 @property
1810 @property
1818 def last_db_change(self):
1811 def last_db_change(self):
1819 return self.updated_on
1812 return self.updated_on
1820
1813
1821 @property
1814 @property
1822 def clone_uri_hidden(self):
1815 def clone_uri_hidden(self):
1823 clone_uri = self.clone_uri
1816 clone_uri = self.clone_uri
1824 if clone_uri:
1817 if clone_uri:
1825 import urlobject
1818 import urlobject
1826 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1819 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1827 if url_obj.password:
1820 if url_obj.password:
1828 clone_uri = url_obj.with_password('*****')
1821 clone_uri = url_obj.with_password('*****')
1829 return clone_uri
1822 return clone_uri
1830
1823
1831 def clone_url(self, **override):
1824 def clone_url(self, **override):
1832 qualified_home_url = url('home', qualified=True)
1825 qualified_home_url = url('home', qualified=True)
1833
1826
1834 uri_tmpl = None
1827 uri_tmpl = None
1835 if 'with_id' in override:
1828 if 'with_id' in override:
1836 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1829 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1837 del override['with_id']
1830 del override['with_id']
1838
1831
1839 if 'uri_tmpl' in override:
1832 if 'uri_tmpl' in override:
1840 uri_tmpl = override['uri_tmpl']
1833 uri_tmpl = override['uri_tmpl']
1841 del override['uri_tmpl']
1834 del override['uri_tmpl']
1842
1835
1843 # we didn't override our tmpl from **overrides
1836 # we didn't override our tmpl from **overrides
1844 if not uri_tmpl:
1837 if not uri_tmpl:
1845 uri_tmpl = self.DEFAULT_CLONE_URI
1838 uri_tmpl = self.DEFAULT_CLONE_URI
1846 try:
1839 try:
1847 from pylons import tmpl_context as c
1840 from pylons import tmpl_context as c
1848 uri_tmpl = c.clone_uri_tmpl
1841 uri_tmpl = c.clone_uri_tmpl
1849 except Exception:
1842 except Exception:
1850 # in any case if we call this outside of request context,
1843 # in any case if we call this outside of request context,
1851 # ie, not having tmpl_context set up
1844 # ie, not having tmpl_context set up
1852 pass
1845 pass
1853
1846
1854 return get_clone_url(uri_tmpl=uri_tmpl,
1847 return get_clone_url(uri_tmpl=uri_tmpl,
1855 qualifed_home_url=qualified_home_url,
1848 qualifed_home_url=qualified_home_url,
1856 repo_name=self.repo_name,
1849 repo_name=self.repo_name,
1857 repo_id=self.repo_id, **override)
1850 repo_id=self.repo_id, **override)
1858
1851
1859 def set_state(self, state):
1852 def set_state(self, state):
1860 self.repo_state = state
1853 self.repo_state = state
1861 Session().add(self)
1854 Session().add(self)
1862 #==========================================================================
1855 #==========================================================================
1863 # SCM PROPERTIES
1856 # SCM PROPERTIES
1864 #==========================================================================
1857 #==========================================================================
1865
1858
1866 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1859 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1867 return get_commit_safe(
1860 return get_commit_safe(
1868 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1861 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1869
1862
1870 def get_changeset(self, rev=None, pre_load=None):
1863 def get_changeset(self, rev=None, pre_load=None):
1871 warnings.warn("Use get_commit", DeprecationWarning)
1864 warnings.warn("Use get_commit", DeprecationWarning)
1872 commit_id = None
1865 commit_id = None
1873 commit_idx = None
1866 commit_idx = None
1874 if isinstance(rev, basestring):
1867 if isinstance(rev, basestring):
1875 commit_id = rev
1868 commit_id = rev
1876 else:
1869 else:
1877 commit_idx = rev
1870 commit_idx = rev
1878 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1871 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1879 pre_load=pre_load)
1872 pre_load=pre_load)
1880
1873
1881 def get_landing_commit(self):
1874 def get_landing_commit(self):
1882 """
1875 """
1883 Returns landing commit, or if that doesn't exist returns the tip
1876 Returns landing commit, or if that doesn't exist returns the tip
1884 """
1877 """
1885 _rev_type, _rev = self.landing_rev
1878 _rev_type, _rev = self.landing_rev
1886 commit = self.get_commit(_rev)
1879 commit = self.get_commit(_rev)
1887 if isinstance(commit, EmptyCommit):
1880 if isinstance(commit, EmptyCommit):
1888 return self.get_commit()
1881 return self.get_commit()
1889 return commit
1882 return commit
1890
1883
1891 def update_commit_cache(self, cs_cache=None, config=None):
1884 def update_commit_cache(self, cs_cache=None, config=None):
1892 """
1885 """
1893 Update cache of last changeset for repository, keys should be::
1886 Update cache of last changeset for repository, keys should be::
1894
1887
1895 short_id
1888 short_id
1896 raw_id
1889 raw_id
1897 revision
1890 revision
1898 parents
1891 parents
1899 message
1892 message
1900 date
1893 date
1901 author
1894 author
1902
1895
1903 :param cs_cache:
1896 :param cs_cache:
1904 """
1897 """
1905 from rhodecode.lib.vcs.backends.base import BaseChangeset
1898 from rhodecode.lib.vcs.backends.base import BaseChangeset
1906 if cs_cache is None:
1899 if cs_cache is None:
1907 # use no-cache version here
1900 # use no-cache version here
1908 scm_repo = self.scm_instance(cache=False, config=config)
1901 scm_repo = self.scm_instance(cache=False, config=config)
1909 if scm_repo:
1902 if scm_repo:
1910 cs_cache = scm_repo.get_commit(
1903 cs_cache = scm_repo.get_commit(
1911 pre_load=["author", "date", "message", "parents"])
1904 pre_load=["author", "date", "message", "parents"])
1912 else:
1905 else:
1913 cs_cache = EmptyCommit()
1906 cs_cache = EmptyCommit()
1914
1907
1915 if isinstance(cs_cache, BaseChangeset):
1908 if isinstance(cs_cache, BaseChangeset):
1916 cs_cache = cs_cache.__json__()
1909 cs_cache = cs_cache.__json__()
1917
1910
1918 def is_outdated(new_cs_cache):
1911 def is_outdated(new_cs_cache):
1919 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1912 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1920 new_cs_cache['revision'] != self.changeset_cache['revision']):
1913 new_cs_cache['revision'] != self.changeset_cache['revision']):
1921 return True
1914 return True
1922 return False
1915 return False
1923
1916
1924 # check if we have maybe already latest cached revision
1917 # check if we have maybe already latest cached revision
1925 if is_outdated(cs_cache) or not self.changeset_cache:
1918 if is_outdated(cs_cache) or not self.changeset_cache:
1926 _default = datetime.datetime.fromtimestamp(0)
1919 _default = datetime.datetime.fromtimestamp(0)
1927 last_change = cs_cache.get('date') or _default
1920 last_change = cs_cache.get('date') or _default
1928 log.debug('updated repo %s with new cs cache %s',
1921 log.debug('updated repo %s with new cs cache %s',
1929 self.repo_name, cs_cache)
1922 self.repo_name, cs_cache)
1930 self.updated_on = last_change
1923 self.updated_on = last_change
1931 self.changeset_cache = cs_cache
1924 self.changeset_cache = cs_cache
1932 Session().add(self)
1925 Session().add(self)
1933 Session().commit()
1926 Session().commit()
1934 else:
1927 else:
1935 log.debug('Skipping update_commit_cache for repo:`%s` '
1928 log.debug('Skipping update_commit_cache for repo:`%s` '
1936 'commit already with latest changes', self.repo_name)
1929 'commit already with latest changes', self.repo_name)
1937
1930
1938 @property
1931 @property
1939 def tip(self):
1932 def tip(self):
1940 return self.get_commit('tip')
1933 return self.get_commit('tip')
1941
1934
1942 @property
1935 @property
1943 def author(self):
1936 def author(self):
1944 return self.tip.author
1937 return self.tip.author
1945
1938
1946 @property
1939 @property
1947 def last_change(self):
1940 def last_change(self):
1948 return self.scm_instance().last_change
1941 return self.scm_instance().last_change
1949
1942
1950 def get_comments(self, revisions=None):
1943 def get_comments(self, revisions=None):
1951 """
1944 """
1952 Returns comments for this repository grouped by revisions
1945 Returns comments for this repository grouped by revisions
1953
1946
1954 :param revisions: filter query by revisions only
1947 :param revisions: filter query by revisions only
1955 """
1948 """
1956 cmts = ChangesetComment.query()\
1949 cmts = ChangesetComment.query()\
1957 .filter(ChangesetComment.repo == self)
1950 .filter(ChangesetComment.repo == self)
1958 if revisions:
1951 if revisions:
1959 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1952 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1960 grouped = collections.defaultdict(list)
1953 grouped = collections.defaultdict(list)
1961 for cmt in cmts.all():
1954 for cmt in cmts.all():
1962 grouped[cmt.revision].append(cmt)
1955 grouped[cmt.revision].append(cmt)
1963 return grouped
1956 return grouped
1964
1957
1965 def statuses(self, revisions=None):
1958 def statuses(self, revisions=None):
1966 """
1959 """
1967 Returns statuses for this repository
1960 Returns statuses for this repository
1968
1961
1969 :param revisions: list of revisions to get statuses for
1962 :param revisions: list of revisions to get statuses for
1970 """
1963 """
1971 statuses = ChangesetStatus.query()\
1964 statuses = ChangesetStatus.query()\
1972 .filter(ChangesetStatus.repo == self)\
1965 .filter(ChangesetStatus.repo == self)\
1973 .filter(ChangesetStatus.version == 0)
1966 .filter(ChangesetStatus.version == 0)
1974
1967
1975 if revisions:
1968 if revisions:
1976 # Try doing the filtering in chunks to avoid hitting limits
1969 # Try doing the filtering in chunks to avoid hitting limits
1977 size = 500
1970 size = 500
1978 status_results = []
1971 status_results = []
1979 for chunk in xrange(0, len(revisions), size):
1972 for chunk in xrange(0, len(revisions), size):
1980 status_results += statuses.filter(
1973 status_results += statuses.filter(
1981 ChangesetStatus.revision.in_(
1974 ChangesetStatus.revision.in_(
1982 revisions[chunk: chunk+size])
1975 revisions[chunk: chunk+size])
1983 ).all()
1976 ).all()
1984 else:
1977 else:
1985 status_results = statuses.all()
1978 status_results = statuses.all()
1986
1979
1987 grouped = {}
1980 grouped = {}
1988
1981
1989 # maybe we have open new pullrequest without a status?
1982 # maybe we have open new pullrequest without a status?
1990 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1983 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1991 status_lbl = ChangesetStatus.get_status_lbl(stat)
1984 status_lbl = ChangesetStatus.get_status_lbl(stat)
1992 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1985 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1993 for rev in pr.revisions:
1986 for rev in pr.revisions:
1994 pr_id = pr.pull_request_id
1987 pr_id = pr.pull_request_id
1995 pr_repo = pr.target_repo.repo_name
1988 pr_repo = pr.target_repo.repo_name
1996 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1989 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1997
1990
1998 for stat in status_results:
1991 for stat in status_results:
1999 pr_id = pr_repo = None
1992 pr_id = pr_repo = None
2000 if stat.pull_request:
1993 if stat.pull_request:
2001 pr_id = stat.pull_request.pull_request_id
1994 pr_id = stat.pull_request.pull_request_id
2002 pr_repo = stat.pull_request.target_repo.repo_name
1995 pr_repo = stat.pull_request.target_repo.repo_name
2003 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1996 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2004 pr_id, pr_repo]
1997 pr_id, pr_repo]
2005 return grouped
1998 return grouped
2006
1999
2007 # ==========================================================================
2000 # ==========================================================================
2008 # SCM CACHE INSTANCE
2001 # SCM CACHE INSTANCE
2009 # ==========================================================================
2002 # ==========================================================================
2010
2003
2011 def scm_instance(self, **kwargs):
2004 def scm_instance(self, **kwargs):
2012 import rhodecode
2005 import rhodecode
2013
2006
2014 # Passing a config will not hit the cache currently only used
2007 # Passing a config will not hit the cache currently only used
2015 # for repo2dbmapper
2008 # for repo2dbmapper
2016 config = kwargs.pop('config', None)
2009 config = kwargs.pop('config', None)
2017 cache = kwargs.pop('cache', None)
2010 cache = kwargs.pop('cache', None)
2018 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2011 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2019 # if cache is NOT defined use default global, else we have a full
2012 # if cache is NOT defined use default global, else we have a full
2020 # control over cache behaviour
2013 # control over cache behaviour
2021 if cache is None and full_cache and not config:
2014 if cache is None and full_cache and not config:
2022 return self._get_instance_cached()
2015 return self._get_instance_cached()
2023 return self._get_instance(cache=bool(cache), config=config)
2016 return self._get_instance(cache=bool(cache), config=config)
2024
2017
2025 def _get_instance_cached(self):
2018 def _get_instance_cached(self):
2026 @cache_region('long_term')
2019 @cache_region('long_term')
2027 def _get_repo(cache_key):
2020 def _get_repo(cache_key):
2028 return self._get_instance()
2021 return self._get_instance()
2029
2022
2030 invalidator_context = CacheKey.repo_context_cache(
2023 invalidator_context = CacheKey.repo_context_cache(
2031 _get_repo, self.repo_name, None, thread_scoped=True)
2024 _get_repo, self.repo_name, None, thread_scoped=True)
2032
2025
2033 with invalidator_context as context:
2026 with invalidator_context as context:
2034 context.invalidate()
2027 context.invalidate()
2035 repo = context.compute()
2028 repo = context.compute()
2036
2029
2037 return repo
2030 return repo
2038
2031
2039 def _get_instance(self, cache=True, config=None):
2032 def _get_instance(self, cache=True, config=None):
2040 config = config or self._config
2033 config = config or self._config
2041 custom_wire = {
2034 custom_wire = {
2042 'cache': cache # controls the vcs.remote cache
2035 'cache': cache # controls the vcs.remote cache
2043 }
2036 }
2044 repo = get_vcs_instance(
2037 repo = get_vcs_instance(
2045 repo_path=safe_str(self.repo_full_path),
2038 repo_path=safe_str(self.repo_full_path),
2046 config=config,
2039 config=config,
2047 with_wire=custom_wire,
2040 with_wire=custom_wire,
2048 create=False,
2041 create=False,
2049 _vcs_alias=self.repo_type)
2042 _vcs_alias=self.repo_type)
2050
2043
2051 return repo
2044 return repo
2052
2045
2053 def __json__(self):
2046 def __json__(self):
2054 return {'landing_rev': self.landing_rev}
2047 return {'landing_rev': self.landing_rev}
2055
2048
2056 def get_dict(self):
2049 def get_dict(self):
2057
2050
2058 # Since we transformed `repo_name` to a hybrid property, we need to
2051 # Since we transformed `repo_name` to a hybrid property, we need to
2059 # keep compatibility with the code which uses `repo_name` field.
2052 # keep compatibility with the code which uses `repo_name` field.
2060
2053
2061 result = super(Repository, self).get_dict()
2054 result = super(Repository, self).get_dict()
2062 result['repo_name'] = result.pop('_repo_name', None)
2055 result['repo_name'] = result.pop('_repo_name', None)
2063 return result
2056 return result
2064
2057
2065
2058
2066 class RepoGroup(Base, BaseModel):
2059 class RepoGroup(Base, BaseModel):
2067 __tablename__ = 'groups'
2060 __tablename__ = 'groups'
2068 __table_args__ = (
2061 __table_args__ = (
2069 UniqueConstraint('group_name', 'group_parent_id'),
2062 UniqueConstraint('group_name', 'group_parent_id'),
2070 CheckConstraint('group_id != group_parent_id'),
2063 CheckConstraint('group_id != group_parent_id'),
2071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2064 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2072 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2065 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2073 )
2066 )
2074 __mapper_args__ = {'order_by': 'group_name'}
2067 __mapper_args__ = {'order_by': 'group_name'}
2075
2068
2076 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2069 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2077
2070
2078 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2071 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2079 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2072 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2080 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2073 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2081 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2074 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2082 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2075 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2083 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2084 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2077 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2085 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2078 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2086
2079
2087 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2080 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2088 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2081 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2089 parent_group = relationship('RepoGroup', remote_side=group_id)
2082 parent_group = relationship('RepoGroup', remote_side=group_id)
2090 user = relationship('User')
2083 user = relationship('User')
2091 integrations = relationship('Integration',
2084 integrations = relationship('Integration',
2092 cascade="all, delete, delete-orphan")
2085 cascade="all, delete, delete-orphan")
2093
2086
2094 def __init__(self, group_name='', parent_group=None):
2087 def __init__(self, group_name='', parent_group=None):
2095 self.group_name = group_name
2088 self.group_name = group_name
2096 self.parent_group = parent_group
2089 self.parent_group = parent_group
2097
2090
2098 def __unicode__(self):
2091 def __unicode__(self):
2099 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2092 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2100 self.group_name)
2093 self.group_name)
2101
2094
2102 @classmethod
2095 @classmethod
2103 def _generate_choice(cls, repo_group):
2096 def _generate_choice(cls, repo_group):
2104 from webhelpers.html import literal as _literal
2097 from webhelpers.html import literal as _literal
2105 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2098 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2106 return repo_group.group_id, _name(repo_group.full_path_splitted)
2099 return repo_group.group_id, _name(repo_group.full_path_splitted)
2107
2100
2108 @classmethod
2101 @classmethod
2109 def groups_choices(cls, groups=None, show_empty_group=True):
2102 def groups_choices(cls, groups=None, show_empty_group=True):
2110 if not groups:
2103 if not groups:
2111 groups = cls.query().all()
2104 groups = cls.query().all()
2112
2105
2113 repo_groups = []
2106 repo_groups = []
2114 if show_empty_group:
2107 if show_empty_group:
2115 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2108 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2116
2109
2117 repo_groups.extend([cls._generate_choice(x) for x in groups])
2110 repo_groups.extend([cls._generate_choice(x) for x in groups])
2118
2111
2119 repo_groups = sorted(
2112 repo_groups = sorted(
2120 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2113 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2121 return repo_groups
2114 return repo_groups
2122
2115
2123 @classmethod
2116 @classmethod
2124 def url_sep(cls):
2117 def url_sep(cls):
2125 return URL_SEP
2118 return URL_SEP
2126
2119
2127 @classmethod
2120 @classmethod
2128 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2121 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2129 if case_insensitive:
2122 if case_insensitive:
2130 gr = cls.query().filter(func.lower(cls.group_name)
2123 gr = cls.query().filter(func.lower(cls.group_name)
2131 == func.lower(group_name))
2124 == func.lower(group_name))
2132 else:
2125 else:
2133 gr = cls.query().filter(cls.group_name == group_name)
2126 gr = cls.query().filter(cls.group_name == group_name)
2134 if cache:
2127 if cache:
2135 gr = gr.options(FromCache(
2128 gr = gr.options(FromCache(
2136 "sql_cache_short",
2129 "sql_cache_short",
2137 "get_group_%s" % _hash_key(group_name)))
2130 "get_group_%s" % _hash_key(group_name)))
2138 return gr.scalar()
2131 return gr.scalar()
2139
2132
2140 @classmethod
2133 @classmethod
2141 def get_user_personal_repo_group(cls, user_id):
2134 def get_user_personal_repo_group(cls, user_id):
2142 user = User.get(user_id)
2135 user = User.get(user_id)
2143 return cls.query()\
2136 return cls.query()\
2144 .filter(cls.personal == true())\
2137 .filter(cls.personal == true())\
2145 .filter(cls.user == user).scalar()
2138 .filter(cls.user == user).scalar()
2146
2139
2147 @classmethod
2140 @classmethod
2148 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2141 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2149 case_insensitive=True):
2142 case_insensitive=True):
2150 q = RepoGroup.query()
2143 q = RepoGroup.query()
2151
2144
2152 if not isinstance(user_id, Optional):
2145 if not isinstance(user_id, Optional):
2153 q = q.filter(RepoGroup.user_id == user_id)
2146 q = q.filter(RepoGroup.user_id == user_id)
2154
2147
2155 if not isinstance(group_id, Optional):
2148 if not isinstance(group_id, Optional):
2156 q = q.filter(RepoGroup.group_parent_id == group_id)
2149 q = q.filter(RepoGroup.group_parent_id == group_id)
2157
2150
2158 if case_insensitive:
2151 if case_insensitive:
2159 q = q.order_by(func.lower(RepoGroup.group_name))
2152 q = q.order_by(func.lower(RepoGroup.group_name))
2160 else:
2153 else:
2161 q = q.order_by(RepoGroup.group_name)
2154 q = q.order_by(RepoGroup.group_name)
2162 return q.all()
2155 return q.all()
2163
2156
2164 @property
2157 @property
2165 def parents(self):
2158 def parents(self):
2166 parents_recursion_limit = 10
2159 parents_recursion_limit = 10
2167 groups = []
2160 groups = []
2168 if self.parent_group is None:
2161 if self.parent_group is None:
2169 return groups
2162 return groups
2170 cur_gr = self.parent_group
2163 cur_gr = self.parent_group
2171 groups.insert(0, cur_gr)
2164 groups.insert(0, cur_gr)
2172 cnt = 0
2165 cnt = 0
2173 while 1:
2166 while 1:
2174 cnt += 1
2167 cnt += 1
2175 gr = getattr(cur_gr, 'parent_group', None)
2168 gr = getattr(cur_gr, 'parent_group', None)
2176 cur_gr = cur_gr.parent_group
2169 cur_gr = cur_gr.parent_group
2177 if gr is None:
2170 if gr is None:
2178 break
2171 break
2179 if cnt == parents_recursion_limit:
2172 if cnt == parents_recursion_limit:
2180 # this will prevent accidental infinit loops
2173 # this will prevent accidental infinit loops
2181 log.error(('more than %s parents found for group %s, stopping '
2174 log.error(('more than %s parents found for group %s, stopping '
2182 'recursive parent fetching' % (parents_recursion_limit, self)))
2175 'recursive parent fetching' % (parents_recursion_limit, self)))
2183 break
2176 break
2184
2177
2185 groups.insert(0, gr)
2178 groups.insert(0, gr)
2186 return groups
2179 return groups
2187
2180
2188 @property
2181 @property
2189 def children(self):
2182 def children(self):
2190 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2183 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2191
2184
2192 @property
2185 @property
2193 def name(self):
2186 def name(self):
2194 return self.group_name.split(RepoGroup.url_sep())[-1]
2187 return self.group_name.split(RepoGroup.url_sep())[-1]
2195
2188
2196 @property
2189 @property
2197 def full_path(self):
2190 def full_path(self):
2198 return self.group_name
2191 return self.group_name
2199
2192
2200 @property
2193 @property
2201 def full_path_splitted(self):
2194 def full_path_splitted(self):
2202 return self.group_name.split(RepoGroup.url_sep())
2195 return self.group_name.split(RepoGroup.url_sep())
2203
2196
2204 @property
2197 @property
2205 def repositories(self):
2198 def repositories(self):
2206 return Repository.query()\
2199 return Repository.query()\
2207 .filter(Repository.group == self)\
2200 .filter(Repository.group == self)\
2208 .order_by(Repository.repo_name)
2201 .order_by(Repository.repo_name)
2209
2202
2210 @property
2203 @property
2211 def repositories_recursive_count(self):
2204 def repositories_recursive_count(self):
2212 cnt = self.repositories.count()
2205 cnt = self.repositories.count()
2213
2206
2214 def children_count(group):
2207 def children_count(group):
2215 cnt = 0
2208 cnt = 0
2216 for child in group.children:
2209 for child in group.children:
2217 cnt += child.repositories.count()
2210 cnt += child.repositories.count()
2218 cnt += children_count(child)
2211 cnt += children_count(child)
2219 return cnt
2212 return cnt
2220
2213
2221 return cnt + children_count(self)
2214 return cnt + children_count(self)
2222
2215
2223 def _recursive_objects(self, include_repos=True):
2216 def _recursive_objects(self, include_repos=True):
2224 all_ = []
2217 all_ = []
2225
2218
2226 def _get_members(root_gr):
2219 def _get_members(root_gr):
2227 if include_repos:
2220 if include_repos:
2228 for r in root_gr.repositories:
2221 for r in root_gr.repositories:
2229 all_.append(r)
2222 all_.append(r)
2230 childs = root_gr.children.all()
2223 childs = root_gr.children.all()
2231 if childs:
2224 if childs:
2232 for gr in childs:
2225 for gr in childs:
2233 all_.append(gr)
2226 all_.append(gr)
2234 _get_members(gr)
2227 _get_members(gr)
2235
2228
2236 _get_members(self)
2229 _get_members(self)
2237 return [self] + all_
2230 return [self] + all_
2238
2231
2239 def recursive_groups_and_repos(self):
2232 def recursive_groups_and_repos(self):
2240 """
2233 """
2241 Recursive return all groups, with repositories in those groups
2234 Recursive return all groups, with repositories in those groups
2242 """
2235 """
2243 return self._recursive_objects()
2236 return self._recursive_objects()
2244
2237
2245 def recursive_groups(self):
2238 def recursive_groups(self):
2246 """
2239 """
2247 Returns all children groups for this group including children of children
2240 Returns all children groups for this group including children of children
2248 """
2241 """
2249 return self._recursive_objects(include_repos=False)
2242 return self._recursive_objects(include_repos=False)
2250
2243
2251 def get_new_name(self, group_name):
2244 def get_new_name(self, group_name):
2252 """
2245 """
2253 returns new full group name based on parent and new name
2246 returns new full group name based on parent and new name
2254
2247
2255 :param group_name:
2248 :param group_name:
2256 """
2249 """
2257 path_prefix = (self.parent_group.full_path_splitted if
2250 path_prefix = (self.parent_group.full_path_splitted if
2258 self.parent_group else [])
2251 self.parent_group else [])
2259 return RepoGroup.url_sep().join(path_prefix + [group_name])
2252 return RepoGroup.url_sep().join(path_prefix + [group_name])
2260
2253
2261 def permissions(self, with_admins=True, with_owner=True):
2254 def permissions(self, with_admins=True, with_owner=True):
2262 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2255 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2263 q = q.options(joinedload(UserRepoGroupToPerm.group),
2256 q = q.options(joinedload(UserRepoGroupToPerm.group),
2264 joinedload(UserRepoGroupToPerm.user),
2257 joinedload(UserRepoGroupToPerm.user),
2265 joinedload(UserRepoGroupToPerm.permission),)
2258 joinedload(UserRepoGroupToPerm.permission),)
2266
2259
2267 # get owners and admins and permissions. We do a trick of re-writing
2260 # get owners and admins and permissions. We do a trick of re-writing
2268 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2261 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2269 # has a global reference and changing one object propagates to all
2262 # has a global reference and changing one object propagates to all
2270 # others. This means if admin is also an owner admin_row that change
2263 # others. This means if admin is also an owner admin_row that change
2271 # would propagate to both objects
2264 # would propagate to both objects
2272 perm_rows = []
2265 perm_rows = []
2273 for _usr in q.all():
2266 for _usr in q.all():
2274 usr = AttributeDict(_usr.user.get_dict())
2267 usr = AttributeDict(_usr.user.get_dict())
2275 usr.permission = _usr.permission.permission_name
2268 usr.permission = _usr.permission.permission_name
2276 perm_rows.append(usr)
2269 perm_rows.append(usr)
2277
2270
2278 # filter the perm rows by 'default' first and then sort them by
2271 # filter the perm rows by 'default' first and then sort them by
2279 # admin,write,read,none permissions sorted again alphabetically in
2272 # admin,write,read,none permissions sorted again alphabetically in
2280 # each group
2273 # each group
2281 perm_rows = sorted(perm_rows, key=display_sort)
2274 perm_rows = sorted(perm_rows, key=display_sort)
2282
2275
2283 _admin_perm = 'group.admin'
2276 _admin_perm = 'group.admin'
2284 owner_row = []
2277 owner_row = []
2285 if with_owner:
2278 if with_owner:
2286 usr = AttributeDict(self.user.get_dict())
2279 usr = AttributeDict(self.user.get_dict())
2287 usr.owner_row = True
2280 usr.owner_row = True
2288 usr.permission = _admin_perm
2281 usr.permission = _admin_perm
2289 owner_row.append(usr)
2282 owner_row.append(usr)
2290
2283
2291 super_admin_rows = []
2284 super_admin_rows = []
2292 if with_admins:
2285 if with_admins:
2293 for usr in User.get_all_super_admins():
2286 for usr in User.get_all_super_admins():
2294 # if this admin is also owner, don't double the record
2287 # if this admin is also owner, don't double the record
2295 if usr.user_id == owner_row[0].user_id:
2288 if usr.user_id == owner_row[0].user_id:
2296 owner_row[0].admin_row = True
2289 owner_row[0].admin_row = True
2297 else:
2290 else:
2298 usr = AttributeDict(usr.get_dict())
2291 usr = AttributeDict(usr.get_dict())
2299 usr.admin_row = True
2292 usr.admin_row = True
2300 usr.permission = _admin_perm
2293 usr.permission = _admin_perm
2301 super_admin_rows.append(usr)
2294 super_admin_rows.append(usr)
2302
2295
2303 return super_admin_rows + owner_row + perm_rows
2296 return super_admin_rows + owner_row + perm_rows
2304
2297
2305 def permission_user_groups(self):
2298 def permission_user_groups(self):
2306 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2299 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2307 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2300 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2308 joinedload(UserGroupRepoGroupToPerm.users_group),
2301 joinedload(UserGroupRepoGroupToPerm.users_group),
2309 joinedload(UserGroupRepoGroupToPerm.permission),)
2302 joinedload(UserGroupRepoGroupToPerm.permission),)
2310
2303
2311 perm_rows = []
2304 perm_rows = []
2312 for _user_group in q.all():
2305 for _user_group in q.all():
2313 usr = AttributeDict(_user_group.users_group.get_dict())
2306 usr = AttributeDict(_user_group.users_group.get_dict())
2314 usr.permission = _user_group.permission.permission_name
2307 usr.permission = _user_group.permission.permission_name
2315 perm_rows.append(usr)
2308 perm_rows.append(usr)
2316
2309
2317 return perm_rows
2310 return perm_rows
2318
2311
2319 def get_api_data(self):
2312 def get_api_data(self):
2320 """
2313 """
2321 Common function for generating api data
2314 Common function for generating api data
2322
2315
2323 """
2316 """
2324 group = self
2317 group = self
2325 data = {
2318 data = {
2326 'group_id': group.group_id,
2319 'group_id': group.group_id,
2327 'group_name': group.group_name,
2320 'group_name': group.group_name,
2328 'group_description': group.group_description,
2321 'group_description': group.group_description,
2329 'parent_group': group.parent_group.group_name if group.parent_group else None,
2322 'parent_group': group.parent_group.group_name if group.parent_group else None,
2330 'repositories': [x.repo_name for x in group.repositories],
2323 'repositories': [x.repo_name for x in group.repositories],
2331 'owner': group.user.username,
2324 'owner': group.user.username,
2332 }
2325 }
2333 return data
2326 return data
2334
2327
2335
2328
2336 class Permission(Base, BaseModel):
2329 class Permission(Base, BaseModel):
2337 __tablename__ = 'permissions'
2330 __tablename__ = 'permissions'
2338 __table_args__ = (
2331 __table_args__ = (
2339 Index('p_perm_name_idx', 'permission_name'),
2332 Index('p_perm_name_idx', 'permission_name'),
2340 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2333 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2341 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2334 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2342 )
2335 )
2343 PERMS = [
2336 PERMS = [
2344 ('hg.admin', _('RhodeCode Super Administrator')),
2337 ('hg.admin', _('RhodeCode Super Administrator')),
2345
2338
2346 ('repository.none', _('Repository no access')),
2339 ('repository.none', _('Repository no access')),
2347 ('repository.read', _('Repository read access')),
2340 ('repository.read', _('Repository read access')),
2348 ('repository.write', _('Repository write access')),
2341 ('repository.write', _('Repository write access')),
2349 ('repository.admin', _('Repository admin access')),
2342 ('repository.admin', _('Repository admin access')),
2350
2343
2351 ('group.none', _('Repository group no access')),
2344 ('group.none', _('Repository group no access')),
2352 ('group.read', _('Repository group read access')),
2345 ('group.read', _('Repository group read access')),
2353 ('group.write', _('Repository group write access')),
2346 ('group.write', _('Repository group write access')),
2354 ('group.admin', _('Repository group admin access')),
2347 ('group.admin', _('Repository group admin access')),
2355
2348
2356 ('usergroup.none', _('User group no access')),
2349 ('usergroup.none', _('User group no access')),
2357 ('usergroup.read', _('User group read access')),
2350 ('usergroup.read', _('User group read access')),
2358 ('usergroup.write', _('User group write access')),
2351 ('usergroup.write', _('User group write access')),
2359 ('usergroup.admin', _('User group admin access')),
2352 ('usergroup.admin', _('User group admin access')),
2360
2353
2361 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2354 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2362 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2355 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2363
2356
2364 ('hg.usergroup.create.false', _('User Group creation disabled')),
2357 ('hg.usergroup.create.false', _('User Group creation disabled')),
2365 ('hg.usergroup.create.true', _('User Group creation enabled')),
2358 ('hg.usergroup.create.true', _('User Group creation enabled')),
2366
2359
2367 ('hg.create.none', _('Repository creation disabled')),
2360 ('hg.create.none', _('Repository creation disabled')),
2368 ('hg.create.repository', _('Repository creation enabled')),
2361 ('hg.create.repository', _('Repository creation enabled')),
2369 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2362 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2370 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2363 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2371
2364
2372 ('hg.fork.none', _('Repository forking disabled')),
2365 ('hg.fork.none', _('Repository forking disabled')),
2373 ('hg.fork.repository', _('Repository forking enabled')),
2366 ('hg.fork.repository', _('Repository forking enabled')),
2374
2367
2375 ('hg.register.none', _('Registration disabled')),
2368 ('hg.register.none', _('Registration disabled')),
2376 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2369 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2377 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2370 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2378
2371
2379 ('hg.password_reset.enabled', _('Password reset enabled')),
2372 ('hg.password_reset.enabled', _('Password reset enabled')),
2380 ('hg.password_reset.hidden', _('Password reset hidden')),
2373 ('hg.password_reset.hidden', _('Password reset hidden')),
2381 ('hg.password_reset.disabled', _('Password reset disabled')),
2374 ('hg.password_reset.disabled', _('Password reset disabled')),
2382
2375
2383 ('hg.extern_activate.manual', _('Manual activation of external account')),
2376 ('hg.extern_activate.manual', _('Manual activation of external account')),
2384 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2377 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2385
2378
2386 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2379 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2387 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2380 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2388 ]
2381 ]
2389
2382
2390 # definition of system default permissions for DEFAULT user
2383 # definition of system default permissions for DEFAULT user
2391 DEFAULT_USER_PERMISSIONS = [
2384 DEFAULT_USER_PERMISSIONS = [
2392 'repository.read',
2385 'repository.read',
2393 'group.read',
2386 'group.read',
2394 'usergroup.read',
2387 'usergroup.read',
2395 'hg.create.repository',
2388 'hg.create.repository',
2396 'hg.repogroup.create.false',
2389 'hg.repogroup.create.false',
2397 'hg.usergroup.create.false',
2390 'hg.usergroup.create.false',
2398 'hg.create.write_on_repogroup.true',
2391 'hg.create.write_on_repogroup.true',
2399 'hg.fork.repository',
2392 'hg.fork.repository',
2400 'hg.register.manual_activate',
2393 'hg.register.manual_activate',
2401 'hg.password_reset.enabled',
2394 'hg.password_reset.enabled',
2402 'hg.extern_activate.auto',
2395 'hg.extern_activate.auto',
2403 'hg.inherit_default_perms.true',
2396 'hg.inherit_default_perms.true',
2404 ]
2397 ]
2405
2398
2406 # defines which permissions are more important higher the more important
2399 # defines which permissions are more important higher the more important
2407 # Weight defines which permissions are more important.
2400 # Weight defines which permissions are more important.
2408 # The higher number the more important.
2401 # The higher number the more important.
2409 PERM_WEIGHTS = {
2402 PERM_WEIGHTS = {
2410 'repository.none': 0,
2403 'repository.none': 0,
2411 'repository.read': 1,
2404 'repository.read': 1,
2412 'repository.write': 3,
2405 'repository.write': 3,
2413 'repository.admin': 4,
2406 'repository.admin': 4,
2414
2407
2415 'group.none': 0,
2408 'group.none': 0,
2416 'group.read': 1,
2409 'group.read': 1,
2417 'group.write': 3,
2410 'group.write': 3,
2418 'group.admin': 4,
2411 'group.admin': 4,
2419
2412
2420 'usergroup.none': 0,
2413 'usergroup.none': 0,
2421 'usergroup.read': 1,
2414 'usergroup.read': 1,
2422 'usergroup.write': 3,
2415 'usergroup.write': 3,
2423 'usergroup.admin': 4,
2416 'usergroup.admin': 4,
2424
2417
2425 'hg.repogroup.create.false': 0,
2418 'hg.repogroup.create.false': 0,
2426 'hg.repogroup.create.true': 1,
2419 'hg.repogroup.create.true': 1,
2427
2420
2428 'hg.usergroup.create.false': 0,
2421 'hg.usergroup.create.false': 0,
2429 'hg.usergroup.create.true': 1,
2422 'hg.usergroup.create.true': 1,
2430
2423
2431 'hg.fork.none': 0,
2424 'hg.fork.none': 0,
2432 'hg.fork.repository': 1,
2425 'hg.fork.repository': 1,
2433 'hg.create.none': 0,
2426 'hg.create.none': 0,
2434 'hg.create.repository': 1
2427 'hg.create.repository': 1
2435 }
2428 }
2436
2429
2437 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2430 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2438 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2431 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2439 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2432 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2440
2433
2441 def __unicode__(self):
2434 def __unicode__(self):
2442 return u"<%s('%s:%s')>" % (
2435 return u"<%s('%s:%s')>" % (
2443 self.__class__.__name__, self.permission_id, self.permission_name
2436 self.__class__.__name__, self.permission_id, self.permission_name
2444 )
2437 )
2445
2438
2446 @classmethod
2439 @classmethod
2447 def get_by_key(cls, key):
2440 def get_by_key(cls, key):
2448 return cls.query().filter(cls.permission_name == key).scalar()
2441 return cls.query().filter(cls.permission_name == key).scalar()
2449
2442
2450 @classmethod
2443 @classmethod
2451 def get_default_repo_perms(cls, user_id, repo_id=None):
2444 def get_default_repo_perms(cls, user_id, repo_id=None):
2452 q = Session().query(UserRepoToPerm, Repository, Permission)\
2445 q = Session().query(UserRepoToPerm, Repository, Permission)\
2453 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2446 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2454 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2447 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2455 .filter(UserRepoToPerm.user_id == user_id)
2448 .filter(UserRepoToPerm.user_id == user_id)
2456 if repo_id:
2449 if repo_id:
2457 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2450 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2458 return q.all()
2451 return q.all()
2459
2452
2460 @classmethod
2453 @classmethod
2461 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2454 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2462 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2455 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2463 .join(
2456 .join(
2464 Permission,
2457 Permission,
2465 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2458 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2466 .join(
2459 .join(
2467 Repository,
2460 Repository,
2468 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2461 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2469 .join(
2462 .join(
2470 UserGroup,
2463 UserGroup,
2471 UserGroupRepoToPerm.users_group_id ==
2464 UserGroupRepoToPerm.users_group_id ==
2472 UserGroup.users_group_id)\
2465 UserGroup.users_group_id)\
2473 .join(
2466 .join(
2474 UserGroupMember,
2467 UserGroupMember,
2475 UserGroupRepoToPerm.users_group_id ==
2468 UserGroupRepoToPerm.users_group_id ==
2476 UserGroupMember.users_group_id)\
2469 UserGroupMember.users_group_id)\
2477 .filter(
2470 .filter(
2478 UserGroupMember.user_id == user_id,
2471 UserGroupMember.user_id == user_id,
2479 UserGroup.users_group_active == true())
2472 UserGroup.users_group_active == true())
2480 if repo_id:
2473 if repo_id:
2481 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2474 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2482 return q.all()
2475 return q.all()
2483
2476
2484 @classmethod
2477 @classmethod
2485 def get_default_group_perms(cls, user_id, repo_group_id=None):
2478 def get_default_group_perms(cls, user_id, repo_group_id=None):
2486 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2479 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2487 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2480 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2488 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2481 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2489 .filter(UserRepoGroupToPerm.user_id == user_id)
2482 .filter(UserRepoGroupToPerm.user_id == user_id)
2490 if repo_group_id:
2483 if repo_group_id:
2491 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2484 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2492 return q.all()
2485 return q.all()
2493
2486
2494 @classmethod
2487 @classmethod
2495 def get_default_group_perms_from_user_group(
2488 def get_default_group_perms_from_user_group(
2496 cls, user_id, repo_group_id=None):
2489 cls, user_id, repo_group_id=None):
2497 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2490 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2498 .join(
2491 .join(
2499 Permission,
2492 Permission,
2500 UserGroupRepoGroupToPerm.permission_id ==
2493 UserGroupRepoGroupToPerm.permission_id ==
2501 Permission.permission_id)\
2494 Permission.permission_id)\
2502 .join(
2495 .join(
2503 RepoGroup,
2496 RepoGroup,
2504 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2497 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2505 .join(
2498 .join(
2506 UserGroup,
2499 UserGroup,
2507 UserGroupRepoGroupToPerm.users_group_id ==
2500 UserGroupRepoGroupToPerm.users_group_id ==
2508 UserGroup.users_group_id)\
2501 UserGroup.users_group_id)\
2509 .join(
2502 .join(
2510 UserGroupMember,
2503 UserGroupMember,
2511 UserGroupRepoGroupToPerm.users_group_id ==
2504 UserGroupRepoGroupToPerm.users_group_id ==
2512 UserGroupMember.users_group_id)\
2505 UserGroupMember.users_group_id)\
2513 .filter(
2506 .filter(
2514 UserGroupMember.user_id == user_id,
2507 UserGroupMember.user_id == user_id,
2515 UserGroup.users_group_active == true())
2508 UserGroup.users_group_active == true())
2516 if repo_group_id:
2509 if repo_group_id:
2517 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2510 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2518 return q.all()
2511 return q.all()
2519
2512
2520 @classmethod
2513 @classmethod
2521 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2514 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2522 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2515 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2523 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2516 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2524 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2517 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2525 .filter(UserUserGroupToPerm.user_id == user_id)
2518 .filter(UserUserGroupToPerm.user_id == user_id)
2526 if user_group_id:
2519 if user_group_id:
2527 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2520 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2528 return q.all()
2521 return q.all()
2529
2522
2530 @classmethod
2523 @classmethod
2531 def get_default_user_group_perms_from_user_group(
2524 def get_default_user_group_perms_from_user_group(
2532 cls, user_id, user_group_id=None):
2525 cls, user_id, user_group_id=None):
2533 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2526 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2534 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2527 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2535 .join(
2528 .join(
2536 Permission,
2529 Permission,
2537 UserGroupUserGroupToPerm.permission_id ==
2530 UserGroupUserGroupToPerm.permission_id ==
2538 Permission.permission_id)\
2531 Permission.permission_id)\
2539 .join(
2532 .join(
2540 TargetUserGroup,
2533 TargetUserGroup,
2541 UserGroupUserGroupToPerm.target_user_group_id ==
2534 UserGroupUserGroupToPerm.target_user_group_id ==
2542 TargetUserGroup.users_group_id)\
2535 TargetUserGroup.users_group_id)\
2543 .join(
2536 .join(
2544 UserGroup,
2537 UserGroup,
2545 UserGroupUserGroupToPerm.user_group_id ==
2538 UserGroupUserGroupToPerm.user_group_id ==
2546 UserGroup.users_group_id)\
2539 UserGroup.users_group_id)\
2547 .join(
2540 .join(
2548 UserGroupMember,
2541 UserGroupMember,
2549 UserGroupUserGroupToPerm.user_group_id ==
2542 UserGroupUserGroupToPerm.user_group_id ==
2550 UserGroupMember.users_group_id)\
2543 UserGroupMember.users_group_id)\
2551 .filter(
2544 .filter(
2552 UserGroupMember.user_id == user_id,
2545 UserGroupMember.user_id == user_id,
2553 UserGroup.users_group_active == true())
2546 UserGroup.users_group_active == true())
2554 if user_group_id:
2547 if user_group_id:
2555 q = q.filter(
2548 q = q.filter(
2556 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2549 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2557
2550
2558 return q.all()
2551 return q.all()
2559
2552
2560
2553
2561 class UserRepoToPerm(Base, BaseModel):
2554 class UserRepoToPerm(Base, BaseModel):
2562 __tablename__ = 'repo_to_perm'
2555 __tablename__ = 'repo_to_perm'
2563 __table_args__ = (
2556 __table_args__ = (
2564 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2557 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2559 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2567 )
2560 )
2568 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2561 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2562 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2570 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2563 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2571 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2564 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2572
2565
2573 user = relationship('User')
2566 user = relationship('User')
2574 repository = relationship('Repository')
2567 repository = relationship('Repository')
2575 permission = relationship('Permission')
2568 permission = relationship('Permission')
2576
2569
2577 @classmethod
2570 @classmethod
2578 def create(cls, user, repository, permission):
2571 def create(cls, user, repository, permission):
2579 n = cls()
2572 n = cls()
2580 n.user = user
2573 n.user = user
2581 n.repository = repository
2574 n.repository = repository
2582 n.permission = permission
2575 n.permission = permission
2583 Session().add(n)
2576 Session().add(n)
2584 return n
2577 return n
2585
2578
2586 def __unicode__(self):
2579 def __unicode__(self):
2587 return u'<%s => %s >' % (self.user, self.repository)
2580 return u'<%s => %s >' % (self.user, self.repository)
2588
2581
2589
2582
2590 class UserUserGroupToPerm(Base, BaseModel):
2583 class UserUserGroupToPerm(Base, BaseModel):
2591 __tablename__ = 'user_user_group_to_perm'
2584 __tablename__ = 'user_user_group_to_perm'
2592 __table_args__ = (
2585 __table_args__ = (
2593 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2586 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2595 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2588 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2596 )
2589 )
2597 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2590 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2598 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2599 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2592 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2600 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2593 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2601
2594
2602 user = relationship('User')
2595 user = relationship('User')
2603 user_group = relationship('UserGroup')
2596 user_group = relationship('UserGroup')
2604 permission = relationship('Permission')
2597 permission = relationship('Permission')
2605
2598
2606 @classmethod
2599 @classmethod
2607 def create(cls, user, user_group, permission):
2600 def create(cls, user, user_group, permission):
2608 n = cls()
2601 n = cls()
2609 n.user = user
2602 n.user = user
2610 n.user_group = user_group
2603 n.user_group = user_group
2611 n.permission = permission
2604 n.permission = permission
2612 Session().add(n)
2605 Session().add(n)
2613 return n
2606 return n
2614
2607
2615 def __unicode__(self):
2608 def __unicode__(self):
2616 return u'<%s => %s >' % (self.user, self.user_group)
2609 return u'<%s => %s >' % (self.user, self.user_group)
2617
2610
2618
2611
2619 class UserToPerm(Base, BaseModel):
2612 class UserToPerm(Base, BaseModel):
2620 __tablename__ = 'user_to_perm'
2613 __tablename__ = 'user_to_perm'
2621 __table_args__ = (
2614 __table_args__ = (
2622 UniqueConstraint('user_id', 'permission_id'),
2615 UniqueConstraint('user_id', 'permission_id'),
2623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2616 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2617 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2625 )
2618 )
2626 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2619 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2627 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2620 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2628 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2621 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2629
2622
2630 user = relationship('User')
2623 user = relationship('User')
2631 permission = relationship('Permission', lazy='joined')
2624 permission = relationship('Permission', lazy='joined')
2632
2625
2633 def __unicode__(self):
2626 def __unicode__(self):
2634 return u'<%s => %s >' % (self.user, self.permission)
2627 return u'<%s => %s >' % (self.user, self.permission)
2635
2628
2636
2629
2637 class UserGroupRepoToPerm(Base, BaseModel):
2630 class UserGroupRepoToPerm(Base, BaseModel):
2638 __tablename__ = 'users_group_repo_to_perm'
2631 __tablename__ = 'users_group_repo_to_perm'
2639 __table_args__ = (
2632 __table_args__ = (
2640 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2633 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2634 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2635 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 )
2636 )
2644 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2637 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2638 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2646 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2639 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2647 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2640 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2648
2641
2649 users_group = relationship('UserGroup')
2642 users_group = relationship('UserGroup')
2650 permission = relationship('Permission')
2643 permission = relationship('Permission')
2651 repository = relationship('Repository')
2644 repository = relationship('Repository')
2652
2645
2653 @classmethod
2646 @classmethod
2654 def create(cls, users_group, repository, permission):
2647 def create(cls, users_group, repository, permission):
2655 n = cls()
2648 n = cls()
2656 n.users_group = users_group
2649 n.users_group = users_group
2657 n.repository = repository
2650 n.repository = repository
2658 n.permission = permission
2651 n.permission = permission
2659 Session().add(n)
2652 Session().add(n)
2660 return n
2653 return n
2661
2654
2662 def __unicode__(self):
2655 def __unicode__(self):
2663 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2656 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2664
2657
2665
2658
2666 class UserGroupUserGroupToPerm(Base, BaseModel):
2659 class UserGroupUserGroupToPerm(Base, BaseModel):
2667 __tablename__ = 'user_group_user_group_to_perm'
2660 __tablename__ = 'user_group_user_group_to_perm'
2668 __table_args__ = (
2661 __table_args__ = (
2669 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2662 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2670 CheckConstraint('target_user_group_id != user_group_id'),
2663 CheckConstraint('target_user_group_id != user_group_id'),
2671 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2672 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2665 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2673 )
2666 )
2674 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2667 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2675 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2668 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2676 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2669 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2677 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2670 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2678
2671
2679 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2672 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2680 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2673 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2681 permission = relationship('Permission')
2674 permission = relationship('Permission')
2682
2675
2683 @classmethod
2676 @classmethod
2684 def create(cls, target_user_group, user_group, permission):
2677 def create(cls, target_user_group, user_group, permission):
2685 n = cls()
2678 n = cls()
2686 n.target_user_group = target_user_group
2679 n.target_user_group = target_user_group
2687 n.user_group = user_group
2680 n.user_group = user_group
2688 n.permission = permission
2681 n.permission = permission
2689 Session().add(n)
2682 Session().add(n)
2690 return n
2683 return n
2691
2684
2692 def __unicode__(self):
2685 def __unicode__(self):
2693 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2686 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2694
2687
2695
2688
2696 class UserGroupToPerm(Base, BaseModel):
2689 class UserGroupToPerm(Base, BaseModel):
2697 __tablename__ = 'users_group_to_perm'
2690 __tablename__ = 'users_group_to_perm'
2698 __table_args__ = (
2691 __table_args__ = (
2699 UniqueConstraint('users_group_id', 'permission_id',),
2692 UniqueConstraint('users_group_id', 'permission_id',),
2700 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2701 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2694 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2702 )
2695 )
2703 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2696 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2704 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2697 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2705 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2698 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2706
2699
2707 users_group = relationship('UserGroup')
2700 users_group = relationship('UserGroup')
2708 permission = relationship('Permission')
2701 permission = relationship('Permission')
2709
2702
2710
2703
2711 class UserRepoGroupToPerm(Base, BaseModel):
2704 class UserRepoGroupToPerm(Base, BaseModel):
2712 __tablename__ = 'user_repo_group_to_perm'
2705 __tablename__ = 'user_repo_group_to_perm'
2713 __table_args__ = (
2706 __table_args__ = (
2714 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2707 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2708 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2716 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2709 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2717 )
2710 )
2718
2711
2719 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2712 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2720 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2713 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2721 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2714 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2722 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2715 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2723
2716
2724 user = relationship('User')
2717 user = relationship('User')
2725 group = relationship('RepoGroup')
2718 group = relationship('RepoGroup')
2726 permission = relationship('Permission')
2719 permission = relationship('Permission')
2727
2720
2728 @classmethod
2721 @classmethod
2729 def create(cls, user, repository_group, permission):
2722 def create(cls, user, repository_group, permission):
2730 n = cls()
2723 n = cls()
2731 n.user = user
2724 n.user = user
2732 n.group = repository_group
2725 n.group = repository_group
2733 n.permission = permission
2726 n.permission = permission
2734 Session().add(n)
2727 Session().add(n)
2735 return n
2728 return n
2736
2729
2737
2730
2738 class UserGroupRepoGroupToPerm(Base, BaseModel):
2731 class UserGroupRepoGroupToPerm(Base, BaseModel):
2739 __tablename__ = 'users_group_repo_group_to_perm'
2732 __tablename__ = 'users_group_repo_group_to_perm'
2740 __table_args__ = (
2733 __table_args__ = (
2741 UniqueConstraint('users_group_id', 'group_id'),
2734 UniqueConstraint('users_group_id', 'group_id'),
2742 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2735 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2743 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2736 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2744 )
2737 )
2745
2738
2746 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)
2739 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)
2747 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2740 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2748 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2741 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2749 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2742 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2750
2743
2751 users_group = relationship('UserGroup')
2744 users_group = relationship('UserGroup')
2752 permission = relationship('Permission')
2745 permission = relationship('Permission')
2753 group = relationship('RepoGroup')
2746 group = relationship('RepoGroup')
2754
2747
2755 @classmethod
2748 @classmethod
2756 def create(cls, user_group, repository_group, permission):
2749 def create(cls, user_group, repository_group, permission):
2757 n = cls()
2750 n = cls()
2758 n.users_group = user_group
2751 n.users_group = user_group
2759 n.group = repository_group
2752 n.group = repository_group
2760 n.permission = permission
2753 n.permission = permission
2761 Session().add(n)
2754 Session().add(n)
2762 return n
2755 return n
2763
2756
2764 def __unicode__(self):
2757 def __unicode__(self):
2765 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2758 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2766
2759
2767
2760
2768 class Statistics(Base, BaseModel):
2761 class Statistics(Base, BaseModel):
2769 __tablename__ = 'statistics'
2762 __tablename__ = 'statistics'
2770 __table_args__ = (
2763 __table_args__ = (
2771 UniqueConstraint('repository_id'),
2764 UniqueConstraint('repository_id'),
2772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2765 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2766 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2774 )
2767 )
2775 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2768 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2776 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2769 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2777 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2770 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2778 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2771 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2779 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2772 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2780 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2773 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2781
2774
2782 repository = relationship('Repository', single_parent=True)
2775 repository = relationship('Repository', single_parent=True)
2783
2776
2784
2777
2785 class UserFollowing(Base, BaseModel):
2778 class UserFollowing(Base, BaseModel):
2786 __tablename__ = 'user_followings'
2779 __tablename__ = 'user_followings'
2787 __table_args__ = (
2780 __table_args__ = (
2788 UniqueConstraint('user_id', 'follows_repository_id'),
2781 UniqueConstraint('user_id', 'follows_repository_id'),
2789 UniqueConstraint('user_id', 'follows_user_id'),
2782 UniqueConstraint('user_id', 'follows_user_id'),
2790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2784 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2792 )
2785 )
2793
2786
2794 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2787 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2788 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2796 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2789 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2797 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2790 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2798 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2791 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2799
2792
2800 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2793 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2801
2794
2802 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2795 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2803 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2796 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2804
2797
2805 @classmethod
2798 @classmethod
2806 def get_repo_followers(cls, repo_id):
2799 def get_repo_followers(cls, repo_id):
2807 return cls.query().filter(cls.follows_repo_id == repo_id)
2800 return cls.query().filter(cls.follows_repo_id == repo_id)
2808
2801
2809
2802
2810 class CacheKey(Base, BaseModel):
2803 class CacheKey(Base, BaseModel):
2811 __tablename__ = 'cache_invalidation'
2804 __tablename__ = 'cache_invalidation'
2812 __table_args__ = (
2805 __table_args__ = (
2813 UniqueConstraint('cache_key'),
2806 UniqueConstraint('cache_key'),
2814 Index('key_idx', 'cache_key'),
2807 Index('key_idx', 'cache_key'),
2815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2809 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2817 )
2810 )
2818 CACHE_TYPE_ATOM = 'ATOM'
2811 CACHE_TYPE_ATOM = 'ATOM'
2819 CACHE_TYPE_RSS = 'RSS'
2812 CACHE_TYPE_RSS = 'RSS'
2820 CACHE_TYPE_README = 'README'
2813 CACHE_TYPE_README = 'README'
2821
2814
2822 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2815 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2823 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2816 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2824 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2817 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2825 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2818 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2826
2819
2827 def __init__(self, cache_key, cache_args=''):
2820 def __init__(self, cache_key, cache_args=''):
2828 self.cache_key = cache_key
2821 self.cache_key = cache_key
2829 self.cache_args = cache_args
2822 self.cache_args = cache_args
2830 self.cache_active = False
2823 self.cache_active = False
2831
2824
2832 def __unicode__(self):
2825 def __unicode__(self):
2833 return u"<%s('%s:%s[%s]')>" % (
2826 return u"<%s('%s:%s[%s]')>" % (
2834 self.__class__.__name__,
2827 self.__class__.__name__,
2835 self.cache_id, self.cache_key, self.cache_active)
2828 self.cache_id, self.cache_key, self.cache_active)
2836
2829
2837 def _cache_key_partition(self):
2830 def _cache_key_partition(self):
2838 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2831 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2839 return prefix, repo_name, suffix
2832 return prefix, repo_name, suffix
2840
2833
2841 def get_prefix(self):
2834 def get_prefix(self):
2842 """
2835 """
2843 Try to extract prefix from existing cache key. The key could consist
2836 Try to extract prefix from existing cache key. The key could consist
2844 of prefix, repo_name, suffix
2837 of prefix, repo_name, suffix
2845 """
2838 """
2846 # this returns prefix, repo_name, suffix
2839 # this returns prefix, repo_name, suffix
2847 return self._cache_key_partition()[0]
2840 return self._cache_key_partition()[0]
2848
2841
2849 def get_suffix(self):
2842 def get_suffix(self):
2850 """
2843 """
2851 get suffix that might have been used in _get_cache_key to
2844 get suffix that might have been used in _get_cache_key to
2852 generate self.cache_key. Only used for informational purposes
2845 generate self.cache_key. Only used for informational purposes
2853 in repo_edit.mako.
2846 in repo_edit.mako.
2854 """
2847 """
2855 # prefix, repo_name, suffix
2848 # prefix, repo_name, suffix
2856 return self._cache_key_partition()[2]
2849 return self._cache_key_partition()[2]
2857
2850
2858 @classmethod
2851 @classmethod
2859 def delete_all_cache(cls):
2852 def delete_all_cache(cls):
2860 """
2853 """
2861 Delete all cache keys from database.
2854 Delete all cache keys from database.
2862 Should only be run when all instances are down and all entries
2855 Should only be run when all instances are down and all entries
2863 thus stale.
2856 thus stale.
2864 """
2857 """
2865 cls.query().delete()
2858 cls.query().delete()
2866 Session().commit()
2859 Session().commit()
2867
2860
2868 @classmethod
2861 @classmethod
2869 def get_cache_key(cls, repo_name, cache_type):
2862 def get_cache_key(cls, repo_name, cache_type):
2870 """
2863 """
2871
2864
2872 Generate a cache key for this process of RhodeCode instance.
2865 Generate a cache key for this process of RhodeCode instance.
2873 Prefix most likely will be process id or maybe explicitly set
2866 Prefix most likely will be process id or maybe explicitly set
2874 instance_id from .ini file.
2867 instance_id from .ini file.
2875 """
2868 """
2876 import rhodecode
2869 import rhodecode
2877 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2870 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2878
2871
2879 repo_as_unicode = safe_unicode(repo_name)
2872 repo_as_unicode = safe_unicode(repo_name)
2880 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2873 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2881 if cache_type else repo_as_unicode
2874 if cache_type else repo_as_unicode
2882
2875
2883 return u'{}{}'.format(prefix, key)
2876 return u'{}{}'.format(prefix, key)
2884
2877
2885 @classmethod
2878 @classmethod
2886 def set_invalidate(cls, repo_name, delete=False):
2879 def set_invalidate(cls, repo_name, delete=False):
2887 """
2880 """
2888 Mark all caches of a repo as invalid in the database.
2881 Mark all caches of a repo as invalid in the database.
2889 """
2882 """
2890
2883
2891 try:
2884 try:
2892 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2885 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2893 if delete:
2886 if delete:
2894 log.debug('cache objects deleted for repo %s',
2887 log.debug('cache objects deleted for repo %s',
2895 safe_str(repo_name))
2888 safe_str(repo_name))
2896 qry.delete()
2889 qry.delete()
2897 else:
2890 else:
2898 log.debug('cache objects marked as invalid for repo %s',
2891 log.debug('cache objects marked as invalid for repo %s',
2899 safe_str(repo_name))
2892 safe_str(repo_name))
2900 qry.update({"cache_active": False})
2893 qry.update({"cache_active": False})
2901
2894
2902 Session().commit()
2895 Session().commit()
2903 except Exception:
2896 except Exception:
2904 log.exception(
2897 log.exception(
2905 'Cache key invalidation failed for repository %s',
2898 'Cache key invalidation failed for repository %s',
2906 safe_str(repo_name))
2899 safe_str(repo_name))
2907 Session().rollback()
2900 Session().rollback()
2908
2901
2909 @classmethod
2902 @classmethod
2910 def get_active_cache(cls, cache_key):
2903 def get_active_cache(cls, cache_key):
2911 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2904 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2912 if inv_obj:
2905 if inv_obj:
2913 return inv_obj
2906 return inv_obj
2914 return None
2907 return None
2915
2908
2916 @classmethod
2909 @classmethod
2917 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2910 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2918 thread_scoped=False):
2911 thread_scoped=False):
2919 """
2912 """
2920 @cache_region('long_term')
2913 @cache_region('long_term')
2921 def _heavy_calculation(cache_key):
2914 def _heavy_calculation(cache_key):
2922 return 'result'
2915 return 'result'
2923
2916
2924 cache_context = CacheKey.repo_context_cache(
2917 cache_context = CacheKey.repo_context_cache(
2925 _heavy_calculation, repo_name, cache_type)
2918 _heavy_calculation, repo_name, cache_type)
2926
2919
2927 with cache_context as context:
2920 with cache_context as context:
2928 context.invalidate()
2921 context.invalidate()
2929 computed = context.compute()
2922 computed = context.compute()
2930
2923
2931 assert computed == 'result'
2924 assert computed == 'result'
2932 """
2925 """
2933 from rhodecode.lib import caches
2926 from rhodecode.lib import caches
2934 return caches.InvalidationContext(
2927 return caches.InvalidationContext(
2935 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2928 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2936
2929
2937
2930
2938 class ChangesetComment(Base, BaseModel):
2931 class ChangesetComment(Base, BaseModel):
2939 __tablename__ = 'changeset_comments'
2932 __tablename__ = 'changeset_comments'
2940 __table_args__ = (
2933 __table_args__ = (
2941 Index('cc_revision_idx', 'revision'),
2934 Index('cc_revision_idx', 'revision'),
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2944 )
2937 )
2945
2938
2946 COMMENT_OUTDATED = u'comment_outdated'
2939 COMMENT_OUTDATED = u'comment_outdated'
2947 COMMENT_TYPE_NOTE = u'note'
2940 COMMENT_TYPE_NOTE = u'note'
2948 COMMENT_TYPE_TODO = u'todo'
2941 COMMENT_TYPE_TODO = u'todo'
2949 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2942 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2950
2943
2951 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2944 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2952 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2945 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2953 revision = Column('revision', String(40), nullable=True)
2946 revision = Column('revision', String(40), nullable=True)
2954 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2947 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2955 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2948 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2956 line_no = Column('line_no', Unicode(10), nullable=True)
2949 line_no = Column('line_no', Unicode(10), nullable=True)
2957 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2950 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2958 f_path = Column('f_path', Unicode(1000), nullable=True)
2951 f_path = Column('f_path', Unicode(1000), nullable=True)
2959 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2952 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2960 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2953 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2961 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2954 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2955 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2963 renderer = Column('renderer', Unicode(64), nullable=True)
2956 renderer = Column('renderer', Unicode(64), nullable=True)
2964 display_state = Column('display_state', Unicode(128), nullable=True)
2957 display_state = Column('display_state', Unicode(128), nullable=True)
2965
2958
2966 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2959 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2967 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2960 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2968 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2961 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2969 author = relationship('User', lazy='joined')
2962 author = relationship('User', lazy='joined')
2970 repo = relationship('Repository')
2963 repo = relationship('Repository')
2971 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2964 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2972 pull_request = relationship('PullRequest', lazy='joined')
2965 pull_request = relationship('PullRequest', lazy='joined')
2973 pull_request_version = relationship('PullRequestVersion')
2966 pull_request_version = relationship('PullRequestVersion')
2974
2967
2975 @classmethod
2968 @classmethod
2976 def get_users(cls, revision=None, pull_request_id=None):
2969 def get_users(cls, revision=None, pull_request_id=None):
2977 """
2970 """
2978 Returns user associated with this ChangesetComment. ie those
2971 Returns user associated with this ChangesetComment. ie those
2979 who actually commented
2972 who actually commented
2980
2973
2981 :param cls:
2974 :param cls:
2982 :param revision:
2975 :param revision:
2983 """
2976 """
2984 q = Session().query(User)\
2977 q = Session().query(User)\
2985 .join(ChangesetComment.author)
2978 .join(ChangesetComment.author)
2986 if revision:
2979 if revision:
2987 q = q.filter(cls.revision == revision)
2980 q = q.filter(cls.revision == revision)
2988 elif pull_request_id:
2981 elif pull_request_id:
2989 q = q.filter(cls.pull_request_id == pull_request_id)
2982 q = q.filter(cls.pull_request_id == pull_request_id)
2990 return q.all()
2983 return q.all()
2991
2984
2992 @classmethod
2985 @classmethod
2993 def get_index_from_version(cls, pr_version, versions):
2986 def get_index_from_version(cls, pr_version, versions):
2994 num_versions = [x.pull_request_version_id for x in versions]
2987 num_versions = [x.pull_request_version_id for x in versions]
2995 try:
2988 try:
2996 return num_versions.index(pr_version) +1
2989 return num_versions.index(pr_version) +1
2997 except (IndexError, ValueError):
2990 except (IndexError, ValueError):
2998 return
2991 return
2999
2992
3000 @property
2993 @property
3001 def outdated(self):
2994 def outdated(self):
3002 return self.display_state == self.COMMENT_OUTDATED
2995 return self.display_state == self.COMMENT_OUTDATED
3003
2996
3004 def outdated_at_version(self, version):
2997 def outdated_at_version(self, version):
3005 """
2998 """
3006 Checks if comment is outdated for given pull request version
2999 Checks if comment is outdated for given pull request version
3007 """
3000 """
3008 return self.outdated and self.pull_request_version_id != version
3001 return self.outdated and self.pull_request_version_id != version
3009
3002
3010 def older_than_version(self, version):
3003 def older_than_version(self, version):
3011 """
3004 """
3012 Checks if comment is made from previous version than given
3005 Checks if comment is made from previous version than given
3013 """
3006 """
3014 if version is None:
3007 if version is None:
3015 return self.pull_request_version_id is not None
3008 return self.pull_request_version_id is not None
3016
3009
3017 return self.pull_request_version_id < version
3010 return self.pull_request_version_id < version
3018
3011
3019 @property
3012 @property
3020 def resolved(self):
3013 def resolved(self):
3021 return self.resolved_by[0] if self.resolved_by else None
3014 return self.resolved_by[0] if self.resolved_by else None
3022
3015
3023 @property
3016 @property
3024 def is_todo(self):
3017 def is_todo(self):
3025 return self.comment_type == self.COMMENT_TYPE_TODO
3018 return self.comment_type == self.COMMENT_TYPE_TODO
3026
3019
3027 def get_index_version(self, versions):
3020 def get_index_version(self, versions):
3028 return self.get_index_from_version(
3021 return self.get_index_from_version(
3029 self.pull_request_version_id, versions)
3022 self.pull_request_version_id, versions)
3030
3023
3031 def render(self, mentions=False):
3024 def render(self, mentions=False):
3032 from rhodecode.lib import helpers as h
3025 from rhodecode.lib import helpers as h
3033 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3026 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3034
3027
3035 def __repr__(self):
3028 def __repr__(self):
3036 if self.comment_id:
3029 if self.comment_id:
3037 return '<DB:Comment #%s>' % self.comment_id
3030 return '<DB:Comment #%s>' % self.comment_id
3038 else:
3031 else:
3039 return '<DB:Comment at %#x>' % id(self)
3032 return '<DB:Comment at %#x>' % id(self)
3040
3033
3041
3034
3042 class ChangesetStatus(Base, BaseModel):
3035 class ChangesetStatus(Base, BaseModel):
3043 __tablename__ = 'changeset_statuses'
3036 __tablename__ = 'changeset_statuses'
3044 __table_args__ = (
3037 __table_args__ = (
3045 Index('cs_revision_idx', 'revision'),
3038 Index('cs_revision_idx', 'revision'),
3046 Index('cs_version_idx', 'version'),
3039 Index('cs_version_idx', 'version'),
3047 UniqueConstraint('repo_id', 'revision', 'version'),
3040 UniqueConstraint('repo_id', 'revision', 'version'),
3048 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3049 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3042 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3050 )
3043 )
3051 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3044 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3052 STATUS_APPROVED = 'approved'
3045 STATUS_APPROVED = 'approved'
3053 STATUS_REJECTED = 'rejected'
3046 STATUS_REJECTED = 'rejected'
3054 STATUS_UNDER_REVIEW = 'under_review'
3047 STATUS_UNDER_REVIEW = 'under_review'
3055
3048
3056 STATUSES = [
3049 STATUSES = [
3057 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3050 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3058 (STATUS_APPROVED, _("Approved")),
3051 (STATUS_APPROVED, _("Approved")),
3059 (STATUS_REJECTED, _("Rejected")),
3052 (STATUS_REJECTED, _("Rejected")),
3060 (STATUS_UNDER_REVIEW, _("Under Review")),
3053 (STATUS_UNDER_REVIEW, _("Under Review")),
3061 ]
3054 ]
3062
3055
3063 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3056 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3064 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3057 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3065 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3058 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3066 revision = Column('revision', String(40), nullable=False)
3059 revision = Column('revision', String(40), nullable=False)
3067 status = Column('status', String(128), nullable=False, default=DEFAULT)
3060 status = Column('status', String(128), nullable=False, default=DEFAULT)
3068 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3061 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3069 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3062 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3070 version = Column('version', Integer(), nullable=False, default=0)
3063 version = Column('version', Integer(), nullable=False, default=0)
3071 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3064 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3072
3065
3073 author = relationship('User', lazy='joined')
3066 author = relationship('User', lazy='joined')
3074 repo = relationship('Repository')
3067 repo = relationship('Repository')
3075 comment = relationship('ChangesetComment', lazy='joined')
3068 comment = relationship('ChangesetComment', lazy='joined')
3076 pull_request = relationship('PullRequest', lazy='joined')
3069 pull_request = relationship('PullRequest', lazy='joined')
3077
3070
3078 def __unicode__(self):
3071 def __unicode__(self):
3079 return u"<%s('%s[v%s]:%s')>" % (
3072 return u"<%s('%s[v%s]:%s')>" % (
3080 self.__class__.__name__,
3073 self.__class__.__name__,
3081 self.status, self.version, self.author
3074 self.status, self.version, self.author
3082 )
3075 )
3083
3076
3084 @classmethod
3077 @classmethod
3085 def get_status_lbl(cls, value):
3078 def get_status_lbl(cls, value):
3086 return dict(cls.STATUSES).get(value)
3079 return dict(cls.STATUSES).get(value)
3087
3080
3088 @property
3081 @property
3089 def status_lbl(self):
3082 def status_lbl(self):
3090 return ChangesetStatus.get_status_lbl(self.status)
3083 return ChangesetStatus.get_status_lbl(self.status)
3091
3084
3092
3085
3093 class _PullRequestBase(BaseModel):
3086 class _PullRequestBase(BaseModel):
3094 """
3087 """
3095 Common attributes of pull request and version entries.
3088 Common attributes of pull request and version entries.
3096 """
3089 """
3097
3090
3098 # .status values
3091 # .status values
3099 STATUS_NEW = u'new'
3092 STATUS_NEW = u'new'
3100 STATUS_OPEN = u'open'
3093 STATUS_OPEN = u'open'
3101 STATUS_CLOSED = u'closed'
3094 STATUS_CLOSED = u'closed'
3102
3095
3103 title = Column('title', Unicode(255), nullable=True)
3096 title = Column('title', Unicode(255), nullable=True)
3104 description = Column(
3097 description = Column(
3105 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3098 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3106 nullable=True)
3099 nullable=True)
3107 # new/open/closed status of pull request (not approve/reject/etc)
3100 # new/open/closed status of pull request (not approve/reject/etc)
3108 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3101 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3109 created_on = Column(
3102 created_on = Column(
3110 'created_on', DateTime(timezone=False), nullable=False,
3103 'created_on', DateTime(timezone=False), nullable=False,
3111 default=datetime.datetime.now)
3104 default=datetime.datetime.now)
3112 updated_on = Column(
3105 updated_on = Column(
3113 'updated_on', DateTime(timezone=False), nullable=False,
3106 'updated_on', DateTime(timezone=False), nullable=False,
3114 default=datetime.datetime.now)
3107 default=datetime.datetime.now)
3115
3108
3116 @declared_attr
3109 @declared_attr
3117 def user_id(cls):
3110 def user_id(cls):
3118 return Column(
3111 return Column(
3119 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3112 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3120 unique=None)
3113 unique=None)
3121
3114
3122 # 500 revisions max
3115 # 500 revisions max
3123 _revisions = Column(
3116 _revisions = Column(
3124 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3117 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3125
3118
3126 @declared_attr
3119 @declared_attr
3127 def source_repo_id(cls):
3120 def source_repo_id(cls):
3128 # TODO: dan: rename column to source_repo_id
3121 # TODO: dan: rename column to source_repo_id
3129 return Column(
3122 return Column(
3130 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3123 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3131 nullable=False)
3124 nullable=False)
3132
3125
3133 source_ref = Column('org_ref', Unicode(255), nullable=False)
3126 source_ref = Column('org_ref', Unicode(255), nullable=False)
3134
3127
3135 @declared_attr
3128 @declared_attr
3136 def target_repo_id(cls):
3129 def target_repo_id(cls):
3137 # TODO: dan: rename column to target_repo_id
3130 # TODO: dan: rename column to target_repo_id
3138 return Column(
3131 return Column(
3139 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3132 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3140 nullable=False)
3133 nullable=False)
3141
3134
3142 target_ref = Column('other_ref', Unicode(255), nullable=False)
3135 target_ref = Column('other_ref', Unicode(255), nullable=False)
3143 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3136 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3144
3137
3145 # TODO: dan: rename column to last_merge_source_rev
3138 # TODO: dan: rename column to last_merge_source_rev
3146 _last_merge_source_rev = Column(
3139 _last_merge_source_rev = Column(
3147 'last_merge_org_rev', String(40), nullable=True)
3140 'last_merge_org_rev', String(40), nullable=True)
3148 # TODO: dan: rename column to last_merge_target_rev
3141 # TODO: dan: rename column to last_merge_target_rev
3149 _last_merge_target_rev = Column(
3142 _last_merge_target_rev = Column(
3150 'last_merge_other_rev', String(40), nullable=True)
3143 'last_merge_other_rev', String(40), nullable=True)
3151 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3144 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3152 merge_rev = Column('merge_rev', String(40), nullable=True)
3145 merge_rev = Column('merge_rev', String(40), nullable=True)
3153
3146
3154 @hybrid_property
3147 @hybrid_property
3155 def revisions(self):
3148 def revisions(self):
3156 return self._revisions.split(':') if self._revisions else []
3149 return self._revisions.split(':') if self._revisions else []
3157
3150
3158 @revisions.setter
3151 @revisions.setter
3159 def revisions(self, val):
3152 def revisions(self, val):
3160 self._revisions = ':'.join(val)
3153 self._revisions = ':'.join(val)
3161
3154
3162 @declared_attr
3155 @declared_attr
3163 def author(cls):
3156 def author(cls):
3164 return relationship('User', lazy='joined')
3157 return relationship('User', lazy='joined')
3165
3158
3166 @declared_attr
3159 @declared_attr
3167 def source_repo(cls):
3160 def source_repo(cls):
3168 return relationship(
3161 return relationship(
3169 'Repository',
3162 'Repository',
3170 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3163 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3171
3164
3172 @property
3165 @property
3173 def source_ref_parts(self):
3166 def source_ref_parts(self):
3174 return self.unicode_to_reference(self.source_ref)
3167 return self.unicode_to_reference(self.source_ref)
3175
3168
3176 @declared_attr
3169 @declared_attr
3177 def target_repo(cls):
3170 def target_repo(cls):
3178 return relationship(
3171 return relationship(
3179 'Repository',
3172 'Repository',
3180 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3173 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3181
3174
3182 @property
3175 @property
3183 def target_ref_parts(self):
3176 def target_ref_parts(self):
3184 return self.unicode_to_reference(self.target_ref)
3177 return self.unicode_to_reference(self.target_ref)
3185
3178
3186 @property
3179 @property
3187 def shadow_merge_ref(self):
3180 def shadow_merge_ref(self):
3188 return self.unicode_to_reference(self._shadow_merge_ref)
3181 return self.unicode_to_reference(self._shadow_merge_ref)
3189
3182
3190 @shadow_merge_ref.setter
3183 @shadow_merge_ref.setter
3191 def shadow_merge_ref(self, ref):
3184 def shadow_merge_ref(self, ref):
3192 self._shadow_merge_ref = self.reference_to_unicode(ref)
3185 self._shadow_merge_ref = self.reference_to_unicode(ref)
3193
3186
3194 def unicode_to_reference(self, raw):
3187 def unicode_to_reference(self, raw):
3195 """
3188 """
3196 Convert a unicode (or string) to a reference object.
3189 Convert a unicode (or string) to a reference object.
3197 If unicode evaluates to False it returns None.
3190 If unicode evaluates to False it returns None.
3198 """
3191 """
3199 if raw:
3192 if raw:
3200 refs = raw.split(':')
3193 refs = raw.split(':')
3201 return Reference(*refs)
3194 return Reference(*refs)
3202 else:
3195 else:
3203 return None
3196 return None
3204
3197
3205 def reference_to_unicode(self, ref):
3198 def reference_to_unicode(self, ref):
3206 """
3199 """
3207 Convert a reference object to unicode.
3200 Convert a reference object to unicode.
3208 If reference is None it returns None.
3201 If reference is None it returns None.
3209 """
3202 """
3210 if ref:
3203 if ref:
3211 return u':'.join(ref)
3204 return u':'.join(ref)
3212 else:
3205 else:
3213 return None
3206 return None
3214
3207
3215 def get_api_data(self):
3208 def get_api_data(self):
3216 from rhodecode.model.pull_request import PullRequestModel
3209 from rhodecode.model.pull_request import PullRequestModel
3217 pull_request = self
3210 pull_request = self
3218 merge_status = PullRequestModel().merge_status(pull_request)
3211 merge_status = PullRequestModel().merge_status(pull_request)
3219
3212
3220 pull_request_url = url(
3213 pull_request_url = url(
3221 'pullrequest_show', repo_name=self.target_repo.repo_name,
3214 'pullrequest_show', repo_name=self.target_repo.repo_name,
3222 pull_request_id=self.pull_request_id, qualified=True)
3215 pull_request_id=self.pull_request_id, qualified=True)
3223
3216
3224 merge_data = {
3217 merge_data = {
3225 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3218 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3226 'reference': (
3219 'reference': (
3227 pull_request.shadow_merge_ref._asdict()
3220 pull_request.shadow_merge_ref._asdict()
3228 if pull_request.shadow_merge_ref else None),
3221 if pull_request.shadow_merge_ref else None),
3229 }
3222 }
3230
3223
3231 data = {
3224 data = {
3232 'pull_request_id': pull_request.pull_request_id,
3225 'pull_request_id': pull_request.pull_request_id,
3233 'url': pull_request_url,
3226 'url': pull_request_url,
3234 'title': pull_request.title,
3227 'title': pull_request.title,
3235 'description': pull_request.description,
3228 'description': pull_request.description,
3236 'status': pull_request.status,
3229 'status': pull_request.status,
3237 'created_on': pull_request.created_on,
3230 'created_on': pull_request.created_on,
3238 'updated_on': pull_request.updated_on,
3231 'updated_on': pull_request.updated_on,
3239 'commit_ids': pull_request.revisions,
3232 'commit_ids': pull_request.revisions,
3240 'review_status': pull_request.calculated_review_status(),
3233 'review_status': pull_request.calculated_review_status(),
3241 'mergeable': {
3234 'mergeable': {
3242 'status': merge_status[0],
3235 'status': merge_status[0],
3243 'message': unicode(merge_status[1]),
3236 'message': unicode(merge_status[1]),
3244 },
3237 },
3245 'source': {
3238 'source': {
3246 'clone_url': pull_request.source_repo.clone_url(),
3239 'clone_url': pull_request.source_repo.clone_url(),
3247 'repository': pull_request.source_repo.repo_name,
3240 'repository': pull_request.source_repo.repo_name,
3248 'reference': {
3241 'reference': {
3249 'name': pull_request.source_ref_parts.name,
3242 'name': pull_request.source_ref_parts.name,
3250 'type': pull_request.source_ref_parts.type,
3243 'type': pull_request.source_ref_parts.type,
3251 'commit_id': pull_request.source_ref_parts.commit_id,
3244 'commit_id': pull_request.source_ref_parts.commit_id,
3252 },
3245 },
3253 },
3246 },
3254 'target': {
3247 'target': {
3255 'clone_url': pull_request.target_repo.clone_url(),
3248 'clone_url': pull_request.target_repo.clone_url(),
3256 'repository': pull_request.target_repo.repo_name,
3249 'repository': pull_request.target_repo.repo_name,
3257 'reference': {
3250 'reference': {
3258 'name': pull_request.target_ref_parts.name,
3251 'name': pull_request.target_ref_parts.name,
3259 'type': pull_request.target_ref_parts.type,
3252 'type': pull_request.target_ref_parts.type,
3260 'commit_id': pull_request.target_ref_parts.commit_id,
3253 'commit_id': pull_request.target_ref_parts.commit_id,
3261 },
3254 },
3262 },
3255 },
3263 'merge': merge_data,
3256 'merge': merge_data,
3264 'author': pull_request.author.get_api_data(include_secrets=False,
3257 'author': pull_request.author.get_api_data(include_secrets=False,
3265 details='basic'),
3258 details='basic'),
3266 'reviewers': [
3259 'reviewers': [
3267 {
3260 {
3268 'user': reviewer.get_api_data(include_secrets=False,
3261 'user': reviewer.get_api_data(include_secrets=False,
3269 details='basic'),
3262 details='basic'),
3270 'reasons': reasons,
3263 'reasons': reasons,
3271 'review_status': st[0][1].status if st else 'not_reviewed',
3264 'review_status': st[0][1].status if st else 'not_reviewed',
3272 }
3265 }
3273 for reviewer, reasons, st in pull_request.reviewers_statuses()
3266 for reviewer, reasons, st in pull_request.reviewers_statuses()
3274 ]
3267 ]
3275 }
3268 }
3276
3269
3277 return data
3270 return data
3278
3271
3279
3272
3280 class PullRequest(Base, _PullRequestBase):
3273 class PullRequest(Base, _PullRequestBase):
3281 __tablename__ = 'pull_requests'
3274 __tablename__ = 'pull_requests'
3282 __table_args__ = (
3275 __table_args__ = (
3283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3277 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3285 )
3278 )
3286
3279
3287 pull_request_id = Column(
3280 pull_request_id = Column(
3288 'pull_request_id', Integer(), nullable=False, primary_key=True)
3281 'pull_request_id', Integer(), nullable=False, primary_key=True)
3289
3282
3290 def __repr__(self):
3283 def __repr__(self):
3291 if self.pull_request_id:
3284 if self.pull_request_id:
3292 return '<DB:PullRequest #%s>' % self.pull_request_id
3285 return '<DB:PullRequest #%s>' % self.pull_request_id
3293 else:
3286 else:
3294 return '<DB:PullRequest at %#x>' % id(self)
3287 return '<DB:PullRequest at %#x>' % id(self)
3295
3288
3296 reviewers = relationship('PullRequestReviewers',
3289 reviewers = relationship('PullRequestReviewers',
3297 cascade="all, delete, delete-orphan")
3290 cascade="all, delete, delete-orphan")
3298 statuses = relationship('ChangesetStatus')
3291 statuses = relationship('ChangesetStatus')
3299 comments = relationship('ChangesetComment',
3292 comments = relationship('ChangesetComment',
3300 cascade="all, delete, delete-orphan")
3293 cascade="all, delete, delete-orphan")
3301 versions = relationship('PullRequestVersion',
3294 versions = relationship('PullRequestVersion',
3302 cascade="all, delete, delete-orphan",
3295 cascade="all, delete, delete-orphan",
3303 lazy='dynamic')
3296 lazy='dynamic')
3304
3297
3305 @classmethod
3298 @classmethod
3306 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3299 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3307 internal_methods=None):
3300 internal_methods=None):
3308
3301
3309 class PullRequestDisplay(object):
3302 class PullRequestDisplay(object):
3310 """
3303 """
3311 Special object wrapper for showing PullRequest data via Versions
3304 Special object wrapper for showing PullRequest data via Versions
3312 It mimics PR object as close as possible. This is read only object
3305 It mimics PR object as close as possible. This is read only object
3313 just for display
3306 just for display
3314 """
3307 """
3315
3308
3316 def __init__(self, attrs, internal=None):
3309 def __init__(self, attrs, internal=None):
3317 self.attrs = attrs
3310 self.attrs = attrs
3318 # internal have priority over the given ones via attrs
3311 # internal have priority over the given ones via attrs
3319 self.internal = internal or ['versions']
3312 self.internal = internal or ['versions']
3320
3313
3321 def __getattr__(self, item):
3314 def __getattr__(self, item):
3322 if item in self.internal:
3315 if item in self.internal:
3323 return getattr(self, item)
3316 return getattr(self, item)
3324 try:
3317 try:
3325 return self.attrs[item]
3318 return self.attrs[item]
3326 except KeyError:
3319 except KeyError:
3327 raise AttributeError(
3320 raise AttributeError(
3328 '%s object has no attribute %s' % (self, item))
3321 '%s object has no attribute %s' % (self, item))
3329
3322
3330 def __repr__(self):
3323 def __repr__(self):
3331 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3324 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3332
3325
3333 def versions(self):
3326 def versions(self):
3334 return pull_request_obj.versions.order_by(
3327 return pull_request_obj.versions.order_by(
3335 PullRequestVersion.pull_request_version_id).all()
3328 PullRequestVersion.pull_request_version_id).all()
3336
3329
3337 def is_closed(self):
3330 def is_closed(self):
3338 return pull_request_obj.is_closed()
3331 return pull_request_obj.is_closed()
3339
3332
3340 @property
3333 @property
3341 def pull_request_version_id(self):
3334 def pull_request_version_id(self):
3342 return getattr(pull_request_obj, 'pull_request_version_id', None)
3335 return getattr(pull_request_obj, 'pull_request_version_id', None)
3343
3336
3344 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3337 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3345
3338
3346 attrs.author = StrictAttributeDict(
3339 attrs.author = StrictAttributeDict(
3347 pull_request_obj.author.get_api_data())
3340 pull_request_obj.author.get_api_data())
3348 if pull_request_obj.target_repo:
3341 if pull_request_obj.target_repo:
3349 attrs.target_repo = StrictAttributeDict(
3342 attrs.target_repo = StrictAttributeDict(
3350 pull_request_obj.target_repo.get_api_data())
3343 pull_request_obj.target_repo.get_api_data())
3351 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3344 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3352
3345
3353 if pull_request_obj.source_repo:
3346 if pull_request_obj.source_repo:
3354 attrs.source_repo = StrictAttributeDict(
3347 attrs.source_repo = StrictAttributeDict(
3355 pull_request_obj.source_repo.get_api_data())
3348 pull_request_obj.source_repo.get_api_data())
3356 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3349 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3357
3350
3358 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3351 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3359 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3352 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3360 attrs.revisions = pull_request_obj.revisions
3353 attrs.revisions = pull_request_obj.revisions
3361
3354
3362 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3355 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3363
3356
3364 return PullRequestDisplay(attrs, internal=internal_methods)
3357 return PullRequestDisplay(attrs, internal=internal_methods)
3365
3358
3366 def is_closed(self):
3359 def is_closed(self):
3367 return self.status == self.STATUS_CLOSED
3360 return self.status == self.STATUS_CLOSED
3368
3361
3369 def __json__(self):
3362 def __json__(self):
3370 return {
3363 return {
3371 'revisions': self.revisions,
3364 'revisions': self.revisions,
3372 }
3365 }
3373
3366
3374 def calculated_review_status(self):
3367 def calculated_review_status(self):
3375 from rhodecode.model.changeset_status import ChangesetStatusModel
3368 from rhodecode.model.changeset_status import ChangesetStatusModel
3376 return ChangesetStatusModel().calculated_review_status(self)
3369 return ChangesetStatusModel().calculated_review_status(self)
3377
3370
3378 def reviewers_statuses(self):
3371 def reviewers_statuses(self):
3379 from rhodecode.model.changeset_status import ChangesetStatusModel
3372 from rhodecode.model.changeset_status import ChangesetStatusModel
3380 return ChangesetStatusModel().reviewers_statuses(self)
3373 return ChangesetStatusModel().reviewers_statuses(self)
3381
3374
3382 @property
3375 @property
3383 def workspace_id(self):
3376 def workspace_id(self):
3384 from rhodecode.model.pull_request import PullRequestModel
3377 from rhodecode.model.pull_request import PullRequestModel
3385 return PullRequestModel()._workspace_id(self)
3378 return PullRequestModel()._workspace_id(self)
3386
3379
3387 def get_shadow_repo(self):
3380 def get_shadow_repo(self):
3388 workspace_id = self.workspace_id
3381 workspace_id = self.workspace_id
3389 vcs_obj = self.target_repo.scm_instance()
3382 vcs_obj = self.target_repo.scm_instance()
3390 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3383 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3391 workspace_id)
3384 workspace_id)
3392 return vcs_obj._get_shadow_instance(shadow_repository_path)
3385 return vcs_obj._get_shadow_instance(shadow_repository_path)
3393
3386
3394
3387
3395 class PullRequestVersion(Base, _PullRequestBase):
3388 class PullRequestVersion(Base, _PullRequestBase):
3396 __tablename__ = 'pull_request_versions'
3389 __tablename__ = 'pull_request_versions'
3397 __table_args__ = (
3390 __table_args__ = (
3398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3391 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3392 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3400 )
3393 )
3401
3394
3402 pull_request_version_id = Column(
3395 pull_request_version_id = Column(
3403 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3396 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3404 pull_request_id = Column(
3397 pull_request_id = Column(
3405 'pull_request_id', Integer(),
3398 'pull_request_id', Integer(),
3406 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3399 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3407 pull_request = relationship('PullRequest')
3400 pull_request = relationship('PullRequest')
3408
3401
3409 def __repr__(self):
3402 def __repr__(self):
3410 if self.pull_request_version_id:
3403 if self.pull_request_version_id:
3411 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3404 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3412 else:
3405 else:
3413 return '<DB:PullRequestVersion at %#x>' % id(self)
3406 return '<DB:PullRequestVersion at %#x>' % id(self)
3414
3407
3415 @property
3408 @property
3416 def reviewers(self):
3409 def reviewers(self):
3417 return self.pull_request.reviewers
3410 return self.pull_request.reviewers
3418
3411
3419 @property
3412 @property
3420 def versions(self):
3413 def versions(self):
3421 return self.pull_request.versions
3414 return self.pull_request.versions
3422
3415
3423 def is_closed(self):
3416 def is_closed(self):
3424 # calculate from original
3417 # calculate from original
3425 return self.pull_request.status == self.STATUS_CLOSED
3418 return self.pull_request.status == self.STATUS_CLOSED
3426
3419
3427 def calculated_review_status(self):
3420 def calculated_review_status(self):
3428 return self.pull_request.calculated_review_status()
3421 return self.pull_request.calculated_review_status()
3429
3422
3430 def reviewers_statuses(self):
3423 def reviewers_statuses(self):
3431 return self.pull_request.reviewers_statuses()
3424 return self.pull_request.reviewers_statuses()
3432
3425
3433
3426
3434 class PullRequestReviewers(Base, BaseModel):
3427 class PullRequestReviewers(Base, BaseModel):
3435 __tablename__ = 'pull_request_reviewers'
3428 __tablename__ = 'pull_request_reviewers'
3436 __table_args__ = (
3429 __table_args__ = (
3437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3430 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3438 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3431 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3439 )
3432 )
3440
3433
3441 def __init__(self, user=None, pull_request=None, reasons=None):
3434 def __init__(self, user=None, pull_request=None, reasons=None):
3442 self.user = user
3435 self.user = user
3443 self.pull_request = pull_request
3436 self.pull_request = pull_request
3444 self.reasons = reasons or []
3437 self.reasons = reasons or []
3445
3438
3446 @hybrid_property
3439 @hybrid_property
3447 def reasons(self):
3440 def reasons(self):
3448 if not self._reasons:
3441 if not self._reasons:
3449 return []
3442 return []
3450 return self._reasons
3443 return self._reasons
3451
3444
3452 @reasons.setter
3445 @reasons.setter
3453 def reasons(self, val):
3446 def reasons(self, val):
3454 val = val or []
3447 val = val or []
3455 if any(not isinstance(x, basestring) for x in val):
3448 if any(not isinstance(x, basestring) for x in val):
3456 raise Exception('invalid reasons type, must be list of strings')
3449 raise Exception('invalid reasons type, must be list of strings')
3457 self._reasons = val
3450 self._reasons = val
3458
3451
3459 pull_requests_reviewers_id = Column(
3452 pull_requests_reviewers_id = Column(
3460 'pull_requests_reviewers_id', Integer(), nullable=False,
3453 'pull_requests_reviewers_id', Integer(), nullable=False,
3461 primary_key=True)
3454 primary_key=True)
3462 pull_request_id = Column(
3455 pull_request_id = Column(
3463 "pull_request_id", Integer(),
3456 "pull_request_id", Integer(),
3464 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3457 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3465 user_id = Column(
3458 user_id = Column(
3466 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3459 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3467 _reasons = Column(
3460 _reasons = Column(
3468 'reason', MutationList.as_mutable(
3461 'reason', MutationList.as_mutable(
3469 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3462 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3470
3463
3471 user = relationship('User')
3464 user = relationship('User')
3472 pull_request = relationship('PullRequest')
3465 pull_request = relationship('PullRequest')
3473
3466
3474
3467
3475 class Notification(Base, BaseModel):
3468 class Notification(Base, BaseModel):
3476 __tablename__ = 'notifications'
3469 __tablename__ = 'notifications'
3477 __table_args__ = (
3470 __table_args__ = (
3478 Index('notification_type_idx', 'type'),
3471 Index('notification_type_idx', 'type'),
3479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3473 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3481 )
3474 )
3482
3475
3483 TYPE_CHANGESET_COMMENT = u'cs_comment'
3476 TYPE_CHANGESET_COMMENT = u'cs_comment'
3484 TYPE_MESSAGE = u'message'
3477 TYPE_MESSAGE = u'message'
3485 TYPE_MENTION = u'mention'
3478 TYPE_MENTION = u'mention'
3486 TYPE_REGISTRATION = u'registration'
3479 TYPE_REGISTRATION = u'registration'
3487 TYPE_PULL_REQUEST = u'pull_request'
3480 TYPE_PULL_REQUEST = u'pull_request'
3488 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3481 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3489
3482
3490 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3483 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3491 subject = Column('subject', Unicode(512), nullable=True)
3484 subject = Column('subject', Unicode(512), nullable=True)
3492 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3485 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3493 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3486 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3494 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3487 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3495 type_ = Column('type', Unicode(255))
3488 type_ = Column('type', Unicode(255))
3496
3489
3497 created_by_user = relationship('User')
3490 created_by_user = relationship('User')
3498 notifications_to_users = relationship('UserNotification', lazy='joined',
3491 notifications_to_users = relationship('UserNotification', lazy='joined',
3499 cascade="all, delete, delete-orphan")
3492 cascade="all, delete, delete-orphan")
3500
3493
3501 @property
3494 @property
3502 def recipients(self):
3495 def recipients(self):
3503 return [x.user for x in UserNotification.query()\
3496 return [x.user for x in UserNotification.query()\
3504 .filter(UserNotification.notification == self)\
3497 .filter(UserNotification.notification == self)\
3505 .order_by(UserNotification.user_id.asc()).all()]
3498 .order_by(UserNotification.user_id.asc()).all()]
3506
3499
3507 @classmethod
3500 @classmethod
3508 def create(cls, created_by, subject, body, recipients, type_=None):
3501 def create(cls, created_by, subject, body, recipients, type_=None):
3509 if type_ is None:
3502 if type_ is None:
3510 type_ = Notification.TYPE_MESSAGE
3503 type_ = Notification.TYPE_MESSAGE
3511
3504
3512 notification = cls()
3505 notification = cls()
3513 notification.created_by_user = created_by
3506 notification.created_by_user = created_by
3514 notification.subject = subject
3507 notification.subject = subject
3515 notification.body = body
3508 notification.body = body
3516 notification.type_ = type_
3509 notification.type_ = type_
3517 notification.created_on = datetime.datetime.now()
3510 notification.created_on = datetime.datetime.now()
3518
3511
3519 for u in recipients:
3512 for u in recipients:
3520 assoc = UserNotification()
3513 assoc = UserNotification()
3521 assoc.notification = notification
3514 assoc.notification = notification
3522
3515
3523 # if created_by is inside recipients mark his notification
3516 # if created_by is inside recipients mark his notification
3524 # as read
3517 # as read
3525 if u.user_id == created_by.user_id:
3518 if u.user_id == created_by.user_id:
3526 assoc.read = True
3519 assoc.read = True
3527
3520
3528 u.notifications.append(assoc)
3521 u.notifications.append(assoc)
3529 Session().add(notification)
3522 Session().add(notification)
3530
3523
3531 return notification
3524 return notification
3532
3525
3533 @property
3526 @property
3534 def description(self):
3527 def description(self):
3535 from rhodecode.model.notification import NotificationModel
3528 from rhodecode.model.notification import NotificationModel
3536 return NotificationModel().make_description(self)
3529 return NotificationModel().make_description(self)
3537
3530
3538
3531
3539 class UserNotification(Base, BaseModel):
3532 class UserNotification(Base, BaseModel):
3540 __tablename__ = 'user_to_notification'
3533 __tablename__ = 'user_to_notification'
3541 __table_args__ = (
3534 __table_args__ = (
3542 UniqueConstraint('user_id', 'notification_id'),
3535 UniqueConstraint('user_id', 'notification_id'),
3543 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3544 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3537 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3545 )
3538 )
3546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3539 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3547 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3540 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3548 read = Column('read', Boolean, default=False)
3541 read = Column('read', Boolean, default=False)
3549 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3542 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3550
3543
3551 user = relationship('User', lazy="joined")
3544 user = relationship('User', lazy="joined")
3552 notification = relationship('Notification', lazy="joined",
3545 notification = relationship('Notification', lazy="joined",
3553 order_by=lambda: Notification.created_on.desc(),)
3546 order_by=lambda: Notification.created_on.desc(),)
3554
3547
3555 def mark_as_read(self):
3548 def mark_as_read(self):
3556 self.read = True
3549 self.read = True
3557 Session().add(self)
3550 Session().add(self)
3558
3551
3559
3552
3560 class Gist(Base, BaseModel):
3553 class Gist(Base, BaseModel):
3561 __tablename__ = 'gists'
3554 __tablename__ = 'gists'
3562 __table_args__ = (
3555 __table_args__ = (
3563 Index('g_gist_access_id_idx', 'gist_access_id'),
3556 Index('g_gist_access_id_idx', 'gist_access_id'),
3564 Index('g_created_on_idx', 'created_on'),
3557 Index('g_created_on_idx', 'created_on'),
3565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3559 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3567 )
3560 )
3568 GIST_PUBLIC = u'public'
3561 GIST_PUBLIC = u'public'
3569 GIST_PRIVATE = u'private'
3562 GIST_PRIVATE = u'private'
3570 DEFAULT_FILENAME = u'gistfile1.txt'
3563 DEFAULT_FILENAME = u'gistfile1.txt'
3571
3564
3572 ACL_LEVEL_PUBLIC = u'acl_public'
3565 ACL_LEVEL_PUBLIC = u'acl_public'
3573 ACL_LEVEL_PRIVATE = u'acl_private'
3566 ACL_LEVEL_PRIVATE = u'acl_private'
3574
3567
3575 gist_id = Column('gist_id', Integer(), primary_key=True)
3568 gist_id = Column('gist_id', Integer(), primary_key=True)
3576 gist_access_id = Column('gist_access_id', Unicode(250))
3569 gist_access_id = Column('gist_access_id', Unicode(250))
3577 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3570 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3578 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3571 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3579 gist_expires = Column('gist_expires', Float(53), nullable=False)
3572 gist_expires = Column('gist_expires', Float(53), nullable=False)
3580 gist_type = Column('gist_type', Unicode(128), nullable=False)
3573 gist_type = Column('gist_type', Unicode(128), nullable=False)
3581 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3574 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3582 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3575 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3583 acl_level = Column('acl_level', Unicode(128), nullable=True)
3576 acl_level = Column('acl_level', Unicode(128), nullable=True)
3584
3577
3585 owner = relationship('User')
3578 owner = relationship('User')
3586
3579
3587 def __repr__(self):
3580 def __repr__(self):
3588 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3581 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3589
3582
3590 @classmethod
3583 @classmethod
3591 def get_or_404(cls, id_):
3584 def get_or_404(cls, id_):
3592 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3585 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3593 if not res:
3586 if not res:
3594 raise HTTPNotFound
3587 raise HTTPNotFound
3595 return res
3588 return res
3596
3589
3597 @classmethod
3590 @classmethod
3598 def get_by_access_id(cls, gist_access_id):
3591 def get_by_access_id(cls, gist_access_id):
3599 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3592 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3600
3593
3601 def gist_url(self):
3594 def gist_url(self):
3602 import rhodecode
3595 import rhodecode
3603 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3596 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3604 if alias_url:
3597 if alias_url:
3605 return alias_url.replace('{gistid}', self.gist_access_id)
3598 return alias_url.replace('{gistid}', self.gist_access_id)
3606
3599
3607 return url('gist', gist_id=self.gist_access_id, qualified=True)
3600 return url('gist', gist_id=self.gist_access_id, qualified=True)
3608
3601
3609 @classmethod
3602 @classmethod
3610 def base_path(cls):
3603 def base_path(cls):
3611 """
3604 """
3612 Returns base path when all gists are stored
3605 Returns base path when all gists are stored
3613
3606
3614 :param cls:
3607 :param cls:
3615 """
3608 """
3616 from rhodecode.model.gist import GIST_STORE_LOC
3609 from rhodecode.model.gist import GIST_STORE_LOC
3617 q = Session().query(RhodeCodeUi)\
3610 q = Session().query(RhodeCodeUi)\
3618 .filter(RhodeCodeUi.ui_key == URL_SEP)
3611 .filter(RhodeCodeUi.ui_key == URL_SEP)
3619 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3612 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3620 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3613 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3621
3614
3622 def get_api_data(self):
3615 def get_api_data(self):
3623 """
3616 """
3624 Common function for generating gist related data for API
3617 Common function for generating gist related data for API
3625 """
3618 """
3626 gist = self
3619 gist = self
3627 data = {
3620 data = {
3628 'gist_id': gist.gist_id,
3621 'gist_id': gist.gist_id,
3629 'type': gist.gist_type,
3622 'type': gist.gist_type,
3630 'access_id': gist.gist_access_id,
3623 'access_id': gist.gist_access_id,
3631 'description': gist.gist_description,
3624 'description': gist.gist_description,
3632 'url': gist.gist_url(),
3625 'url': gist.gist_url(),
3633 'expires': gist.gist_expires,
3626 'expires': gist.gist_expires,
3634 'created_on': gist.created_on,
3627 'created_on': gist.created_on,
3635 'modified_at': gist.modified_at,
3628 'modified_at': gist.modified_at,
3636 'content': None,
3629 'content': None,
3637 'acl_level': gist.acl_level,
3630 'acl_level': gist.acl_level,
3638 }
3631 }
3639 return data
3632 return data
3640
3633
3641 def __json__(self):
3634 def __json__(self):
3642 data = dict(
3635 data = dict(
3643 )
3636 )
3644 data.update(self.get_api_data())
3637 data.update(self.get_api_data())
3645 return data
3638 return data
3646 # SCM functions
3639 # SCM functions
3647
3640
3648 def scm_instance(self, **kwargs):
3641 def scm_instance(self, **kwargs):
3649 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3642 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3650 return get_vcs_instance(
3643 return get_vcs_instance(
3651 repo_path=safe_str(full_repo_path), create=False)
3644 repo_path=safe_str(full_repo_path), create=False)
3652
3645
3653
3646
3654 class ExternalIdentity(Base, BaseModel):
3647 class ExternalIdentity(Base, BaseModel):
3655 __tablename__ = 'external_identities'
3648 __tablename__ = 'external_identities'
3656 __table_args__ = (
3649 __table_args__ = (
3657 Index('local_user_id_idx', 'local_user_id'),
3650 Index('local_user_id_idx', 'local_user_id'),
3658 Index('external_id_idx', 'external_id'),
3651 Index('external_id_idx', 'external_id'),
3659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3660 'mysql_charset': 'utf8'})
3653 'mysql_charset': 'utf8'})
3661
3654
3662 external_id = Column('external_id', Unicode(255), default=u'',
3655 external_id = Column('external_id', Unicode(255), default=u'',
3663 primary_key=True)
3656 primary_key=True)
3664 external_username = Column('external_username', Unicode(1024), default=u'')
3657 external_username = Column('external_username', Unicode(1024), default=u'')
3665 local_user_id = Column('local_user_id', Integer(),
3658 local_user_id = Column('local_user_id', Integer(),
3666 ForeignKey('users.user_id'), primary_key=True)
3659 ForeignKey('users.user_id'), primary_key=True)
3667 provider_name = Column('provider_name', Unicode(255), default=u'',
3660 provider_name = Column('provider_name', Unicode(255), default=u'',
3668 primary_key=True)
3661 primary_key=True)
3669 access_token = Column('access_token', String(1024), default=u'')
3662 access_token = Column('access_token', String(1024), default=u'')
3670 alt_token = Column('alt_token', String(1024), default=u'')
3663 alt_token = Column('alt_token', String(1024), default=u'')
3671 token_secret = Column('token_secret', String(1024), default=u'')
3664 token_secret = Column('token_secret', String(1024), default=u'')
3672
3665
3673 @classmethod
3666 @classmethod
3674 def by_external_id_and_provider(cls, external_id, provider_name,
3667 def by_external_id_and_provider(cls, external_id, provider_name,
3675 local_user_id=None):
3668 local_user_id=None):
3676 """
3669 """
3677 Returns ExternalIdentity instance based on search params
3670 Returns ExternalIdentity instance based on search params
3678
3671
3679 :param external_id:
3672 :param external_id:
3680 :param provider_name:
3673 :param provider_name:
3681 :return: ExternalIdentity
3674 :return: ExternalIdentity
3682 """
3675 """
3683 query = cls.query()
3676 query = cls.query()
3684 query = query.filter(cls.external_id == external_id)
3677 query = query.filter(cls.external_id == external_id)
3685 query = query.filter(cls.provider_name == provider_name)
3678 query = query.filter(cls.provider_name == provider_name)
3686 if local_user_id:
3679 if local_user_id:
3687 query = query.filter(cls.local_user_id == local_user_id)
3680 query = query.filter(cls.local_user_id == local_user_id)
3688 return query.first()
3681 return query.first()
3689
3682
3690 @classmethod
3683 @classmethod
3691 def user_by_external_id_and_provider(cls, external_id, provider_name):
3684 def user_by_external_id_and_provider(cls, external_id, provider_name):
3692 """
3685 """
3693 Returns User instance based on search params
3686 Returns User instance based on search params
3694
3687
3695 :param external_id:
3688 :param external_id:
3696 :param provider_name:
3689 :param provider_name:
3697 :return: User
3690 :return: User
3698 """
3691 """
3699 query = User.query()
3692 query = User.query()
3700 query = query.filter(cls.external_id == external_id)
3693 query = query.filter(cls.external_id == external_id)
3701 query = query.filter(cls.provider_name == provider_name)
3694 query = query.filter(cls.provider_name == provider_name)
3702 query = query.filter(User.user_id == cls.local_user_id)
3695 query = query.filter(User.user_id == cls.local_user_id)
3703 return query.first()
3696 return query.first()
3704
3697
3705 @classmethod
3698 @classmethod
3706 def by_local_user_id(cls, local_user_id):
3699 def by_local_user_id(cls, local_user_id):
3707 """
3700 """
3708 Returns all tokens for user
3701 Returns all tokens for user
3709
3702
3710 :param local_user_id:
3703 :param local_user_id:
3711 :return: ExternalIdentity
3704 :return: ExternalIdentity
3712 """
3705 """
3713 query = cls.query()
3706 query = cls.query()
3714 query = query.filter(cls.local_user_id == local_user_id)
3707 query = query.filter(cls.local_user_id == local_user_id)
3715 return query
3708 return query
3716
3709
3717
3710
3718 class Integration(Base, BaseModel):
3711 class Integration(Base, BaseModel):
3719 __tablename__ = 'integrations'
3712 __tablename__ = 'integrations'
3720 __table_args__ = (
3713 __table_args__ = (
3721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3723 )
3716 )
3724
3717
3725 integration_id = Column('integration_id', Integer(), primary_key=True)
3718 integration_id = Column('integration_id', Integer(), primary_key=True)
3726 integration_type = Column('integration_type', String(255))
3719 integration_type = Column('integration_type', String(255))
3727 enabled = Column('enabled', Boolean(), nullable=False)
3720 enabled = Column('enabled', Boolean(), nullable=False)
3728 name = Column('name', String(255), nullable=False)
3721 name = Column('name', String(255), nullable=False)
3729 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3722 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3730 default=False)
3723 default=False)
3731
3724
3732 settings = Column(
3725 settings = Column(
3733 'settings_json', MutationObj.as_mutable(
3726 'settings_json', MutationObj.as_mutable(
3734 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3727 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3735 repo_id = Column(
3728 repo_id = Column(
3736 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3729 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3737 nullable=True, unique=None, default=None)
3730 nullable=True, unique=None, default=None)
3738 repo = relationship('Repository', lazy='joined')
3731 repo = relationship('Repository', lazy='joined')
3739
3732
3740 repo_group_id = Column(
3733 repo_group_id = Column(
3741 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3734 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3742 nullable=True, unique=None, default=None)
3735 nullable=True, unique=None, default=None)
3743 repo_group = relationship('RepoGroup', lazy='joined')
3736 repo_group = relationship('RepoGroup', lazy='joined')
3744
3737
3745 @property
3738 @property
3746 def scope(self):
3739 def scope(self):
3747 if self.repo:
3740 if self.repo:
3748 return repr(self.repo)
3741 return repr(self.repo)
3749 if self.repo_group:
3742 if self.repo_group:
3750 if self.child_repos_only:
3743 if self.child_repos_only:
3751 return repr(self.repo_group) + ' (child repos only)'
3744 return repr(self.repo_group) + ' (child repos only)'
3752 else:
3745 else:
3753 return repr(self.repo_group) + ' (recursive)'
3746 return repr(self.repo_group) + ' (recursive)'
3754 if self.child_repos_only:
3747 if self.child_repos_only:
3755 return 'root_repos'
3748 return 'root_repos'
3756 return 'global'
3749 return 'global'
3757
3750
3758 def __repr__(self):
3751 def __repr__(self):
3759 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3752 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3760
3753
3761
3754
3762 class RepoReviewRuleUser(Base, BaseModel):
3755 class RepoReviewRuleUser(Base, BaseModel):
3763 __tablename__ = 'repo_review_rules_users'
3756 __tablename__ = 'repo_review_rules_users'
3764 __table_args__ = (
3757 __table_args__ = (
3765 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3758 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3766 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3759 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3767 )
3760 )
3768 repo_review_rule_user_id = Column(
3761 repo_review_rule_user_id = Column(
3769 'repo_review_rule_user_id', Integer(), primary_key=True)
3762 'repo_review_rule_user_id', Integer(), primary_key=True)
3770 repo_review_rule_id = Column("repo_review_rule_id",
3763 repo_review_rule_id = Column("repo_review_rule_id",
3771 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3764 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3765 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3773 nullable=False)
3766 nullable=False)
3774 user = relationship('User')
3767 user = relationship('User')
3775
3768
3776
3769
3777 class RepoReviewRuleUserGroup(Base, BaseModel):
3770 class RepoReviewRuleUserGroup(Base, BaseModel):
3778 __tablename__ = 'repo_review_rules_users_groups'
3771 __tablename__ = 'repo_review_rules_users_groups'
3779 __table_args__ = (
3772 __table_args__ = (
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3773 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3774 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3782 )
3775 )
3783 repo_review_rule_users_group_id = Column(
3776 repo_review_rule_users_group_id = Column(
3784 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3777 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3785 repo_review_rule_id = Column("repo_review_rule_id",
3778 repo_review_rule_id = Column("repo_review_rule_id",
3786 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3779 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3787 users_group_id = Column("users_group_id", Integer(),
3780 users_group_id = Column("users_group_id", Integer(),
3788 ForeignKey('users_groups.users_group_id'), nullable=False)
3781 ForeignKey('users_groups.users_group_id'), nullable=False)
3789 users_group = relationship('UserGroup')
3782 users_group = relationship('UserGroup')
3790
3783
3791
3784
3792 class RepoReviewRule(Base, BaseModel):
3785 class RepoReviewRule(Base, BaseModel):
3793 __tablename__ = 'repo_review_rules'
3786 __tablename__ = 'repo_review_rules'
3794 __table_args__ = (
3787 __table_args__ = (
3795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3789 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3797 )
3790 )
3798
3791
3799 repo_review_rule_id = Column(
3792 repo_review_rule_id = Column(
3800 'repo_review_rule_id', Integer(), primary_key=True)
3793 'repo_review_rule_id', Integer(), primary_key=True)
3801 repo_id = Column(
3794 repo_id = Column(
3802 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3795 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3803 repo = relationship('Repository', backref='review_rules')
3796 repo = relationship('Repository', backref='review_rules')
3804
3797
3805 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3798 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3806 default=u'*') # glob
3799 default=u'*') # glob
3807 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3800 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3808 default=u'*') # glob
3801 default=u'*') # glob
3809
3802
3810 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3803 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3811 nullable=False, default=False)
3804 nullable=False, default=False)
3812 rule_users = relationship('RepoReviewRuleUser')
3805 rule_users = relationship('RepoReviewRuleUser')
3813 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3806 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3814
3807
3815 @hybrid_property
3808 @hybrid_property
3816 def branch_pattern(self):
3809 def branch_pattern(self):
3817 return self._branch_pattern or '*'
3810 return self._branch_pattern or '*'
3818
3811
3819 def _validate_glob(self, value):
3812 def _validate_glob(self, value):
3820 re.compile('^' + glob2re(value) + '$')
3813 re.compile('^' + glob2re(value) + '$')
3821
3814
3822 @branch_pattern.setter
3815 @branch_pattern.setter
3823 def branch_pattern(self, value):
3816 def branch_pattern(self, value):
3824 self._validate_glob(value)
3817 self._validate_glob(value)
3825 self._branch_pattern = value or '*'
3818 self._branch_pattern = value or '*'
3826
3819
3827 @hybrid_property
3820 @hybrid_property
3828 def file_pattern(self):
3821 def file_pattern(self):
3829 return self._file_pattern or '*'
3822 return self._file_pattern or '*'
3830
3823
3831 @file_pattern.setter
3824 @file_pattern.setter
3832 def file_pattern(self, value):
3825 def file_pattern(self, value):
3833 self._validate_glob(value)
3826 self._validate_glob(value)
3834 self._file_pattern = value or '*'
3827 self._file_pattern = value or '*'
3835
3828
3836 def matches(self, branch, files_changed):
3829 def matches(self, branch, files_changed):
3837 """
3830 """
3838 Check if this review rule matches a branch/files in a pull request
3831 Check if this review rule matches a branch/files in a pull request
3839
3832
3840 :param branch: branch name for the commit
3833 :param branch: branch name for the commit
3841 :param files_changed: list of file paths changed in the pull request
3834 :param files_changed: list of file paths changed in the pull request
3842 """
3835 """
3843
3836
3844 branch = branch or ''
3837 branch = branch or ''
3845 files_changed = files_changed or []
3838 files_changed = files_changed or []
3846
3839
3847 branch_matches = True
3840 branch_matches = True
3848 if branch:
3841 if branch:
3849 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3842 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3850 branch_matches = bool(branch_regex.search(branch))
3843 branch_matches = bool(branch_regex.search(branch))
3851
3844
3852 files_matches = True
3845 files_matches = True
3853 if self.file_pattern != '*':
3846 if self.file_pattern != '*':
3854 files_matches = False
3847 files_matches = False
3855 file_regex = re.compile(glob2re(self.file_pattern))
3848 file_regex = re.compile(glob2re(self.file_pattern))
3856 for filename in files_changed:
3849 for filename in files_changed:
3857 if file_regex.search(filename):
3850 if file_regex.search(filename):
3858 files_matches = True
3851 files_matches = True
3859 break
3852 break
3860
3853
3861 return branch_matches and files_matches
3854 return branch_matches and files_matches
3862
3855
3863 @property
3856 @property
3864 def review_users(self):
3857 def review_users(self):
3865 """ Returns the users which this rule applies to """
3858 """ Returns the users which this rule applies to """
3866
3859
3867 users = set()
3860 users = set()
3868 users |= set([
3861 users |= set([
3869 rule_user.user for rule_user in self.rule_users
3862 rule_user.user for rule_user in self.rule_users
3870 if rule_user.user.active])
3863 if rule_user.user.active])
3871 users |= set(
3864 users |= set(
3872 member.user
3865 member.user
3873 for rule_user_group in self.rule_user_groups
3866 for rule_user_group in self.rule_user_groups
3874 for member in rule_user_group.users_group.members
3867 for member in rule_user_group.users_group.members
3875 if member.user.active
3868 if member.user.active
3876 )
3869 )
3877 return users
3870 return users
3878
3871
3879 def __repr__(self):
3872 def __repr__(self):
3880 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3873 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3881 self.repo_review_rule_id, self.repo)
3874 self.repo_review_rule_id, self.repo)
3882
3875
3883
3876
3884 class DbMigrateVersion(Base, BaseModel):
3877 class DbMigrateVersion(Base, BaseModel):
3885 __tablename__ = 'db_migrate_version'
3878 __tablename__ = 'db_migrate_version'
3886 __table_args__ = (
3879 __table_args__ = (
3887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3889 )
3882 )
3890 repository_id = Column('repository_id', String(250), primary_key=True)
3883 repository_id = Column('repository_id', String(250), primary_key=True)
3891 repository_path = Column('repository_path', Text)
3884 repository_path = Column('repository_path', Text)
3892 version = Column('version', Integer)
3885 version = Column('version', Integer)
3893
3886
3894
3887
3895 class DbSession(Base, BaseModel):
3888 class DbSession(Base, BaseModel):
3896 __tablename__ = 'db_session'
3889 __tablename__ = 'db_session'
3897 __table_args__ = (
3890 __table_args__ = (
3898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3891 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3892 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3900 )
3893 )
3901
3894
3902 def __repr__(self):
3895 def __repr__(self):
3903 return '<DB:DbSession({})>'.format(self.id)
3896 return '<DB:DbSession({})>'.format(self.id)
3904
3897
3905 id = Column('id', Integer())
3898 id = Column('id', Integer())
3906 namespace = Column('namespace', String(255), primary_key=True)
3899 namespace = Column('namespace', String(255), primary_key=True)
3907 accessed = Column('accessed', DateTime, nullable=False)
3900 accessed = Column('accessed', DateTime, nullable=False)
3908 created = Column('created', DateTime, nullable=False)
3901 created = Column('created', DateTime, nullable=False)
3909 data = Column('data', PickleType, nullable=False)
3902 data = Column('data', PickleType, nullable=False)
@@ -1,3915 +1,3908
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
354 HOOK_PUSH = 'changegroup.push_logger'
354 HOOK_PUSH = 'changegroup.push_logger'
355
355
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # git part is currently hardcoded.
357 # git part is currently hardcoded.
358
358
359 # SVN PATTERNS
359 # SVN PATTERNS
360 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_TAG_ID = 'vcs_svn_tag'
361 SVN_TAG_ID = 'vcs_svn_tag'
362
362
363 ui_id = Column(
363 ui_id = Column(
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 primary_key=True)
365 primary_key=True)
366 ui_section = Column(
366 ui_section = Column(
367 "ui_section", String(255), nullable=True, unique=None, default=None)
367 "ui_section", String(255), nullable=True, unique=None, default=None)
368 ui_key = Column(
368 ui_key = Column(
369 "ui_key", String(255), nullable=True, unique=None, default=None)
369 "ui_key", String(255), nullable=True, unique=None, default=None)
370 ui_value = Column(
370 ui_value = Column(
371 "ui_value", String(255), nullable=True, unique=None, default=None)
371 "ui_value", String(255), nullable=True, unique=None, default=None)
372 ui_active = Column(
372 ui_active = Column(
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374
374
375 def __repr__(self):
375 def __repr__(self):
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 self.ui_key, self.ui_value)
377 self.ui_key, self.ui_value)
378
378
379
379
380 class RepoRhodeCodeSetting(Base, BaseModel):
380 class RepoRhodeCodeSetting(Base, BaseModel):
381 __tablename__ = 'repo_rhodecode_settings'
381 __tablename__ = 'repo_rhodecode_settings'
382 __table_args__ = (
382 __table_args__ = (
383 UniqueConstraint(
383 UniqueConstraint(
384 'app_settings_name', 'repository_id',
384 'app_settings_name', 'repository_id',
385 name='uq_repo_rhodecode_setting_name_repo_id'),
385 name='uq_repo_rhodecode_setting_name_repo_id'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 repository_id = Column(
390 repository_id = Column(
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 nullable=False)
392 nullable=False)
393 app_settings_id = Column(
393 app_settings_id = Column(
394 "app_settings_id", Integer(), nullable=False, unique=True,
394 "app_settings_id", Integer(), nullable=False, unique=True,
395 default=None, primary_key=True)
395 default=None, primary_key=True)
396 app_settings_name = Column(
396 app_settings_name = Column(
397 "app_settings_name", String(255), nullable=True, unique=None,
397 "app_settings_name", String(255), nullable=True, unique=None,
398 default=None)
398 default=None)
399 _app_settings_value = Column(
399 _app_settings_value = Column(
400 "app_settings_value", String(4096), nullable=True, unique=None,
400 "app_settings_value", String(4096), nullable=True, unique=None,
401 default=None)
401 default=None)
402 _app_settings_type = Column(
402 _app_settings_type = Column(
403 "app_settings_type", String(255), nullable=True, unique=None,
403 "app_settings_type", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405
405
406 repository = relationship('Repository')
406 repository = relationship('Repository')
407
407
408 def __init__(self, repository_id, key='', val='', type='unicode'):
408 def __init__(self, repository_id, key='', val='', type='unicode'):
409 self.repository_id = repository_id
409 self.repository_id = repository_id
410 self.app_settings_name = key
410 self.app_settings_name = key
411 self.app_settings_type = type
411 self.app_settings_type = type
412 self.app_settings_value = val
412 self.app_settings_value = val
413
413
414 @validates('_app_settings_value')
414 @validates('_app_settings_value')
415 def validate_settings_value(self, key, val):
415 def validate_settings_value(self, key, val):
416 assert type(val) == unicode
416 assert type(val) == unicode
417 return val
417 return val
418
418
419 @hybrid_property
419 @hybrid_property
420 def app_settings_value(self):
420 def app_settings_value(self):
421 v = self._app_settings_value
421 v = self._app_settings_value
422 type_ = self.app_settings_type
422 type_ = self.app_settings_type
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 return converter(v)
425 return converter(v)
426
426
427 @app_settings_value.setter
427 @app_settings_value.setter
428 def app_settings_value(self, val):
428 def app_settings_value(self, val):
429 """
429 """
430 Setter that will always make sure we use unicode in app_settings_value
430 Setter that will always make sure we use unicode in app_settings_value
431
431
432 :param val:
432 :param val:
433 """
433 """
434 self._app_settings_value = safe_unicode(val)
434 self._app_settings_value = safe_unicode(val)
435
435
436 @hybrid_property
436 @hybrid_property
437 def app_settings_type(self):
437 def app_settings_type(self):
438 return self._app_settings_type
438 return self._app_settings_type
439
439
440 @app_settings_type.setter
440 @app_settings_type.setter
441 def app_settings_type(self, val):
441 def app_settings_type(self, val):
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 if val not in SETTINGS_TYPES:
443 if val not in SETTINGS_TYPES:
444 raise Exception('type must be one of %s got %s'
444 raise Exception('type must be one of %s got %s'
445 % (SETTINGS_TYPES.keys(), val))
445 % (SETTINGS_TYPES.keys(), val))
446 self._app_settings_type = val
446 self._app_settings_type = val
447
447
448 def __unicode__(self):
448 def __unicode__(self):
449 return u"<%s('%s:%s:%s[%s]')>" % (
449 return u"<%s('%s:%s:%s[%s]')>" % (
450 self.__class__.__name__, self.repository.repo_name,
450 self.__class__.__name__, self.repository.repo_name,
451 self.app_settings_name, self.app_settings_value,
451 self.app_settings_name, self.app_settings_value,
452 self.app_settings_type
452 self.app_settings_type
453 )
453 )
454
454
455
455
456 class RepoRhodeCodeUi(Base, BaseModel):
456 class RepoRhodeCodeUi(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_ui'
457 __tablename__ = 'repo_rhodecode_ui'
458 __table_args__ = (
458 __table_args__ = (
459 UniqueConstraint(
459 UniqueConstraint(
460 'repository_id', 'ui_section', 'ui_key',
460 'repository_id', 'ui_section', 'ui_key',
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 )
464 )
465
465
466 repository_id = Column(
466 repository_id = Column(
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 nullable=False)
468 nullable=False)
469 ui_id = Column(
469 ui_id = Column(
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 primary_key=True)
471 primary_key=True)
472 ui_section = Column(
472 ui_section = Column(
473 "ui_section", String(255), nullable=True, unique=None, default=None)
473 "ui_section", String(255), nullable=True, unique=None, default=None)
474 ui_key = Column(
474 ui_key = Column(
475 "ui_key", String(255), nullable=True, unique=None, default=None)
475 "ui_key", String(255), nullable=True, unique=None, default=None)
476 ui_value = Column(
476 ui_value = Column(
477 "ui_value", String(255), nullable=True, unique=None, default=None)
477 "ui_value", String(255), nullable=True, unique=None, default=None)
478 ui_active = Column(
478 ui_active = Column(
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480
480
481 repository = relationship('Repository')
481 repository = relationship('Repository')
482
482
483 def __repr__(self):
483 def __repr__(self):
484 return '<%s[%s:%s]%s=>%s]>' % (
484 return '<%s[%s:%s]%s=>%s]>' % (
485 self.__class__.__name__, self.repository.repo_name,
485 self.__class__.__name__, self.repository.repo_name,
486 self.ui_section, self.ui_key, self.ui_value)
486 self.ui_section, self.ui_key, self.ui_value)
487
487
488
488
489 class User(Base, BaseModel):
489 class User(Base, BaseModel):
490 __tablename__ = 'users'
490 __tablename__ = 'users'
491 __table_args__ = (
491 __table_args__ = (
492 UniqueConstraint('username'), UniqueConstraint('email'),
492 UniqueConstraint('username'), UniqueConstraint('email'),
493 Index('u_username_idx', 'username'),
493 Index('u_username_idx', 'username'),
494 Index('u_email_idx', 'email'),
494 Index('u_email_idx', 'email'),
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 )
497 )
498 DEFAULT_USER = 'default'
498 DEFAULT_USER = 'default'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501
501
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517
517
518 user_log = relationship('UserLog')
518 user_log = relationship('UserLog')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520
520
521 repositories = relationship('Repository')
521 repositories = relationship('Repository')
522 repository_groups = relationship('RepoGroup')
522 repository_groups = relationship('RepoGroup')
523 user_groups = relationship('UserGroup')
523 user_groups = relationship('UserGroup')
524
524
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527
527
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531
531
532 group_member = relationship('UserGroupMember', cascade='all')
532 group_member = relationship('UserGroupMember', cascade='all')
533
533
534 notifications = relationship('UserNotification', cascade='all')
534 notifications = relationship('UserNotification', cascade='all')
535 # notifications assigned to this user
535 # notifications assigned to this user
536 user_created_notifications = relationship('Notification', cascade='all')
536 user_created_notifications = relationship('Notification', cascade='all')
537 # comments created by this user
537 # comments created by this user
538 user_comments = relationship('ChangesetComment', cascade='all')
538 user_comments = relationship('ChangesetComment', cascade='all')
539 # user profile extra info
539 # user profile extra info
540 user_emails = relationship('UserEmailMap', cascade='all')
540 user_emails = relationship('UserEmailMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 # gists
543 # gists
544 user_gists = relationship('Gist', cascade='all')
544 user_gists = relationship('Gist', cascade='all')
545 # user pull requests
545 # user pull requests
546 user_pull_requests = relationship('PullRequest', cascade='all')
546 user_pull_requests = relationship('PullRequest', cascade='all')
547 # external identities
547 # external identities
548 extenal_identities = relationship(
548 extenal_identities = relationship(
549 'ExternalIdentity',
549 'ExternalIdentity',
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 cascade='all')
551 cascade='all')
552
552
553 def __unicode__(self):
553 def __unicode__(self):
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 self.user_id, self.username)
555 self.user_id, self.username)
556
556
557 @hybrid_property
557 @hybrid_property
558 def email(self):
558 def email(self):
559 return self._email
559 return self._email
560
560
561 @email.setter
561 @email.setter
562 def email(self, val):
562 def email(self, val):
563 self._email = val.lower() if val else None
563 self._email = val.lower() if val else None
564
564
565 @property
565 @property
566 def firstname(self):
566 def firstname(self):
567 # alias for future
567 # alias for future
568 return self.name
568 return self.name
569
569
570 @property
570 @property
571 def emails(self):
571 def emails(self):
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 return [self.email] + [x.email for x in other]
573 return [self.email] + [x.email for x in other]
574
574
575 @property
575 @property
576 def auth_tokens(self):
576 def auth_tokens(self):
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578
578
579 @property
579 @property
580 def extra_auth_tokens(self):
580 def extra_auth_tokens(self):
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582
582
583 @property
583 @property
584 def feed_token(self):
584 def feed_token(self):
585 return self.get_feed_token()
585 return self.get_feed_token()
586
586
587 def get_feed_token(self):
587 def get_feed_token(self):
588 feed_tokens = UserApiKeys.query()\
588 feed_tokens = UserApiKeys.query()\
589 .filter(UserApiKeys.user == self)\
589 .filter(UserApiKeys.user == self)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
591 .all()
591 .all()
592 if feed_tokens:
592 if feed_tokens:
593 return feed_tokens[0].api_key
593 return feed_tokens[0].api_key
594 return 'NO_FEED_TOKEN_AVAILABLE'
594 return 'NO_FEED_TOKEN_AVAILABLE'
595
595
596 @classmethod
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
601 if role:
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
604 return tokens.all()
605
605
606 def authenticate_by_token(self, auth_token, roles=None):
606 def authenticate_by_token(self, auth_token, roles=None):
607 from rhodecode.lib import auth
607 from rhodecode.lib import auth
608
608
609 log.debug('Trying to authenticate user: %s via auth-token, '
609 log.debug('Trying to authenticate user: %s via auth-token, '
610 'and roles: %s', self, roles)
610 'and roles: %s', self, roles)
611
611
612 if not auth_token:
612 if not auth_token:
613 return False
613 return False
614
614
615 crypto_backend = auth.crypto_backend()
615 crypto_backend = auth.crypto_backend()
616
616
617 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
617 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
618 tokens_q = UserApiKeys.query()\
618 tokens_q = UserApiKeys.query()\
619 .filter(UserApiKeys.user_id == self.user_id)\
619 .filter(UserApiKeys.user_id == self.user_id)\
620 .filter(or_(UserApiKeys.expires == -1,
620 .filter(or_(UserApiKeys.expires == -1,
621 UserApiKeys.expires >= time.time()))
621 UserApiKeys.expires >= time.time()))
622
622
623 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
623 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
624
624
625 plain_tokens = []
625 plain_tokens = []
626 hash_tokens = []
626 hash_tokens = []
627
627
628 for token in tokens_q.all():
628 for token in tokens_q.all():
629 if token.api_key.startswith(crypto_backend.ENC_PREF):
629 if token.api_key.startswith(crypto_backend.ENC_PREF):
630 hash_tokens.append(token.api_key)
630 hash_tokens.append(token.api_key)
631 else:
631 else:
632 plain_tokens.append(token.api_key)
632 plain_tokens.append(token.api_key)
633
633
634 is_plain_match = auth_token in plain_tokens
634 is_plain_match = auth_token in plain_tokens
635 if is_plain_match:
635 if is_plain_match:
636 return True
636 return True
637
637
638 for hashed in hash_tokens:
638 for hashed in hash_tokens:
639 # marcink: this is expensive to calculate, but the most secure
639 # marcink: this is expensive to calculate, but the most secure
640 match = crypto_backend.hash_check(auth_token, hashed)
640 match = crypto_backend.hash_check(auth_token, hashed)
641 if match:
641 if match:
642 return True
642 return True
643
643
644 return False
644 return False
645
645
646 @property
646 @property
647 def builtin_token_roles(self):
648 roles = [
649 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
650 ]
651 return map(UserApiKeys._get_role_name, roles)
652
653 @property
654 def ip_addresses(self):
647 def ip_addresses(self):
655 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
648 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
656 return [x.ip_addr for x in ret]
649 return [x.ip_addr for x in ret]
657
650
658 @property
651 @property
659 def username_and_name(self):
652 def username_and_name(self):
660 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
653 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
661
654
662 @property
655 @property
663 def username_or_name_or_email(self):
656 def username_or_name_or_email(self):
664 full_name = self.full_name if self.full_name is not ' ' else None
657 full_name = self.full_name if self.full_name is not ' ' else None
665 return self.username or full_name or self.email
658 return self.username or full_name or self.email
666
659
667 @property
660 @property
668 def full_name(self):
661 def full_name(self):
669 return '%s %s' % (self.firstname, self.lastname)
662 return '%s %s' % (self.firstname, self.lastname)
670
663
671 @property
664 @property
672 def full_name_or_username(self):
665 def full_name_or_username(self):
673 return ('%s %s' % (self.firstname, self.lastname)
666 return ('%s %s' % (self.firstname, self.lastname)
674 if (self.firstname and self.lastname) else self.username)
667 if (self.firstname and self.lastname) else self.username)
675
668
676 @property
669 @property
677 def full_contact(self):
670 def full_contact(self):
678 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
671 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
679
672
680 @property
673 @property
681 def short_contact(self):
674 def short_contact(self):
682 return '%s %s' % (self.firstname, self.lastname)
675 return '%s %s' % (self.firstname, self.lastname)
683
676
684 @property
677 @property
685 def is_admin(self):
678 def is_admin(self):
686 return self.admin
679 return self.admin
687
680
688 @property
681 @property
689 def AuthUser(self):
682 def AuthUser(self):
690 """
683 """
691 Returns instance of AuthUser for this user
684 Returns instance of AuthUser for this user
692 """
685 """
693 from rhodecode.lib.auth import AuthUser
686 from rhodecode.lib.auth import AuthUser
694 return AuthUser(user_id=self.user_id, api_key=self.api_key,
687 return AuthUser(user_id=self.user_id, api_key=self.api_key,
695 username=self.username)
688 username=self.username)
696
689
697 @hybrid_property
690 @hybrid_property
698 def user_data(self):
691 def user_data(self):
699 if not self._user_data:
692 if not self._user_data:
700 return {}
693 return {}
701
694
702 try:
695 try:
703 return json.loads(self._user_data)
696 return json.loads(self._user_data)
704 except TypeError:
697 except TypeError:
705 return {}
698 return {}
706
699
707 @user_data.setter
700 @user_data.setter
708 def user_data(self, val):
701 def user_data(self, val):
709 if not isinstance(val, dict):
702 if not isinstance(val, dict):
710 raise Exception('user_data must be dict, got %s' % type(val))
703 raise Exception('user_data must be dict, got %s' % type(val))
711 try:
704 try:
712 self._user_data = json.dumps(val)
705 self._user_data = json.dumps(val)
713 except Exception:
706 except Exception:
714 log.error(traceback.format_exc())
707 log.error(traceback.format_exc())
715
708
716 @classmethod
709 @classmethod
717 def get_by_username(cls, username, case_insensitive=False,
710 def get_by_username(cls, username, case_insensitive=False,
718 cache=False, identity_cache=False):
711 cache=False, identity_cache=False):
719 session = Session()
712 session = Session()
720
713
721 if case_insensitive:
714 if case_insensitive:
722 q = cls.query().filter(
715 q = cls.query().filter(
723 func.lower(cls.username) == func.lower(username))
716 func.lower(cls.username) == func.lower(username))
724 else:
717 else:
725 q = cls.query().filter(cls.username == username)
718 q = cls.query().filter(cls.username == username)
726
719
727 if cache:
720 if cache:
728 if identity_cache:
721 if identity_cache:
729 val = cls.identity_cache(session, 'username', username)
722 val = cls.identity_cache(session, 'username', username)
730 if val:
723 if val:
731 return val
724 return val
732 else:
725 else:
733 q = q.options(
726 q = q.options(
734 FromCache("sql_cache_short",
727 FromCache("sql_cache_short",
735 "get_user_by_name_%s" % _hash_key(username)))
728 "get_user_by_name_%s" % _hash_key(username)))
736
729
737 return q.scalar()
730 return q.scalar()
738
731
739 @classmethod
732 @classmethod
740 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
733 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
741 q = cls.query().filter(cls.api_key == auth_token)
734 q = cls.query().filter(cls.api_key == auth_token)
742
735
743 if cache:
736 if cache:
744 q = q.options(FromCache("sql_cache_short",
737 q = q.options(FromCache("sql_cache_short",
745 "get_auth_token_%s" % auth_token))
738 "get_auth_token_%s" % auth_token))
746 res = q.scalar()
739 res = q.scalar()
747
740
748 if fallback and not res:
741 if fallback and not res:
749 #fallback to additional keys
742 #fallback to additional keys
750 _res = UserApiKeys.query()\
743 _res = UserApiKeys.query()\
751 .filter(UserApiKeys.api_key == auth_token)\
744 .filter(UserApiKeys.api_key == auth_token)\
752 .filter(or_(UserApiKeys.expires == -1,
745 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))\
746 UserApiKeys.expires >= time.time()))\
754 .first()
747 .first()
755 if _res:
748 if _res:
756 res = _res.user
749 res = _res.user
757 return res
750 return res
758
751
759 @classmethod
752 @classmethod
760 def get_by_email(cls, email, case_insensitive=False, cache=False):
753 def get_by_email(cls, email, case_insensitive=False, cache=False):
761
754
762 if case_insensitive:
755 if case_insensitive:
763 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
756 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
764
757
765 else:
758 else:
766 q = cls.query().filter(cls.email == email)
759 q = cls.query().filter(cls.email == email)
767
760
768 if cache:
761 if cache:
769 q = q.options(FromCache("sql_cache_short",
762 q = q.options(FromCache("sql_cache_short",
770 "get_email_key_%s" % _hash_key(email)))
763 "get_email_key_%s" % _hash_key(email)))
771
764
772 ret = q.scalar()
765 ret = q.scalar()
773 if ret is None:
766 if ret is None:
774 q = UserEmailMap.query()
767 q = UserEmailMap.query()
775 # try fetching in alternate email map
768 # try fetching in alternate email map
776 if case_insensitive:
769 if case_insensitive:
777 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
770 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
778 else:
771 else:
779 q = q.filter(UserEmailMap.email == email)
772 q = q.filter(UserEmailMap.email == email)
780 q = q.options(joinedload(UserEmailMap.user))
773 q = q.options(joinedload(UserEmailMap.user))
781 if cache:
774 if cache:
782 q = q.options(FromCache("sql_cache_short",
775 q = q.options(FromCache("sql_cache_short",
783 "get_email_map_key_%s" % email))
776 "get_email_map_key_%s" % email))
784 ret = getattr(q.scalar(), 'user', None)
777 ret = getattr(q.scalar(), 'user', None)
785
778
786 return ret
779 return ret
787
780
788 @classmethod
781 @classmethod
789 def get_from_cs_author(cls, author):
782 def get_from_cs_author(cls, author):
790 """
783 """
791 Tries to get User objects out of commit author string
784 Tries to get User objects out of commit author string
792
785
793 :param author:
786 :param author:
794 """
787 """
795 from rhodecode.lib.helpers import email, author_name
788 from rhodecode.lib.helpers import email, author_name
796 # Valid email in the attribute passed, see if they're in the system
789 # Valid email in the attribute passed, see if they're in the system
797 _email = email(author)
790 _email = email(author)
798 if _email:
791 if _email:
799 user = cls.get_by_email(_email, case_insensitive=True)
792 user = cls.get_by_email(_email, case_insensitive=True)
800 if user:
793 if user:
801 return user
794 return user
802 # Maybe we can match by username?
795 # Maybe we can match by username?
803 _author = author_name(author)
796 _author = author_name(author)
804 user = cls.get_by_username(_author, case_insensitive=True)
797 user = cls.get_by_username(_author, case_insensitive=True)
805 if user:
798 if user:
806 return user
799 return user
807
800
808 def update_userdata(self, **kwargs):
801 def update_userdata(self, **kwargs):
809 usr = self
802 usr = self
810 old = usr.user_data
803 old = usr.user_data
811 old.update(**kwargs)
804 old.update(**kwargs)
812 usr.user_data = old
805 usr.user_data = old
813 Session().add(usr)
806 Session().add(usr)
814 log.debug('updated userdata with ', kwargs)
807 log.debug('updated userdata with ', kwargs)
815
808
816 def update_lastlogin(self):
809 def update_lastlogin(self):
817 """Update user lastlogin"""
810 """Update user lastlogin"""
818 self.last_login = datetime.datetime.now()
811 self.last_login = datetime.datetime.now()
819 Session().add(self)
812 Session().add(self)
820 log.debug('updated user %s lastlogin', self.username)
813 log.debug('updated user %s lastlogin', self.username)
821
814
822 def update_lastactivity(self):
815 def update_lastactivity(self):
823 """Update user lastactivity"""
816 """Update user lastactivity"""
824 usr = self
817 usr = self
825 old = usr.user_data
818 old = usr.user_data
826 old.update({'last_activity': time.time()})
819 old.update({'last_activity': time.time()})
827 usr.user_data = old
820 usr.user_data = old
828 Session().add(usr)
821 Session().add(usr)
829 log.debug('updated user %s lastactivity', usr.username)
822 log.debug('updated user %s lastactivity', usr.username)
830
823
831 def update_password(self, new_password):
824 def update_password(self, new_password):
832 from rhodecode.lib.auth import get_crypt_password
825 from rhodecode.lib.auth import get_crypt_password
833
826
834 self.password = get_crypt_password(new_password)
827 self.password = get_crypt_password(new_password)
835 Session().add(self)
828 Session().add(self)
836
829
837 @classmethod
830 @classmethod
838 def get_first_super_admin(cls):
831 def get_first_super_admin(cls):
839 user = User.query().filter(User.admin == true()).first()
832 user = User.query().filter(User.admin == true()).first()
840 if user is None:
833 if user is None:
841 raise Exception('FATAL: Missing administrative account!')
834 raise Exception('FATAL: Missing administrative account!')
842 return user
835 return user
843
836
844 @classmethod
837 @classmethod
845 def get_all_super_admins(cls):
838 def get_all_super_admins(cls):
846 """
839 """
847 Returns all admin accounts sorted by username
840 Returns all admin accounts sorted by username
848 """
841 """
849 return User.query().filter(User.admin == true())\
842 return User.query().filter(User.admin == true())\
850 .order_by(User.username.asc()).all()
843 .order_by(User.username.asc()).all()
851
844
852 @classmethod
845 @classmethod
853 def get_default_user(cls, cache=False):
846 def get_default_user(cls, cache=False):
854 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
847 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
855 if user is None:
848 if user is None:
856 raise Exception('FATAL: Missing default account!')
849 raise Exception('FATAL: Missing default account!')
857 return user
850 return user
858
851
859 def _get_default_perms(self, user, suffix=''):
852 def _get_default_perms(self, user, suffix=''):
860 from rhodecode.model.permission import PermissionModel
853 from rhodecode.model.permission import PermissionModel
861 return PermissionModel().get_default_perms(user.user_perms, suffix)
854 return PermissionModel().get_default_perms(user.user_perms, suffix)
862
855
863 def get_default_perms(self, suffix=''):
856 def get_default_perms(self, suffix=''):
864 return self._get_default_perms(self, suffix)
857 return self._get_default_perms(self, suffix)
865
858
866 def get_api_data(self, include_secrets=False, details='full'):
859 def get_api_data(self, include_secrets=False, details='full'):
867 """
860 """
868 Common function for generating user related data for API
861 Common function for generating user related data for API
869
862
870 :param include_secrets: By default secrets in the API data will be replaced
863 :param include_secrets: By default secrets in the API data will be replaced
871 by a placeholder value to prevent exposing this data by accident. In case
864 by a placeholder value to prevent exposing this data by accident. In case
872 this data shall be exposed, set this flag to ``True``.
865 this data shall be exposed, set this flag to ``True``.
873
866
874 :param details: details can be 'basic|full' basic gives only a subset of
867 :param details: details can be 'basic|full' basic gives only a subset of
875 the available user information that includes user_id, name and emails.
868 the available user information that includes user_id, name and emails.
876 """
869 """
877 user = self
870 user = self
878 user_data = self.user_data
871 user_data = self.user_data
879 data = {
872 data = {
880 'user_id': user.user_id,
873 'user_id': user.user_id,
881 'username': user.username,
874 'username': user.username,
882 'firstname': user.name,
875 'firstname': user.name,
883 'lastname': user.lastname,
876 'lastname': user.lastname,
884 'email': user.email,
877 'email': user.email,
885 'emails': user.emails,
878 'emails': user.emails,
886 }
879 }
887 if details == 'basic':
880 if details == 'basic':
888 return data
881 return data
889
882
890 api_key_length = 40
883 api_key_length = 40
891 api_key_replacement = '*' * api_key_length
884 api_key_replacement = '*' * api_key_length
892
885
893 extras = {
886 extras = {
894 'api_key': api_key_replacement,
887 'api_key': api_key_replacement,
895 'api_keys': [api_key_replacement],
888 'api_keys': [api_key_replacement],
896 'active': user.active,
889 'active': user.active,
897 'admin': user.admin,
890 'admin': user.admin,
898 'extern_type': user.extern_type,
891 'extern_type': user.extern_type,
899 'extern_name': user.extern_name,
892 'extern_name': user.extern_name,
900 'last_login': user.last_login,
893 'last_login': user.last_login,
901 'ip_addresses': user.ip_addresses,
894 'ip_addresses': user.ip_addresses,
902 'language': user_data.get('language')
895 'language': user_data.get('language')
903 }
896 }
904 data.update(extras)
897 data.update(extras)
905
898
906 if include_secrets:
899 if include_secrets:
907 data['api_key'] = user.api_key
900 data['api_key'] = user.api_key
908 data['api_keys'] = user.auth_tokens
901 data['api_keys'] = user.auth_tokens
909 return data
902 return data
910
903
911 def __json__(self):
904 def __json__(self):
912 data = {
905 data = {
913 'full_name': self.full_name,
906 'full_name': self.full_name,
914 'full_name_or_username': self.full_name_or_username,
907 'full_name_or_username': self.full_name_or_username,
915 'short_contact': self.short_contact,
908 'short_contact': self.short_contact,
916 'full_contact': self.full_contact,
909 'full_contact': self.full_contact,
917 }
910 }
918 data.update(self.get_api_data())
911 data.update(self.get_api_data())
919 return data
912 return data
920
913
921
914
922 class UserApiKeys(Base, BaseModel):
915 class UserApiKeys(Base, BaseModel):
923 __tablename__ = 'user_api_keys'
916 __tablename__ = 'user_api_keys'
924 __table_args__ = (
917 __table_args__ = (
925 Index('uak_api_key_idx', 'api_key'),
918 Index('uak_api_key_idx', 'api_key'),
926 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
919 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
927 UniqueConstraint('api_key'),
920 UniqueConstraint('api_key'),
928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
921 {'extend_existing': True, 'mysql_engine': 'InnoDB',
929 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
922 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
930 )
923 )
931 __mapper_args__ = {}
924 __mapper_args__ = {}
932
925
933 # ApiKey role
926 # ApiKey role
934 ROLE_ALL = 'token_role_all'
927 ROLE_ALL = 'token_role_all'
935 ROLE_HTTP = 'token_role_http'
928 ROLE_HTTP = 'token_role_http'
936 ROLE_VCS = 'token_role_vcs'
929 ROLE_VCS = 'token_role_vcs'
937 ROLE_API = 'token_role_api'
930 ROLE_API = 'token_role_api'
938 ROLE_FEED = 'token_role_feed'
931 ROLE_FEED = 'token_role_feed'
939 ROLE_PASSWORD_RESET = 'token_password_reset'
932 ROLE_PASSWORD_RESET = 'token_password_reset'
940
933
941 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
934 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
942
935
943 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
944 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
945 api_key = Column("api_key", String(255), nullable=False, unique=True)
938 api_key = Column("api_key", String(255), nullable=False, unique=True)
946 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
939 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
947 expires = Column('expires', Float(53), nullable=False)
940 expires = Column('expires', Float(53), nullable=False)
948 role = Column('role', String(255), nullable=True)
941 role = Column('role', String(255), nullable=True)
949 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
942 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
950
943
951 # scope columns
944 # scope columns
952 repo_id = Column(
945 repo_id = Column(
953 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
946 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
954 nullable=True, unique=None, default=None)
947 nullable=True, unique=None, default=None)
955 repo = relationship('Repository', lazy='joined')
948 repo = relationship('Repository', lazy='joined')
956
949
957 repo_group_id = Column(
950 repo_group_id = Column(
958 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
951 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
959 nullable=True, unique=None, default=None)
952 nullable=True, unique=None, default=None)
960 repo_group = relationship('RepoGroup', lazy='joined')
953 repo_group = relationship('RepoGroup', lazy='joined')
961
954
962 user = relationship('User', lazy='joined')
955 user = relationship('User', lazy='joined')
963
956
964 @classmethod
957 @classmethod
965 def _get_role_name(cls, role):
958 def _get_role_name(cls, role):
966 return {
959 return {
967 cls.ROLE_ALL: _('all'),
960 cls.ROLE_ALL: _('all'),
968 cls.ROLE_HTTP: _('http/web interface'),
961 cls.ROLE_HTTP: _('http/web interface'),
969 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
962 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
970 cls.ROLE_API: _('api calls'),
963 cls.ROLE_API: _('api calls'),
971 cls.ROLE_FEED: _('feed access'),
964 cls.ROLE_FEED: _('feed access'),
972 }.get(role, role)
965 }.get(role, role)
973
966
974 @property
967 @property
975 def expired(self):
968 def expired(self):
976 if self.expires == -1:
969 if self.expires == -1:
977 return False
970 return False
978 return time.time() > self.expires
971 return time.time() > self.expires
979
972
980 @property
973 @property
981 def role_humanized(self):
974 def role_humanized(self):
982 return self._get_role_name(self.role)
975 return self._get_role_name(self.role)
983
976
984
977
985 class UserEmailMap(Base, BaseModel):
978 class UserEmailMap(Base, BaseModel):
986 __tablename__ = 'user_email_map'
979 __tablename__ = 'user_email_map'
987 __table_args__ = (
980 __table_args__ = (
988 Index('uem_email_idx', 'email'),
981 Index('uem_email_idx', 'email'),
989 UniqueConstraint('email'),
982 UniqueConstraint('email'),
990 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
991 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
992 )
985 )
993 __mapper_args__ = {}
986 __mapper_args__ = {}
994
987
995 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
989 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 _email = Column("email", String(255), nullable=True, unique=False, default=None)
990 _email = Column("email", String(255), nullable=True, unique=False, default=None)
998 user = relationship('User', lazy='joined')
991 user = relationship('User', lazy='joined')
999
992
1000 @validates('_email')
993 @validates('_email')
1001 def validate_email(self, key, email):
994 def validate_email(self, key, email):
1002 # check if this email is not main one
995 # check if this email is not main one
1003 main_email = Session().query(User).filter(User.email == email).scalar()
996 main_email = Session().query(User).filter(User.email == email).scalar()
1004 if main_email is not None:
997 if main_email is not None:
1005 raise AttributeError('email %s is present is user table' % email)
998 raise AttributeError('email %s is present is user table' % email)
1006 return email
999 return email
1007
1000
1008 @hybrid_property
1001 @hybrid_property
1009 def email(self):
1002 def email(self):
1010 return self._email
1003 return self._email
1011
1004
1012 @email.setter
1005 @email.setter
1013 def email(self, val):
1006 def email(self, val):
1014 self._email = val.lower() if val else None
1007 self._email = val.lower() if val else None
1015
1008
1016
1009
1017 class UserIpMap(Base, BaseModel):
1010 class UserIpMap(Base, BaseModel):
1018 __tablename__ = 'user_ip_map'
1011 __tablename__ = 'user_ip_map'
1019 __table_args__ = (
1012 __table_args__ = (
1020 UniqueConstraint('user_id', 'ip_addr'),
1013 UniqueConstraint('user_id', 'ip_addr'),
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1014 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1015 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1023 )
1016 )
1024 __mapper_args__ = {}
1017 __mapper_args__ = {}
1025
1018
1026 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1019 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1027 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1020 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1028 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1021 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1029 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1022 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1030 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1023 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1031 user = relationship('User', lazy='joined')
1024 user = relationship('User', lazy='joined')
1032
1025
1033 @classmethod
1026 @classmethod
1034 def _get_ip_range(cls, ip_addr):
1027 def _get_ip_range(cls, ip_addr):
1035 net = ipaddress.ip_network(ip_addr, strict=False)
1028 net = ipaddress.ip_network(ip_addr, strict=False)
1036 return [str(net.network_address), str(net.broadcast_address)]
1029 return [str(net.network_address), str(net.broadcast_address)]
1037
1030
1038 def __json__(self):
1031 def __json__(self):
1039 return {
1032 return {
1040 'ip_addr': self.ip_addr,
1033 'ip_addr': self.ip_addr,
1041 'ip_range': self._get_ip_range(self.ip_addr),
1034 'ip_range': self._get_ip_range(self.ip_addr),
1042 }
1035 }
1043
1036
1044 def __unicode__(self):
1037 def __unicode__(self):
1045 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1038 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1046 self.user_id, self.ip_addr)
1039 self.user_id, self.ip_addr)
1047
1040
1048 class UserLog(Base, BaseModel):
1041 class UserLog(Base, BaseModel):
1049 __tablename__ = 'user_logs'
1042 __tablename__ = 'user_logs'
1050 __table_args__ = (
1043 __table_args__ = (
1051 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1052 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1053 )
1046 )
1054 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1047 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1055 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1048 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1056 username = Column("username", String(255), nullable=True, unique=None, default=None)
1049 username = Column("username", String(255), nullable=True, unique=None, default=None)
1057 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1050 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1058 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1051 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1059 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1052 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1060 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1053 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1061 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1054 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1062
1055
1063 def __unicode__(self):
1056 def __unicode__(self):
1064 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1057 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1065 self.repository_name,
1058 self.repository_name,
1066 self.action)
1059 self.action)
1067
1060
1068 @property
1061 @property
1069 def action_as_day(self):
1062 def action_as_day(self):
1070 return datetime.date(*self.action_date.timetuple()[:3])
1063 return datetime.date(*self.action_date.timetuple()[:3])
1071
1064
1072 user = relationship('User')
1065 user = relationship('User')
1073 repository = relationship('Repository', cascade='')
1066 repository = relationship('Repository', cascade='')
1074
1067
1075
1068
1076 class UserGroup(Base, BaseModel):
1069 class UserGroup(Base, BaseModel):
1077 __tablename__ = 'users_groups'
1070 __tablename__ = 'users_groups'
1078 __table_args__ = (
1071 __table_args__ = (
1079 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1072 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1080 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1073 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1081 )
1074 )
1082
1075
1083 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1076 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1084 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1077 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1085 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1078 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1086 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1079 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1087 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1080 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1088 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1089 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1082 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1090 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1083 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1091
1084
1092 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1085 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1093 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1086 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1094 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1087 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1095 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1088 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1096 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1089 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1097 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1090 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1098
1091
1099 user = relationship('User')
1092 user = relationship('User')
1100
1093
1101 @hybrid_property
1094 @hybrid_property
1102 def group_data(self):
1095 def group_data(self):
1103 if not self._group_data:
1096 if not self._group_data:
1104 return {}
1097 return {}
1105
1098
1106 try:
1099 try:
1107 return json.loads(self._group_data)
1100 return json.loads(self._group_data)
1108 except TypeError:
1101 except TypeError:
1109 return {}
1102 return {}
1110
1103
1111 @group_data.setter
1104 @group_data.setter
1112 def group_data(self, val):
1105 def group_data(self, val):
1113 try:
1106 try:
1114 self._group_data = json.dumps(val)
1107 self._group_data = json.dumps(val)
1115 except Exception:
1108 except Exception:
1116 log.error(traceback.format_exc())
1109 log.error(traceback.format_exc())
1117
1110
1118 def __unicode__(self):
1111 def __unicode__(self):
1119 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1112 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1120 self.users_group_id,
1113 self.users_group_id,
1121 self.users_group_name)
1114 self.users_group_name)
1122
1115
1123 @classmethod
1116 @classmethod
1124 def get_by_group_name(cls, group_name, cache=False,
1117 def get_by_group_name(cls, group_name, cache=False,
1125 case_insensitive=False):
1118 case_insensitive=False):
1126 if case_insensitive:
1119 if case_insensitive:
1127 q = cls.query().filter(func.lower(cls.users_group_name) ==
1120 q = cls.query().filter(func.lower(cls.users_group_name) ==
1128 func.lower(group_name))
1121 func.lower(group_name))
1129
1122
1130 else:
1123 else:
1131 q = cls.query().filter(cls.users_group_name == group_name)
1124 q = cls.query().filter(cls.users_group_name == group_name)
1132 if cache:
1125 if cache:
1133 q = q.options(FromCache(
1126 q = q.options(FromCache(
1134 "sql_cache_short",
1127 "sql_cache_short",
1135 "get_group_%s" % _hash_key(group_name)))
1128 "get_group_%s" % _hash_key(group_name)))
1136 return q.scalar()
1129 return q.scalar()
1137
1130
1138 @classmethod
1131 @classmethod
1139 def get(cls, user_group_id, cache=False):
1132 def get(cls, user_group_id, cache=False):
1140 user_group = cls.query()
1133 user_group = cls.query()
1141 if cache:
1134 if cache:
1142 user_group = user_group.options(FromCache("sql_cache_short",
1135 user_group = user_group.options(FromCache("sql_cache_short",
1143 "get_users_group_%s" % user_group_id))
1136 "get_users_group_%s" % user_group_id))
1144 return user_group.get(user_group_id)
1137 return user_group.get(user_group_id)
1145
1138
1146 def permissions(self, with_admins=True, with_owner=True):
1139 def permissions(self, with_admins=True, with_owner=True):
1147 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1140 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1148 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1141 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1149 joinedload(UserUserGroupToPerm.user),
1142 joinedload(UserUserGroupToPerm.user),
1150 joinedload(UserUserGroupToPerm.permission),)
1143 joinedload(UserUserGroupToPerm.permission),)
1151
1144
1152 # get owners and admins and permissions. We do a trick of re-writing
1145 # get owners and admins and permissions. We do a trick of re-writing
1153 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1146 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1154 # has a global reference and changing one object propagates to all
1147 # has a global reference and changing one object propagates to all
1155 # others. This means if admin is also an owner admin_row that change
1148 # others. This means if admin is also an owner admin_row that change
1156 # would propagate to both objects
1149 # would propagate to both objects
1157 perm_rows = []
1150 perm_rows = []
1158 for _usr in q.all():
1151 for _usr in q.all():
1159 usr = AttributeDict(_usr.user.get_dict())
1152 usr = AttributeDict(_usr.user.get_dict())
1160 usr.permission = _usr.permission.permission_name
1153 usr.permission = _usr.permission.permission_name
1161 perm_rows.append(usr)
1154 perm_rows.append(usr)
1162
1155
1163 # filter the perm rows by 'default' first and then sort them by
1156 # filter the perm rows by 'default' first and then sort them by
1164 # admin,write,read,none permissions sorted again alphabetically in
1157 # admin,write,read,none permissions sorted again alphabetically in
1165 # each group
1158 # each group
1166 perm_rows = sorted(perm_rows, key=display_sort)
1159 perm_rows = sorted(perm_rows, key=display_sort)
1167
1160
1168 _admin_perm = 'usergroup.admin'
1161 _admin_perm = 'usergroup.admin'
1169 owner_row = []
1162 owner_row = []
1170 if with_owner:
1163 if with_owner:
1171 usr = AttributeDict(self.user.get_dict())
1164 usr = AttributeDict(self.user.get_dict())
1172 usr.owner_row = True
1165 usr.owner_row = True
1173 usr.permission = _admin_perm
1166 usr.permission = _admin_perm
1174 owner_row.append(usr)
1167 owner_row.append(usr)
1175
1168
1176 super_admin_rows = []
1169 super_admin_rows = []
1177 if with_admins:
1170 if with_admins:
1178 for usr in User.get_all_super_admins():
1171 for usr in User.get_all_super_admins():
1179 # if this admin is also owner, don't double the record
1172 # if this admin is also owner, don't double the record
1180 if usr.user_id == owner_row[0].user_id:
1173 if usr.user_id == owner_row[0].user_id:
1181 owner_row[0].admin_row = True
1174 owner_row[0].admin_row = True
1182 else:
1175 else:
1183 usr = AttributeDict(usr.get_dict())
1176 usr = AttributeDict(usr.get_dict())
1184 usr.admin_row = True
1177 usr.admin_row = True
1185 usr.permission = _admin_perm
1178 usr.permission = _admin_perm
1186 super_admin_rows.append(usr)
1179 super_admin_rows.append(usr)
1187
1180
1188 return super_admin_rows + owner_row + perm_rows
1181 return super_admin_rows + owner_row + perm_rows
1189
1182
1190 def permission_user_groups(self):
1183 def permission_user_groups(self):
1191 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1184 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1192 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1185 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1193 joinedload(UserGroupUserGroupToPerm.target_user_group),
1186 joinedload(UserGroupUserGroupToPerm.target_user_group),
1194 joinedload(UserGroupUserGroupToPerm.permission),)
1187 joinedload(UserGroupUserGroupToPerm.permission),)
1195
1188
1196 perm_rows = []
1189 perm_rows = []
1197 for _user_group in q.all():
1190 for _user_group in q.all():
1198 usr = AttributeDict(_user_group.user_group.get_dict())
1191 usr = AttributeDict(_user_group.user_group.get_dict())
1199 usr.permission = _user_group.permission.permission_name
1192 usr.permission = _user_group.permission.permission_name
1200 perm_rows.append(usr)
1193 perm_rows.append(usr)
1201
1194
1202 return perm_rows
1195 return perm_rows
1203
1196
1204 def _get_default_perms(self, user_group, suffix=''):
1197 def _get_default_perms(self, user_group, suffix=''):
1205 from rhodecode.model.permission import PermissionModel
1198 from rhodecode.model.permission import PermissionModel
1206 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1199 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1207
1200
1208 def get_default_perms(self, suffix=''):
1201 def get_default_perms(self, suffix=''):
1209 return self._get_default_perms(self, suffix)
1202 return self._get_default_perms(self, suffix)
1210
1203
1211 def get_api_data(self, with_group_members=True, include_secrets=False):
1204 def get_api_data(self, with_group_members=True, include_secrets=False):
1212 """
1205 """
1213 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1206 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1214 basically forwarded.
1207 basically forwarded.
1215
1208
1216 """
1209 """
1217 user_group = self
1210 user_group = self
1218
1211
1219 data = {
1212 data = {
1220 'users_group_id': user_group.users_group_id,
1213 'users_group_id': user_group.users_group_id,
1221 'group_name': user_group.users_group_name,
1214 'group_name': user_group.users_group_name,
1222 'group_description': user_group.user_group_description,
1215 'group_description': user_group.user_group_description,
1223 'active': user_group.users_group_active,
1216 'active': user_group.users_group_active,
1224 'owner': user_group.user.username,
1217 'owner': user_group.user.username,
1225 }
1218 }
1226 if with_group_members:
1219 if with_group_members:
1227 users = []
1220 users = []
1228 for user in user_group.members:
1221 for user in user_group.members:
1229 user = user.user
1222 user = user.user
1230 users.append(user.get_api_data(include_secrets=include_secrets))
1223 users.append(user.get_api_data(include_secrets=include_secrets))
1231 data['users'] = users
1224 data['users'] = users
1232
1225
1233 return data
1226 return data
1234
1227
1235
1228
1236 class UserGroupMember(Base, BaseModel):
1229 class UserGroupMember(Base, BaseModel):
1237 __tablename__ = 'users_groups_members'
1230 __tablename__ = 'users_groups_members'
1238 __table_args__ = (
1231 __table_args__ = (
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1233 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 )
1234 )
1242
1235
1243 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1236 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1244 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1237 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1245 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1238 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1246
1239
1247 user = relationship('User', lazy='joined')
1240 user = relationship('User', lazy='joined')
1248 users_group = relationship('UserGroup')
1241 users_group = relationship('UserGroup')
1249
1242
1250 def __init__(self, gr_id='', u_id=''):
1243 def __init__(self, gr_id='', u_id=''):
1251 self.users_group_id = gr_id
1244 self.users_group_id = gr_id
1252 self.user_id = u_id
1245 self.user_id = u_id
1253
1246
1254
1247
1255 class RepositoryField(Base, BaseModel):
1248 class RepositoryField(Base, BaseModel):
1256 __tablename__ = 'repositories_fields'
1249 __tablename__ = 'repositories_fields'
1257 __table_args__ = (
1250 __table_args__ = (
1258 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1251 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1259 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1260 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1253 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1261 )
1254 )
1262 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1255 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1263
1256
1264 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1265 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1258 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1266 field_key = Column("field_key", String(250))
1259 field_key = Column("field_key", String(250))
1267 field_label = Column("field_label", String(1024), nullable=False)
1260 field_label = Column("field_label", String(1024), nullable=False)
1268 field_value = Column("field_value", String(10000), nullable=False)
1261 field_value = Column("field_value", String(10000), nullable=False)
1269 field_desc = Column("field_desc", String(1024), nullable=False)
1262 field_desc = Column("field_desc", String(1024), nullable=False)
1270 field_type = Column("field_type", String(255), nullable=False, unique=None)
1263 field_type = Column("field_type", String(255), nullable=False, unique=None)
1271 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1272
1265
1273 repository = relationship('Repository')
1266 repository = relationship('Repository')
1274
1267
1275 @property
1268 @property
1276 def field_key_prefixed(self):
1269 def field_key_prefixed(self):
1277 return 'ex_%s' % self.field_key
1270 return 'ex_%s' % self.field_key
1278
1271
1279 @classmethod
1272 @classmethod
1280 def un_prefix_key(cls, key):
1273 def un_prefix_key(cls, key):
1281 if key.startswith(cls.PREFIX):
1274 if key.startswith(cls.PREFIX):
1282 return key[len(cls.PREFIX):]
1275 return key[len(cls.PREFIX):]
1283 return key
1276 return key
1284
1277
1285 @classmethod
1278 @classmethod
1286 def get_by_key_name(cls, key, repo):
1279 def get_by_key_name(cls, key, repo):
1287 row = cls.query()\
1280 row = cls.query()\
1288 .filter(cls.repository == repo)\
1281 .filter(cls.repository == repo)\
1289 .filter(cls.field_key == key).scalar()
1282 .filter(cls.field_key == key).scalar()
1290 return row
1283 return row
1291
1284
1292
1285
1293 class Repository(Base, BaseModel):
1286 class Repository(Base, BaseModel):
1294 __tablename__ = 'repositories'
1287 __tablename__ = 'repositories'
1295 __table_args__ = (
1288 __table_args__ = (
1296 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1289 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1297 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1298 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1299 )
1292 )
1300 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1293 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1301 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1294 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1302
1295
1303 STATE_CREATED = 'repo_state_created'
1296 STATE_CREATED = 'repo_state_created'
1304 STATE_PENDING = 'repo_state_pending'
1297 STATE_PENDING = 'repo_state_pending'
1305 STATE_ERROR = 'repo_state_error'
1298 STATE_ERROR = 'repo_state_error'
1306
1299
1307 LOCK_AUTOMATIC = 'lock_auto'
1300 LOCK_AUTOMATIC = 'lock_auto'
1308 LOCK_API = 'lock_api'
1301 LOCK_API = 'lock_api'
1309 LOCK_WEB = 'lock_web'
1302 LOCK_WEB = 'lock_web'
1310 LOCK_PULL = 'lock_pull'
1303 LOCK_PULL = 'lock_pull'
1311
1304
1312 NAME_SEP = URL_SEP
1305 NAME_SEP = URL_SEP
1313
1306
1314 repo_id = Column(
1307 repo_id = Column(
1315 "repo_id", Integer(), nullable=False, unique=True, default=None,
1308 "repo_id", Integer(), nullable=False, unique=True, default=None,
1316 primary_key=True)
1309 primary_key=True)
1317 _repo_name = Column(
1310 _repo_name = Column(
1318 "repo_name", Text(), nullable=False, default=None)
1311 "repo_name", Text(), nullable=False, default=None)
1319 _repo_name_hash = Column(
1312 _repo_name_hash = Column(
1320 "repo_name_hash", String(255), nullable=False, unique=True)
1313 "repo_name_hash", String(255), nullable=False, unique=True)
1321 repo_state = Column("repo_state", String(255), nullable=True)
1314 repo_state = Column("repo_state", String(255), nullable=True)
1322
1315
1323 clone_uri = Column(
1316 clone_uri = Column(
1324 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1317 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1325 default=None)
1318 default=None)
1326 repo_type = Column(
1319 repo_type = Column(
1327 "repo_type", String(255), nullable=False, unique=False, default=None)
1320 "repo_type", String(255), nullable=False, unique=False, default=None)
1328 user_id = Column(
1321 user_id = Column(
1329 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1322 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1330 unique=False, default=None)
1323 unique=False, default=None)
1331 private = Column(
1324 private = Column(
1332 "private", Boolean(), nullable=True, unique=None, default=None)
1325 "private", Boolean(), nullable=True, unique=None, default=None)
1333 enable_statistics = Column(
1326 enable_statistics = Column(
1334 "statistics", Boolean(), nullable=True, unique=None, default=True)
1327 "statistics", Boolean(), nullable=True, unique=None, default=True)
1335 enable_downloads = Column(
1328 enable_downloads = Column(
1336 "downloads", Boolean(), nullable=True, unique=None, default=True)
1329 "downloads", Boolean(), nullable=True, unique=None, default=True)
1337 description = Column(
1330 description = Column(
1338 "description", String(10000), nullable=True, unique=None, default=None)
1331 "description", String(10000), nullable=True, unique=None, default=None)
1339 created_on = Column(
1332 created_on = Column(
1340 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1333 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1341 default=datetime.datetime.now)
1334 default=datetime.datetime.now)
1342 updated_on = Column(
1335 updated_on = Column(
1343 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1336 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1344 default=datetime.datetime.now)
1337 default=datetime.datetime.now)
1345 _landing_revision = Column(
1338 _landing_revision = Column(
1346 "landing_revision", String(255), nullable=False, unique=False,
1339 "landing_revision", String(255), nullable=False, unique=False,
1347 default=None)
1340 default=None)
1348 enable_locking = Column(
1341 enable_locking = Column(
1349 "enable_locking", Boolean(), nullable=False, unique=None,
1342 "enable_locking", Boolean(), nullable=False, unique=None,
1350 default=False)
1343 default=False)
1351 _locked = Column(
1344 _locked = Column(
1352 "locked", String(255), nullable=True, unique=False, default=None)
1345 "locked", String(255), nullable=True, unique=False, default=None)
1353 _changeset_cache = Column(
1346 _changeset_cache = Column(
1354 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1347 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1355
1348
1356 fork_id = Column(
1349 fork_id = Column(
1357 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1350 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1358 nullable=True, unique=False, default=None)
1351 nullable=True, unique=False, default=None)
1359 group_id = Column(
1352 group_id = Column(
1360 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1353 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1361 unique=False, default=None)
1354 unique=False, default=None)
1362
1355
1363 user = relationship('User', lazy='joined')
1356 user = relationship('User', lazy='joined')
1364 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1357 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1365 group = relationship('RepoGroup', lazy='joined')
1358 group = relationship('RepoGroup', lazy='joined')
1366 repo_to_perm = relationship(
1359 repo_to_perm = relationship(
1367 'UserRepoToPerm', cascade='all',
1360 'UserRepoToPerm', cascade='all',
1368 order_by='UserRepoToPerm.repo_to_perm_id')
1361 order_by='UserRepoToPerm.repo_to_perm_id')
1369 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1362 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1370 stats = relationship('Statistics', cascade='all', uselist=False)
1363 stats = relationship('Statistics', cascade='all', uselist=False)
1371
1364
1372 followers = relationship(
1365 followers = relationship(
1373 'UserFollowing',
1366 'UserFollowing',
1374 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1367 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1375 cascade='all')
1368 cascade='all')
1376 extra_fields = relationship(
1369 extra_fields = relationship(
1377 'RepositoryField', cascade="all, delete, delete-orphan")
1370 'RepositoryField', cascade="all, delete, delete-orphan")
1378 logs = relationship('UserLog')
1371 logs = relationship('UserLog')
1379 comments = relationship(
1372 comments = relationship(
1380 'ChangesetComment', cascade="all, delete, delete-orphan")
1373 'ChangesetComment', cascade="all, delete, delete-orphan")
1381 pull_requests_source = relationship(
1374 pull_requests_source = relationship(
1382 'PullRequest',
1375 'PullRequest',
1383 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1376 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1384 cascade="all, delete, delete-orphan")
1377 cascade="all, delete, delete-orphan")
1385 pull_requests_target = relationship(
1378 pull_requests_target = relationship(
1386 'PullRequest',
1379 'PullRequest',
1387 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1380 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1388 cascade="all, delete, delete-orphan")
1381 cascade="all, delete, delete-orphan")
1389 ui = relationship('RepoRhodeCodeUi', cascade="all")
1382 ui = relationship('RepoRhodeCodeUi', cascade="all")
1390 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1383 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1391 integrations = relationship('Integration',
1384 integrations = relationship('Integration',
1392 cascade="all, delete, delete-orphan")
1385 cascade="all, delete, delete-orphan")
1393
1386
1394 def __unicode__(self):
1387 def __unicode__(self):
1395 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1388 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1396 safe_unicode(self.repo_name))
1389 safe_unicode(self.repo_name))
1397
1390
1398 @hybrid_property
1391 @hybrid_property
1399 def landing_rev(self):
1392 def landing_rev(self):
1400 # always should return [rev_type, rev]
1393 # always should return [rev_type, rev]
1401 if self._landing_revision:
1394 if self._landing_revision:
1402 _rev_info = self._landing_revision.split(':')
1395 _rev_info = self._landing_revision.split(':')
1403 if len(_rev_info) < 2:
1396 if len(_rev_info) < 2:
1404 _rev_info.insert(0, 'rev')
1397 _rev_info.insert(0, 'rev')
1405 return [_rev_info[0], _rev_info[1]]
1398 return [_rev_info[0], _rev_info[1]]
1406 return [None, None]
1399 return [None, None]
1407
1400
1408 @landing_rev.setter
1401 @landing_rev.setter
1409 def landing_rev(self, val):
1402 def landing_rev(self, val):
1410 if ':' not in val:
1403 if ':' not in val:
1411 raise ValueError('value must be delimited with `:` and consist '
1404 raise ValueError('value must be delimited with `:` and consist '
1412 'of <rev_type>:<rev>, got %s instead' % val)
1405 'of <rev_type>:<rev>, got %s instead' % val)
1413 self._landing_revision = val
1406 self._landing_revision = val
1414
1407
1415 @hybrid_property
1408 @hybrid_property
1416 def locked(self):
1409 def locked(self):
1417 if self._locked:
1410 if self._locked:
1418 user_id, timelocked, reason = self._locked.split(':')
1411 user_id, timelocked, reason = self._locked.split(':')
1419 lock_values = int(user_id), timelocked, reason
1412 lock_values = int(user_id), timelocked, reason
1420 else:
1413 else:
1421 lock_values = [None, None, None]
1414 lock_values = [None, None, None]
1422 return lock_values
1415 return lock_values
1423
1416
1424 @locked.setter
1417 @locked.setter
1425 def locked(self, val):
1418 def locked(self, val):
1426 if val and isinstance(val, (list, tuple)):
1419 if val and isinstance(val, (list, tuple)):
1427 self._locked = ':'.join(map(str, val))
1420 self._locked = ':'.join(map(str, val))
1428 else:
1421 else:
1429 self._locked = None
1422 self._locked = None
1430
1423
1431 @hybrid_property
1424 @hybrid_property
1432 def changeset_cache(self):
1425 def changeset_cache(self):
1433 from rhodecode.lib.vcs.backends.base import EmptyCommit
1426 from rhodecode.lib.vcs.backends.base import EmptyCommit
1434 dummy = EmptyCommit().__json__()
1427 dummy = EmptyCommit().__json__()
1435 if not self._changeset_cache:
1428 if not self._changeset_cache:
1436 return dummy
1429 return dummy
1437 try:
1430 try:
1438 return json.loads(self._changeset_cache)
1431 return json.loads(self._changeset_cache)
1439 except TypeError:
1432 except TypeError:
1440 return dummy
1433 return dummy
1441 except Exception:
1434 except Exception:
1442 log.error(traceback.format_exc())
1435 log.error(traceback.format_exc())
1443 return dummy
1436 return dummy
1444
1437
1445 @changeset_cache.setter
1438 @changeset_cache.setter
1446 def changeset_cache(self, val):
1439 def changeset_cache(self, val):
1447 try:
1440 try:
1448 self._changeset_cache = json.dumps(val)
1441 self._changeset_cache = json.dumps(val)
1449 except Exception:
1442 except Exception:
1450 log.error(traceback.format_exc())
1443 log.error(traceback.format_exc())
1451
1444
1452 @hybrid_property
1445 @hybrid_property
1453 def repo_name(self):
1446 def repo_name(self):
1454 return self._repo_name
1447 return self._repo_name
1455
1448
1456 @repo_name.setter
1449 @repo_name.setter
1457 def repo_name(self, value):
1450 def repo_name(self, value):
1458 self._repo_name = value
1451 self._repo_name = value
1459 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1452 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1460
1453
1461 @classmethod
1454 @classmethod
1462 def normalize_repo_name(cls, repo_name):
1455 def normalize_repo_name(cls, repo_name):
1463 """
1456 """
1464 Normalizes os specific repo_name to the format internally stored inside
1457 Normalizes os specific repo_name to the format internally stored inside
1465 database using URL_SEP
1458 database using URL_SEP
1466
1459
1467 :param cls:
1460 :param cls:
1468 :param repo_name:
1461 :param repo_name:
1469 """
1462 """
1470 return cls.NAME_SEP.join(repo_name.split(os.sep))
1463 return cls.NAME_SEP.join(repo_name.split(os.sep))
1471
1464
1472 @classmethod
1465 @classmethod
1473 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1466 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1474 session = Session()
1467 session = Session()
1475 q = session.query(cls).filter(cls.repo_name == repo_name)
1468 q = session.query(cls).filter(cls.repo_name == repo_name)
1476
1469
1477 if cache:
1470 if cache:
1478 if identity_cache:
1471 if identity_cache:
1479 val = cls.identity_cache(session, 'repo_name', repo_name)
1472 val = cls.identity_cache(session, 'repo_name', repo_name)
1480 if val:
1473 if val:
1481 return val
1474 return val
1482 else:
1475 else:
1483 q = q.options(
1476 q = q.options(
1484 FromCache("sql_cache_short",
1477 FromCache("sql_cache_short",
1485 "get_repo_by_name_%s" % _hash_key(repo_name)))
1478 "get_repo_by_name_%s" % _hash_key(repo_name)))
1486
1479
1487 return q.scalar()
1480 return q.scalar()
1488
1481
1489 @classmethod
1482 @classmethod
1490 def get_by_full_path(cls, repo_full_path):
1483 def get_by_full_path(cls, repo_full_path):
1491 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1484 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1492 repo_name = cls.normalize_repo_name(repo_name)
1485 repo_name = cls.normalize_repo_name(repo_name)
1493 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1486 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1494
1487
1495 @classmethod
1488 @classmethod
1496 def get_repo_forks(cls, repo_id):
1489 def get_repo_forks(cls, repo_id):
1497 return cls.query().filter(Repository.fork_id == repo_id)
1490 return cls.query().filter(Repository.fork_id == repo_id)
1498
1491
1499 @classmethod
1492 @classmethod
1500 def base_path(cls):
1493 def base_path(cls):
1501 """
1494 """
1502 Returns base path when all repos are stored
1495 Returns base path when all repos are stored
1503
1496
1504 :param cls:
1497 :param cls:
1505 """
1498 """
1506 q = Session().query(RhodeCodeUi)\
1499 q = Session().query(RhodeCodeUi)\
1507 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1500 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1508 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1501 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1509 return q.one().ui_value
1502 return q.one().ui_value
1510
1503
1511 @classmethod
1504 @classmethod
1512 def is_valid(cls, repo_name):
1505 def is_valid(cls, repo_name):
1513 """
1506 """
1514 returns True if given repo name is a valid filesystem repository
1507 returns True if given repo name is a valid filesystem repository
1515
1508
1516 :param cls:
1509 :param cls:
1517 :param repo_name:
1510 :param repo_name:
1518 """
1511 """
1519 from rhodecode.lib.utils import is_valid_repo
1512 from rhodecode.lib.utils import is_valid_repo
1520
1513
1521 return is_valid_repo(repo_name, cls.base_path())
1514 return is_valid_repo(repo_name, cls.base_path())
1522
1515
1523 @classmethod
1516 @classmethod
1524 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1517 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1525 case_insensitive=True):
1518 case_insensitive=True):
1526 q = Repository.query()
1519 q = Repository.query()
1527
1520
1528 if not isinstance(user_id, Optional):
1521 if not isinstance(user_id, Optional):
1529 q = q.filter(Repository.user_id == user_id)
1522 q = q.filter(Repository.user_id == user_id)
1530
1523
1531 if not isinstance(group_id, Optional):
1524 if not isinstance(group_id, Optional):
1532 q = q.filter(Repository.group_id == group_id)
1525 q = q.filter(Repository.group_id == group_id)
1533
1526
1534 if case_insensitive:
1527 if case_insensitive:
1535 q = q.order_by(func.lower(Repository.repo_name))
1528 q = q.order_by(func.lower(Repository.repo_name))
1536 else:
1529 else:
1537 q = q.order_by(Repository.repo_name)
1530 q = q.order_by(Repository.repo_name)
1538 return q.all()
1531 return q.all()
1539
1532
1540 @property
1533 @property
1541 def forks(self):
1534 def forks(self):
1542 """
1535 """
1543 Return forks of this repo
1536 Return forks of this repo
1544 """
1537 """
1545 return Repository.get_repo_forks(self.repo_id)
1538 return Repository.get_repo_forks(self.repo_id)
1546
1539
1547 @property
1540 @property
1548 def parent(self):
1541 def parent(self):
1549 """
1542 """
1550 Returns fork parent
1543 Returns fork parent
1551 """
1544 """
1552 return self.fork
1545 return self.fork
1553
1546
1554 @property
1547 @property
1555 def just_name(self):
1548 def just_name(self):
1556 return self.repo_name.split(self.NAME_SEP)[-1]
1549 return self.repo_name.split(self.NAME_SEP)[-1]
1557
1550
1558 @property
1551 @property
1559 def groups_with_parents(self):
1552 def groups_with_parents(self):
1560 groups = []
1553 groups = []
1561 if self.group is None:
1554 if self.group is None:
1562 return groups
1555 return groups
1563
1556
1564 cur_gr = self.group
1557 cur_gr = self.group
1565 groups.insert(0, cur_gr)
1558 groups.insert(0, cur_gr)
1566 while 1:
1559 while 1:
1567 gr = getattr(cur_gr, 'parent_group', None)
1560 gr = getattr(cur_gr, 'parent_group', None)
1568 cur_gr = cur_gr.parent_group
1561 cur_gr = cur_gr.parent_group
1569 if gr is None:
1562 if gr is None:
1570 break
1563 break
1571 groups.insert(0, gr)
1564 groups.insert(0, gr)
1572
1565
1573 return groups
1566 return groups
1574
1567
1575 @property
1568 @property
1576 def groups_and_repo(self):
1569 def groups_and_repo(self):
1577 return self.groups_with_parents, self
1570 return self.groups_with_parents, self
1578
1571
1579 @LazyProperty
1572 @LazyProperty
1580 def repo_path(self):
1573 def repo_path(self):
1581 """
1574 """
1582 Returns base full path for that repository means where it actually
1575 Returns base full path for that repository means where it actually
1583 exists on a filesystem
1576 exists on a filesystem
1584 """
1577 """
1585 q = Session().query(RhodeCodeUi).filter(
1578 q = Session().query(RhodeCodeUi).filter(
1586 RhodeCodeUi.ui_key == self.NAME_SEP)
1579 RhodeCodeUi.ui_key == self.NAME_SEP)
1587 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1580 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1588 return q.one().ui_value
1581 return q.one().ui_value
1589
1582
1590 @property
1583 @property
1591 def repo_full_path(self):
1584 def repo_full_path(self):
1592 p = [self.repo_path]
1585 p = [self.repo_path]
1593 # we need to split the name by / since this is how we store the
1586 # we need to split the name by / since this is how we store the
1594 # names in the database, but that eventually needs to be converted
1587 # names in the database, but that eventually needs to be converted
1595 # into a valid system path
1588 # into a valid system path
1596 p += self.repo_name.split(self.NAME_SEP)
1589 p += self.repo_name.split(self.NAME_SEP)
1597 return os.path.join(*map(safe_unicode, p))
1590 return os.path.join(*map(safe_unicode, p))
1598
1591
1599 @property
1592 @property
1600 def cache_keys(self):
1593 def cache_keys(self):
1601 """
1594 """
1602 Returns associated cache keys for that repo
1595 Returns associated cache keys for that repo
1603 """
1596 """
1604 return CacheKey.query()\
1597 return CacheKey.query()\
1605 .filter(CacheKey.cache_args == self.repo_name)\
1598 .filter(CacheKey.cache_args == self.repo_name)\
1606 .order_by(CacheKey.cache_key)\
1599 .order_by(CacheKey.cache_key)\
1607 .all()
1600 .all()
1608
1601
1609 def get_new_name(self, repo_name):
1602 def get_new_name(self, repo_name):
1610 """
1603 """
1611 returns new full repository name based on assigned group and new new
1604 returns new full repository name based on assigned group and new new
1612
1605
1613 :param group_name:
1606 :param group_name:
1614 """
1607 """
1615 path_prefix = self.group.full_path_splitted if self.group else []
1608 path_prefix = self.group.full_path_splitted if self.group else []
1616 return self.NAME_SEP.join(path_prefix + [repo_name])
1609 return self.NAME_SEP.join(path_prefix + [repo_name])
1617
1610
1618 @property
1611 @property
1619 def _config(self):
1612 def _config(self):
1620 """
1613 """
1621 Returns db based config object.
1614 Returns db based config object.
1622 """
1615 """
1623 from rhodecode.lib.utils import make_db_config
1616 from rhodecode.lib.utils import make_db_config
1624 return make_db_config(clear_session=False, repo=self)
1617 return make_db_config(clear_session=False, repo=self)
1625
1618
1626 def permissions(self, with_admins=True, with_owner=True):
1619 def permissions(self, with_admins=True, with_owner=True):
1627 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1620 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1628 q = q.options(joinedload(UserRepoToPerm.repository),
1621 q = q.options(joinedload(UserRepoToPerm.repository),
1629 joinedload(UserRepoToPerm.user),
1622 joinedload(UserRepoToPerm.user),
1630 joinedload(UserRepoToPerm.permission),)
1623 joinedload(UserRepoToPerm.permission),)
1631
1624
1632 # get owners and admins and permissions. We do a trick of re-writing
1625 # get owners and admins and permissions. We do a trick of re-writing
1633 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1626 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1634 # has a global reference and changing one object propagates to all
1627 # has a global reference and changing one object propagates to all
1635 # others. This means if admin is also an owner admin_row that change
1628 # others. This means if admin is also an owner admin_row that change
1636 # would propagate to both objects
1629 # would propagate to both objects
1637 perm_rows = []
1630 perm_rows = []
1638 for _usr in q.all():
1631 for _usr in q.all():
1639 usr = AttributeDict(_usr.user.get_dict())
1632 usr = AttributeDict(_usr.user.get_dict())
1640 usr.permission = _usr.permission.permission_name
1633 usr.permission = _usr.permission.permission_name
1641 perm_rows.append(usr)
1634 perm_rows.append(usr)
1642
1635
1643 # filter the perm rows by 'default' first and then sort them by
1636 # filter the perm rows by 'default' first and then sort them by
1644 # admin,write,read,none permissions sorted again alphabetically in
1637 # admin,write,read,none permissions sorted again alphabetically in
1645 # each group
1638 # each group
1646 perm_rows = sorted(perm_rows, key=display_sort)
1639 perm_rows = sorted(perm_rows, key=display_sort)
1647
1640
1648 _admin_perm = 'repository.admin'
1641 _admin_perm = 'repository.admin'
1649 owner_row = []
1642 owner_row = []
1650 if with_owner:
1643 if with_owner:
1651 usr = AttributeDict(self.user.get_dict())
1644 usr = AttributeDict(self.user.get_dict())
1652 usr.owner_row = True
1645 usr.owner_row = True
1653 usr.permission = _admin_perm
1646 usr.permission = _admin_perm
1654 owner_row.append(usr)
1647 owner_row.append(usr)
1655
1648
1656 super_admin_rows = []
1649 super_admin_rows = []
1657 if with_admins:
1650 if with_admins:
1658 for usr in User.get_all_super_admins():
1651 for usr in User.get_all_super_admins():
1659 # if this admin is also owner, don't double the record
1652 # if this admin is also owner, don't double the record
1660 if usr.user_id == owner_row[0].user_id:
1653 if usr.user_id == owner_row[0].user_id:
1661 owner_row[0].admin_row = True
1654 owner_row[0].admin_row = True
1662 else:
1655 else:
1663 usr = AttributeDict(usr.get_dict())
1656 usr = AttributeDict(usr.get_dict())
1664 usr.admin_row = True
1657 usr.admin_row = True
1665 usr.permission = _admin_perm
1658 usr.permission = _admin_perm
1666 super_admin_rows.append(usr)
1659 super_admin_rows.append(usr)
1667
1660
1668 return super_admin_rows + owner_row + perm_rows
1661 return super_admin_rows + owner_row + perm_rows
1669
1662
1670 def permission_user_groups(self):
1663 def permission_user_groups(self):
1671 q = UserGroupRepoToPerm.query().filter(
1664 q = UserGroupRepoToPerm.query().filter(
1672 UserGroupRepoToPerm.repository == self)
1665 UserGroupRepoToPerm.repository == self)
1673 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1666 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1674 joinedload(UserGroupRepoToPerm.users_group),
1667 joinedload(UserGroupRepoToPerm.users_group),
1675 joinedload(UserGroupRepoToPerm.permission),)
1668 joinedload(UserGroupRepoToPerm.permission),)
1676
1669
1677 perm_rows = []
1670 perm_rows = []
1678 for _user_group in q.all():
1671 for _user_group in q.all():
1679 usr = AttributeDict(_user_group.users_group.get_dict())
1672 usr = AttributeDict(_user_group.users_group.get_dict())
1680 usr.permission = _user_group.permission.permission_name
1673 usr.permission = _user_group.permission.permission_name
1681 perm_rows.append(usr)
1674 perm_rows.append(usr)
1682
1675
1683 return perm_rows
1676 return perm_rows
1684
1677
1685 def get_api_data(self, include_secrets=False):
1678 def get_api_data(self, include_secrets=False):
1686 """
1679 """
1687 Common function for generating repo api data
1680 Common function for generating repo api data
1688
1681
1689 :param include_secrets: See :meth:`User.get_api_data`.
1682 :param include_secrets: See :meth:`User.get_api_data`.
1690
1683
1691 """
1684 """
1692 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1685 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1693 # move this methods on models level.
1686 # move this methods on models level.
1694 from rhodecode.model.settings import SettingsModel
1687 from rhodecode.model.settings import SettingsModel
1695
1688
1696 repo = self
1689 repo = self
1697 _user_id, _time, _reason = self.locked
1690 _user_id, _time, _reason = self.locked
1698
1691
1699 data = {
1692 data = {
1700 'repo_id': repo.repo_id,
1693 'repo_id': repo.repo_id,
1701 'repo_name': repo.repo_name,
1694 'repo_name': repo.repo_name,
1702 'repo_type': repo.repo_type,
1695 'repo_type': repo.repo_type,
1703 'clone_uri': repo.clone_uri or '',
1696 'clone_uri': repo.clone_uri or '',
1704 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1697 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1705 'private': repo.private,
1698 'private': repo.private,
1706 'created_on': repo.created_on,
1699 'created_on': repo.created_on,
1707 'description': repo.description,
1700 'description': repo.description,
1708 'landing_rev': repo.landing_rev,
1701 'landing_rev': repo.landing_rev,
1709 'owner': repo.user.username,
1702 'owner': repo.user.username,
1710 'fork_of': repo.fork.repo_name if repo.fork else None,
1703 'fork_of': repo.fork.repo_name if repo.fork else None,
1711 'enable_statistics': repo.enable_statistics,
1704 'enable_statistics': repo.enable_statistics,
1712 'enable_locking': repo.enable_locking,
1705 'enable_locking': repo.enable_locking,
1713 'enable_downloads': repo.enable_downloads,
1706 'enable_downloads': repo.enable_downloads,
1714 'last_changeset': repo.changeset_cache,
1707 'last_changeset': repo.changeset_cache,
1715 'locked_by': User.get(_user_id).get_api_data(
1708 'locked_by': User.get(_user_id).get_api_data(
1716 include_secrets=include_secrets) if _user_id else None,
1709 include_secrets=include_secrets) if _user_id else None,
1717 'locked_date': time_to_datetime(_time) if _time else None,
1710 'locked_date': time_to_datetime(_time) if _time else None,
1718 'lock_reason': _reason if _reason else None,
1711 'lock_reason': _reason if _reason else None,
1719 }
1712 }
1720
1713
1721 # TODO: mikhail: should be per-repo settings here
1714 # TODO: mikhail: should be per-repo settings here
1722 rc_config = SettingsModel().get_all_settings()
1715 rc_config = SettingsModel().get_all_settings()
1723 repository_fields = str2bool(
1716 repository_fields = str2bool(
1724 rc_config.get('rhodecode_repository_fields'))
1717 rc_config.get('rhodecode_repository_fields'))
1725 if repository_fields:
1718 if repository_fields:
1726 for f in self.extra_fields:
1719 for f in self.extra_fields:
1727 data[f.field_key_prefixed] = f.field_value
1720 data[f.field_key_prefixed] = f.field_value
1728
1721
1729 return data
1722 return data
1730
1723
1731 @classmethod
1724 @classmethod
1732 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1725 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1733 if not lock_time:
1726 if not lock_time:
1734 lock_time = time.time()
1727 lock_time = time.time()
1735 if not lock_reason:
1728 if not lock_reason:
1736 lock_reason = cls.LOCK_AUTOMATIC
1729 lock_reason = cls.LOCK_AUTOMATIC
1737 repo.locked = [user_id, lock_time, lock_reason]
1730 repo.locked = [user_id, lock_time, lock_reason]
1738 Session().add(repo)
1731 Session().add(repo)
1739 Session().commit()
1732 Session().commit()
1740
1733
1741 @classmethod
1734 @classmethod
1742 def unlock(cls, repo):
1735 def unlock(cls, repo):
1743 repo.locked = None
1736 repo.locked = None
1744 Session().add(repo)
1737 Session().add(repo)
1745 Session().commit()
1738 Session().commit()
1746
1739
1747 @classmethod
1740 @classmethod
1748 def getlock(cls, repo):
1741 def getlock(cls, repo):
1749 return repo.locked
1742 return repo.locked
1750
1743
1751 def is_user_lock(self, user_id):
1744 def is_user_lock(self, user_id):
1752 if self.lock[0]:
1745 if self.lock[0]:
1753 lock_user_id = safe_int(self.lock[0])
1746 lock_user_id = safe_int(self.lock[0])
1754 user_id = safe_int(user_id)
1747 user_id = safe_int(user_id)
1755 # both are ints, and they are equal
1748 # both are ints, and they are equal
1756 return all([lock_user_id, user_id]) and lock_user_id == user_id
1749 return all([lock_user_id, user_id]) and lock_user_id == user_id
1757
1750
1758 return False
1751 return False
1759
1752
1760 def get_locking_state(self, action, user_id, only_when_enabled=True):
1753 def get_locking_state(self, action, user_id, only_when_enabled=True):
1761 """
1754 """
1762 Checks locking on this repository, if locking is enabled and lock is
1755 Checks locking on this repository, if locking is enabled and lock is
1763 present returns a tuple of make_lock, locked, locked_by.
1756 present returns a tuple of make_lock, locked, locked_by.
1764 make_lock can have 3 states None (do nothing) True, make lock
1757 make_lock can have 3 states None (do nothing) True, make lock
1765 False release lock, This value is later propagated to hooks, which
1758 False release lock, This value is later propagated to hooks, which
1766 do the locking. Think about this as signals passed to hooks what to do.
1759 do the locking. Think about this as signals passed to hooks what to do.
1767
1760
1768 """
1761 """
1769 # TODO: johbo: This is part of the business logic and should be moved
1762 # TODO: johbo: This is part of the business logic and should be moved
1770 # into the RepositoryModel.
1763 # into the RepositoryModel.
1771
1764
1772 if action not in ('push', 'pull'):
1765 if action not in ('push', 'pull'):
1773 raise ValueError("Invalid action value: %s" % repr(action))
1766 raise ValueError("Invalid action value: %s" % repr(action))
1774
1767
1775 # defines if locked error should be thrown to user
1768 # defines if locked error should be thrown to user
1776 currently_locked = False
1769 currently_locked = False
1777 # defines if new lock should be made, tri-state
1770 # defines if new lock should be made, tri-state
1778 make_lock = None
1771 make_lock = None
1779 repo = self
1772 repo = self
1780 user = User.get(user_id)
1773 user = User.get(user_id)
1781
1774
1782 lock_info = repo.locked
1775 lock_info = repo.locked
1783
1776
1784 if repo and (repo.enable_locking or not only_when_enabled):
1777 if repo and (repo.enable_locking or not only_when_enabled):
1785 if action == 'push':
1778 if action == 'push':
1786 # check if it's already locked !, if it is compare users
1779 # check if it's already locked !, if it is compare users
1787 locked_by_user_id = lock_info[0]
1780 locked_by_user_id = lock_info[0]
1788 if user.user_id == locked_by_user_id:
1781 if user.user_id == locked_by_user_id:
1789 log.debug(
1782 log.debug(
1790 'Got `push` action from user %s, now unlocking', user)
1783 'Got `push` action from user %s, now unlocking', user)
1791 # unlock if we have push from user who locked
1784 # unlock if we have push from user who locked
1792 make_lock = False
1785 make_lock = False
1793 else:
1786 else:
1794 # we're not the same user who locked, ban with
1787 # we're not the same user who locked, ban with
1795 # code defined in settings (default is 423 HTTP Locked) !
1788 # code defined in settings (default is 423 HTTP Locked) !
1796 log.debug('Repo %s is currently locked by %s', repo, user)
1789 log.debug('Repo %s is currently locked by %s', repo, user)
1797 currently_locked = True
1790 currently_locked = True
1798 elif action == 'pull':
1791 elif action == 'pull':
1799 # [0] user [1] date
1792 # [0] user [1] date
1800 if lock_info[0] and lock_info[1]:
1793 if lock_info[0] and lock_info[1]:
1801 log.debug('Repo %s is currently locked by %s', repo, user)
1794 log.debug('Repo %s is currently locked by %s', repo, user)
1802 currently_locked = True
1795 currently_locked = True
1803 else:
1796 else:
1804 log.debug('Setting lock on repo %s by %s', repo, user)
1797 log.debug('Setting lock on repo %s by %s', repo, user)
1805 make_lock = True
1798 make_lock = True
1806
1799
1807 else:
1800 else:
1808 log.debug('Repository %s do not have locking enabled', repo)
1801 log.debug('Repository %s do not have locking enabled', repo)
1809
1802
1810 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1803 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1811 make_lock, currently_locked, lock_info)
1804 make_lock, currently_locked, lock_info)
1812
1805
1813 from rhodecode.lib.auth import HasRepoPermissionAny
1806 from rhodecode.lib.auth import HasRepoPermissionAny
1814 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1807 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1815 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1808 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1816 # if we don't have at least write permission we cannot make a lock
1809 # if we don't have at least write permission we cannot make a lock
1817 log.debug('lock state reset back to FALSE due to lack '
1810 log.debug('lock state reset back to FALSE due to lack '
1818 'of at least read permission')
1811 'of at least read permission')
1819 make_lock = False
1812 make_lock = False
1820
1813
1821 return make_lock, currently_locked, lock_info
1814 return make_lock, currently_locked, lock_info
1822
1815
1823 @property
1816 @property
1824 def last_db_change(self):
1817 def last_db_change(self):
1825 return self.updated_on
1818 return self.updated_on
1826
1819
1827 @property
1820 @property
1828 def clone_uri_hidden(self):
1821 def clone_uri_hidden(self):
1829 clone_uri = self.clone_uri
1822 clone_uri = self.clone_uri
1830 if clone_uri:
1823 if clone_uri:
1831 import urlobject
1824 import urlobject
1832 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1825 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1833 if url_obj.password:
1826 if url_obj.password:
1834 clone_uri = url_obj.with_password('*****')
1827 clone_uri = url_obj.with_password('*****')
1835 return clone_uri
1828 return clone_uri
1836
1829
1837 def clone_url(self, **override):
1830 def clone_url(self, **override):
1838 qualified_home_url = url('home', qualified=True)
1831 qualified_home_url = url('home', qualified=True)
1839
1832
1840 uri_tmpl = None
1833 uri_tmpl = None
1841 if 'with_id' in override:
1834 if 'with_id' in override:
1842 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1835 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1843 del override['with_id']
1836 del override['with_id']
1844
1837
1845 if 'uri_tmpl' in override:
1838 if 'uri_tmpl' in override:
1846 uri_tmpl = override['uri_tmpl']
1839 uri_tmpl = override['uri_tmpl']
1847 del override['uri_tmpl']
1840 del override['uri_tmpl']
1848
1841
1849 # we didn't override our tmpl from **overrides
1842 # we didn't override our tmpl from **overrides
1850 if not uri_tmpl:
1843 if not uri_tmpl:
1851 uri_tmpl = self.DEFAULT_CLONE_URI
1844 uri_tmpl = self.DEFAULT_CLONE_URI
1852 try:
1845 try:
1853 from pylons import tmpl_context as c
1846 from pylons import tmpl_context as c
1854 uri_tmpl = c.clone_uri_tmpl
1847 uri_tmpl = c.clone_uri_tmpl
1855 except Exception:
1848 except Exception:
1856 # in any case if we call this outside of request context,
1849 # in any case if we call this outside of request context,
1857 # ie, not having tmpl_context set up
1850 # ie, not having tmpl_context set up
1858 pass
1851 pass
1859
1852
1860 return get_clone_url(uri_tmpl=uri_tmpl,
1853 return get_clone_url(uri_tmpl=uri_tmpl,
1861 qualifed_home_url=qualified_home_url,
1854 qualifed_home_url=qualified_home_url,
1862 repo_name=self.repo_name,
1855 repo_name=self.repo_name,
1863 repo_id=self.repo_id, **override)
1856 repo_id=self.repo_id, **override)
1864
1857
1865 def set_state(self, state):
1858 def set_state(self, state):
1866 self.repo_state = state
1859 self.repo_state = state
1867 Session().add(self)
1860 Session().add(self)
1868 #==========================================================================
1861 #==========================================================================
1869 # SCM PROPERTIES
1862 # SCM PROPERTIES
1870 #==========================================================================
1863 #==========================================================================
1871
1864
1872 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1865 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1873 return get_commit_safe(
1866 return get_commit_safe(
1874 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1867 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1875
1868
1876 def get_changeset(self, rev=None, pre_load=None):
1869 def get_changeset(self, rev=None, pre_load=None):
1877 warnings.warn("Use get_commit", DeprecationWarning)
1870 warnings.warn("Use get_commit", DeprecationWarning)
1878 commit_id = None
1871 commit_id = None
1879 commit_idx = None
1872 commit_idx = None
1880 if isinstance(rev, basestring):
1873 if isinstance(rev, basestring):
1881 commit_id = rev
1874 commit_id = rev
1882 else:
1875 else:
1883 commit_idx = rev
1876 commit_idx = rev
1884 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1877 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1885 pre_load=pre_load)
1878 pre_load=pre_load)
1886
1879
1887 def get_landing_commit(self):
1880 def get_landing_commit(self):
1888 """
1881 """
1889 Returns landing commit, or if that doesn't exist returns the tip
1882 Returns landing commit, or if that doesn't exist returns the tip
1890 """
1883 """
1891 _rev_type, _rev = self.landing_rev
1884 _rev_type, _rev = self.landing_rev
1892 commit = self.get_commit(_rev)
1885 commit = self.get_commit(_rev)
1893 if isinstance(commit, EmptyCommit):
1886 if isinstance(commit, EmptyCommit):
1894 return self.get_commit()
1887 return self.get_commit()
1895 return commit
1888 return commit
1896
1889
1897 def update_commit_cache(self, cs_cache=None, config=None):
1890 def update_commit_cache(self, cs_cache=None, config=None):
1898 """
1891 """
1899 Update cache of last changeset for repository, keys should be::
1892 Update cache of last changeset for repository, keys should be::
1900
1893
1901 short_id
1894 short_id
1902 raw_id
1895 raw_id
1903 revision
1896 revision
1904 parents
1897 parents
1905 message
1898 message
1906 date
1899 date
1907 author
1900 author
1908
1901
1909 :param cs_cache:
1902 :param cs_cache:
1910 """
1903 """
1911 from rhodecode.lib.vcs.backends.base import BaseChangeset
1904 from rhodecode.lib.vcs.backends.base import BaseChangeset
1912 if cs_cache is None:
1905 if cs_cache is None:
1913 # use no-cache version here
1906 # use no-cache version here
1914 scm_repo = self.scm_instance(cache=False, config=config)
1907 scm_repo = self.scm_instance(cache=False, config=config)
1915 if scm_repo:
1908 if scm_repo:
1916 cs_cache = scm_repo.get_commit(
1909 cs_cache = scm_repo.get_commit(
1917 pre_load=["author", "date", "message", "parents"])
1910 pre_load=["author", "date", "message", "parents"])
1918 else:
1911 else:
1919 cs_cache = EmptyCommit()
1912 cs_cache = EmptyCommit()
1920
1913
1921 if isinstance(cs_cache, BaseChangeset):
1914 if isinstance(cs_cache, BaseChangeset):
1922 cs_cache = cs_cache.__json__()
1915 cs_cache = cs_cache.__json__()
1923
1916
1924 def is_outdated(new_cs_cache):
1917 def is_outdated(new_cs_cache):
1925 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1918 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1926 new_cs_cache['revision'] != self.changeset_cache['revision']):
1919 new_cs_cache['revision'] != self.changeset_cache['revision']):
1927 return True
1920 return True
1928 return False
1921 return False
1929
1922
1930 # check if we have maybe already latest cached revision
1923 # check if we have maybe already latest cached revision
1931 if is_outdated(cs_cache) or not self.changeset_cache:
1924 if is_outdated(cs_cache) or not self.changeset_cache:
1932 _default = datetime.datetime.fromtimestamp(0)
1925 _default = datetime.datetime.fromtimestamp(0)
1933 last_change = cs_cache.get('date') or _default
1926 last_change = cs_cache.get('date') or _default
1934 log.debug('updated repo %s with new cs cache %s',
1927 log.debug('updated repo %s with new cs cache %s',
1935 self.repo_name, cs_cache)
1928 self.repo_name, cs_cache)
1936 self.updated_on = last_change
1929 self.updated_on = last_change
1937 self.changeset_cache = cs_cache
1930 self.changeset_cache = cs_cache
1938 Session().add(self)
1931 Session().add(self)
1939 Session().commit()
1932 Session().commit()
1940 else:
1933 else:
1941 log.debug('Skipping update_commit_cache for repo:`%s` '
1934 log.debug('Skipping update_commit_cache for repo:`%s` '
1942 'commit already with latest changes', self.repo_name)
1935 'commit already with latest changes', self.repo_name)
1943
1936
1944 @property
1937 @property
1945 def tip(self):
1938 def tip(self):
1946 return self.get_commit('tip')
1939 return self.get_commit('tip')
1947
1940
1948 @property
1941 @property
1949 def author(self):
1942 def author(self):
1950 return self.tip.author
1943 return self.tip.author
1951
1944
1952 @property
1945 @property
1953 def last_change(self):
1946 def last_change(self):
1954 return self.scm_instance().last_change
1947 return self.scm_instance().last_change
1955
1948
1956 def get_comments(self, revisions=None):
1949 def get_comments(self, revisions=None):
1957 """
1950 """
1958 Returns comments for this repository grouped by revisions
1951 Returns comments for this repository grouped by revisions
1959
1952
1960 :param revisions: filter query by revisions only
1953 :param revisions: filter query by revisions only
1961 """
1954 """
1962 cmts = ChangesetComment.query()\
1955 cmts = ChangesetComment.query()\
1963 .filter(ChangesetComment.repo == self)
1956 .filter(ChangesetComment.repo == self)
1964 if revisions:
1957 if revisions:
1965 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1958 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1966 grouped = collections.defaultdict(list)
1959 grouped = collections.defaultdict(list)
1967 for cmt in cmts.all():
1960 for cmt in cmts.all():
1968 grouped[cmt.revision].append(cmt)
1961 grouped[cmt.revision].append(cmt)
1969 return grouped
1962 return grouped
1970
1963
1971 def statuses(self, revisions=None):
1964 def statuses(self, revisions=None):
1972 """
1965 """
1973 Returns statuses for this repository
1966 Returns statuses for this repository
1974
1967
1975 :param revisions: list of revisions to get statuses for
1968 :param revisions: list of revisions to get statuses for
1976 """
1969 """
1977 statuses = ChangesetStatus.query()\
1970 statuses = ChangesetStatus.query()\
1978 .filter(ChangesetStatus.repo == self)\
1971 .filter(ChangesetStatus.repo == self)\
1979 .filter(ChangesetStatus.version == 0)
1972 .filter(ChangesetStatus.version == 0)
1980
1973
1981 if revisions:
1974 if revisions:
1982 # Try doing the filtering in chunks to avoid hitting limits
1975 # Try doing the filtering in chunks to avoid hitting limits
1983 size = 500
1976 size = 500
1984 status_results = []
1977 status_results = []
1985 for chunk in xrange(0, len(revisions), size):
1978 for chunk in xrange(0, len(revisions), size):
1986 status_results += statuses.filter(
1979 status_results += statuses.filter(
1987 ChangesetStatus.revision.in_(
1980 ChangesetStatus.revision.in_(
1988 revisions[chunk: chunk+size])
1981 revisions[chunk: chunk+size])
1989 ).all()
1982 ).all()
1990 else:
1983 else:
1991 status_results = statuses.all()
1984 status_results = statuses.all()
1992
1985
1993 grouped = {}
1986 grouped = {}
1994
1987
1995 # maybe we have open new pullrequest without a status?
1988 # maybe we have open new pullrequest without a status?
1996 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1989 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1997 status_lbl = ChangesetStatus.get_status_lbl(stat)
1990 status_lbl = ChangesetStatus.get_status_lbl(stat)
1998 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1991 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1999 for rev in pr.revisions:
1992 for rev in pr.revisions:
2000 pr_id = pr.pull_request_id
1993 pr_id = pr.pull_request_id
2001 pr_repo = pr.target_repo.repo_name
1994 pr_repo = pr.target_repo.repo_name
2002 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1995 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2003
1996
2004 for stat in status_results:
1997 for stat in status_results:
2005 pr_id = pr_repo = None
1998 pr_id = pr_repo = None
2006 if stat.pull_request:
1999 if stat.pull_request:
2007 pr_id = stat.pull_request.pull_request_id
2000 pr_id = stat.pull_request.pull_request_id
2008 pr_repo = stat.pull_request.target_repo.repo_name
2001 pr_repo = stat.pull_request.target_repo.repo_name
2009 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2002 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2010 pr_id, pr_repo]
2003 pr_id, pr_repo]
2011 return grouped
2004 return grouped
2012
2005
2013 # ==========================================================================
2006 # ==========================================================================
2014 # SCM CACHE INSTANCE
2007 # SCM CACHE INSTANCE
2015 # ==========================================================================
2008 # ==========================================================================
2016
2009
2017 def scm_instance(self, **kwargs):
2010 def scm_instance(self, **kwargs):
2018 import rhodecode
2011 import rhodecode
2019
2012
2020 # Passing a config will not hit the cache currently only used
2013 # Passing a config will not hit the cache currently only used
2021 # for repo2dbmapper
2014 # for repo2dbmapper
2022 config = kwargs.pop('config', None)
2015 config = kwargs.pop('config', None)
2023 cache = kwargs.pop('cache', None)
2016 cache = kwargs.pop('cache', None)
2024 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2017 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2025 # if cache is NOT defined use default global, else we have a full
2018 # if cache is NOT defined use default global, else we have a full
2026 # control over cache behaviour
2019 # control over cache behaviour
2027 if cache is None and full_cache and not config:
2020 if cache is None and full_cache and not config:
2028 return self._get_instance_cached()
2021 return self._get_instance_cached()
2029 return self._get_instance(cache=bool(cache), config=config)
2022 return self._get_instance(cache=bool(cache), config=config)
2030
2023
2031 def _get_instance_cached(self):
2024 def _get_instance_cached(self):
2032 @cache_region('long_term')
2025 @cache_region('long_term')
2033 def _get_repo(cache_key):
2026 def _get_repo(cache_key):
2034 return self._get_instance()
2027 return self._get_instance()
2035
2028
2036 invalidator_context = CacheKey.repo_context_cache(
2029 invalidator_context = CacheKey.repo_context_cache(
2037 _get_repo, self.repo_name, None, thread_scoped=True)
2030 _get_repo, self.repo_name, None, thread_scoped=True)
2038
2031
2039 with invalidator_context as context:
2032 with invalidator_context as context:
2040 context.invalidate()
2033 context.invalidate()
2041 repo = context.compute()
2034 repo = context.compute()
2042
2035
2043 return repo
2036 return repo
2044
2037
2045 def _get_instance(self, cache=True, config=None):
2038 def _get_instance(self, cache=True, config=None):
2046 config = config or self._config
2039 config = config or self._config
2047 custom_wire = {
2040 custom_wire = {
2048 'cache': cache # controls the vcs.remote cache
2041 'cache': cache # controls the vcs.remote cache
2049 }
2042 }
2050 repo = get_vcs_instance(
2043 repo = get_vcs_instance(
2051 repo_path=safe_str(self.repo_full_path),
2044 repo_path=safe_str(self.repo_full_path),
2052 config=config,
2045 config=config,
2053 with_wire=custom_wire,
2046 with_wire=custom_wire,
2054 create=False,
2047 create=False,
2055 _vcs_alias=self.repo_type)
2048 _vcs_alias=self.repo_type)
2056
2049
2057 return repo
2050 return repo
2058
2051
2059 def __json__(self):
2052 def __json__(self):
2060 return {'landing_rev': self.landing_rev}
2053 return {'landing_rev': self.landing_rev}
2061
2054
2062 def get_dict(self):
2055 def get_dict(self):
2063
2056
2064 # Since we transformed `repo_name` to a hybrid property, we need to
2057 # Since we transformed `repo_name` to a hybrid property, we need to
2065 # keep compatibility with the code which uses `repo_name` field.
2058 # keep compatibility with the code which uses `repo_name` field.
2066
2059
2067 result = super(Repository, self).get_dict()
2060 result = super(Repository, self).get_dict()
2068 result['repo_name'] = result.pop('_repo_name', None)
2061 result['repo_name'] = result.pop('_repo_name', None)
2069 return result
2062 return result
2070
2063
2071
2064
2072 class RepoGroup(Base, BaseModel):
2065 class RepoGroup(Base, BaseModel):
2073 __tablename__ = 'groups'
2066 __tablename__ = 'groups'
2074 __table_args__ = (
2067 __table_args__ = (
2075 UniqueConstraint('group_name', 'group_parent_id'),
2068 UniqueConstraint('group_name', 'group_parent_id'),
2076 CheckConstraint('group_id != group_parent_id'),
2069 CheckConstraint('group_id != group_parent_id'),
2077 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2078 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2079 )
2072 )
2080 __mapper_args__ = {'order_by': 'group_name'}
2073 __mapper_args__ = {'order_by': 'group_name'}
2081
2074
2082 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2075 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2083
2076
2084 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2077 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2085 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2078 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2086 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2079 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2087 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2080 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2088 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2081 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2090 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2083 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2091 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2084 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2092
2085
2093 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2086 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2094 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2087 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2095 parent_group = relationship('RepoGroup', remote_side=group_id)
2088 parent_group = relationship('RepoGroup', remote_side=group_id)
2096 user = relationship('User')
2089 user = relationship('User')
2097 integrations = relationship('Integration',
2090 integrations = relationship('Integration',
2098 cascade="all, delete, delete-orphan")
2091 cascade="all, delete, delete-orphan")
2099
2092
2100 def __init__(self, group_name='', parent_group=None):
2093 def __init__(self, group_name='', parent_group=None):
2101 self.group_name = group_name
2094 self.group_name = group_name
2102 self.parent_group = parent_group
2095 self.parent_group = parent_group
2103
2096
2104 def __unicode__(self):
2097 def __unicode__(self):
2105 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2098 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2106 self.group_name)
2099 self.group_name)
2107
2100
2108 @classmethod
2101 @classmethod
2109 def _generate_choice(cls, repo_group):
2102 def _generate_choice(cls, repo_group):
2110 from webhelpers.html import literal as _literal
2103 from webhelpers.html import literal as _literal
2111 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2104 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2112 return repo_group.group_id, _name(repo_group.full_path_splitted)
2105 return repo_group.group_id, _name(repo_group.full_path_splitted)
2113
2106
2114 @classmethod
2107 @classmethod
2115 def groups_choices(cls, groups=None, show_empty_group=True):
2108 def groups_choices(cls, groups=None, show_empty_group=True):
2116 if not groups:
2109 if not groups:
2117 groups = cls.query().all()
2110 groups = cls.query().all()
2118
2111
2119 repo_groups = []
2112 repo_groups = []
2120 if show_empty_group:
2113 if show_empty_group:
2121 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2114 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2122
2115
2123 repo_groups.extend([cls._generate_choice(x) for x in groups])
2116 repo_groups.extend([cls._generate_choice(x) for x in groups])
2124
2117
2125 repo_groups = sorted(
2118 repo_groups = sorted(
2126 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2119 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2127 return repo_groups
2120 return repo_groups
2128
2121
2129 @classmethod
2122 @classmethod
2130 def url_sep(cls):
2123 def url_sep(cls):
2131 return URL_SEP
2124 return URL_SEP
2132
2125
2133 @classmethod
2126 @classmethod
2134 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2127 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2135 if case_insensitive:
2128 if case_insensitive:
2136 gr = cls.query().filter(func.lower(cls.group_name)
2129 gr = cls.query().filter(func.lower(cls.group_name)
2137 == func.lower(group_name))
2130 == func.lower(group_name))
2138 else:
2131 else:
2139 gr = cls.query().filter(cls.group_name == group_name)
2132 gr = cls.query().filter(cls.group_name == group_name)
2140 if cache:
2133 if cache:
2141 gr = gr.options(FromCache(
2134 gr = gr.options(FromCache(
2142 "sql_cache_short",
2135 "sql_cache_short",
2143 "get_group_%s" % _hash_key(group_name)))
2136 "get_group_%s" % _hash_key(group_name)))
2144 return gr.scalar()
2137 return gr.scalar()
2145
2138
2146 @classmethod
2139 @classmethod
2147 def get_user_personal_repo_group(cls, user_id):
2140 def get_user_personal_repo_group(cls, user_id):
2148 user = User.get(user_id)
2141 user = User.get(user_id)
2149 return cls.query()\
2142 return cls.query()\
2150 .filter(cls.personal == true())\
2143 .filter(cls.personal == true())\
2151 .filter(cls.user == user).scalar()
2144 .filter(cls.user == user).scalar()
2152
2145
2153 @classmethod
2146 @classmethod
2154 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2147 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2155 case_insensitive=True):
2148 case_insensitive=True):
2156 q = RepoGroup.query()
2149 q = RepoGroup.query()
2157
2150
2158 if not isinstance(user_id, Optional):
2151 if not isinstance(user_id, Optional):
2159 q = q.filter(RepoGroup.user_id == user_id)
2152 q = q.filter(RepoGroup.user_id == user_id)
2160
2153
2161 if not isinstance(group_id, Optional):
2154 if not isinstance(group_id, Optional):
2162 q = q.filter(RepoGroup.group_parent_id == group_id)
2155 q = q.filter(RepoGroup.group_parent_id == group_id)
2163
2156
2164 if case_insensitive:
2157 if case_insensitive:
2165 q = q.order_by(func.lower(RepoGroup.group_name))
2158 q = q.order_by(func.lower(RepoGroup.group_name))
2166 else:
2159 else:
2167 q = q.order_by(RepoGroup.group_name)
2160 q = q.order_by(RepoGroup.group_name)
2168 return q.all()
2161 return q.all()
2169
2162
2170 @property
2163 @property
2171 def parents(self):
2164 def parents(self):
2172 parents_recursion_limit = 10
2165 parents_recursion_limit = 10
2173 groups = []
2166 groups = []
2174 if self.parent_group is None:
2167 if self.parent_group is None:
2175 return groups
2168 return groups
2176 cur_gr = self.parent_group
2169 cur_gr = self.parent_group
2177 groups.insert(0, cur_gr)
2170 groups.insert(0, cur_gr)
2178 cnt = 0
2171 cnt = 0
2179 while 1:
2172 while 1:
2180 cnt += 1
2173 cnt += 1
2181 gr = getattr(cur_gr, 'parent_group', None)
2174 gr = getattr(cur_gr, 'parent_group', None)
2182 cur_gr = cur_gr.parent_group
2175 cur_gr = cur_gr.parent_group
2183 if gr is None:
2176 if gr is None:
2184 break
2177 break
2185 if cnt == parents_recursion_limit:
2178 if cnt == parents_recursion_limit:
2186 # this will prevent accidental infinit loops
2179 # this will prevent accidental infinit loops
2187 log.error(('more than %s parents found for group %s, stopping '
2180 log.error(('more than %s parents found for group %s, stopping '
2188 'recursive parent fetching' % (parents_recursion_limit, self)))
2181 'recursive parent fetching' % (parents_recursion_limit, self)))
2189 break
2182 break
2190
2183
2191 groups.insert(0, gr)
2184 groups.insert(0, gr)
2192 return groups
2185 return groups
2193
2186
2194 @property
2187 @property
2195 def children(self):
2188 def children(self):
2196 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2189 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2197
2190
2198 @property
2191 @property
2199 def name(self):
2192 def name(self):
2200 return self.group_name.split(RepoGroup.url_sep())[-1]
2193 return self.group_name.split(RepoGroup.url_sep())[-1]
2201
2194
2202 @property
2195 @property
2203 def full_path(self):
2196 def full_path(self):
2204 return self.group_name
2197 return self.group_name
2205
2198
2206 @property
2199 @property
2207 def full_path_splitted(self):
2200 def full_path_splitted(self):
2208 return self.group_name.split(RepoGroup.url_sep())
2201 return self.group_name.split(RepoGroup.url_sep())
2209
2202
2210 @property
2203 @property
2211 def repositories(self):
2204 def repositories(self):
2212 return Repository.query()\
2205 return Repository.query()\
2213 .filter(Repository.group == self)\
2206 .filter(Repository.group == self)\
2214 .order_by(Repository.repo_name)
2207 .order_by(Repository.repo_name)
2215
2208
2216 @property
2209 @property
2217 def repositories_recursive_count(self):
2210 def repositories_recursive_count(self):
2218 cnt = self.repositories.count()
2211 cnt = self.repositories.count()
2219
2212
2220 def children_count(group):
2213 def children_count(group):
2221 cnt = 0
2214 cnt = 0
2222 for child in group.children:
2215 for child in group.children:
2223 cnt += child.repositories.count()
2216 cnt += child.repositories.count()
2224 cnt += children_count(child)
2217 cnt += children_count(child)
2225 return cnt
2218 return cnt
2226
2219
2227 return cnt + children_count(self)
2220 return cnt + children_count(self)
2228
2221
2229 def _recursive_objects(self, include_repos=True):
2222 def _recursive_objects(self, include_repos=True):
2230 all_ = []
2223 all_ = []
2231
2224
2232 def _get_members(root_gr):
2225 def _get_members(root_gr):
2233 if include_repos:
2226 if include_repos:
2234 for r in root_gr.repositories:
2227 for r in root_gr.repositories:
2235 all_.append(r)
2228 all_.append(r)
2236 childs = root_gr.children.all()
2229 childs = root_gr.children.all()
2237 if childs:
2230 if childs:
2238 for gr in childs:
2231 for gr in childs:
2239 all_.append(gr)
2232 all_.append(gr)
2240 _get_members(gr)
2233 _get_members(gr)
2241
2234
2242 _get_members(self)
2235 _get_members(self)
2243 return [self] + all_
2236 return [self] + all_
2244
2237
2245 def recursive_groups_and_repos(self):
2238 def recursive_groups_and_repos(self):
2246 """
2239 """
2247 Recursive return all groups, with repositories in those groups
2240 Recursive return all groups, with repositories in those groups
2248 """
2241 """
2249 return self._recursive_objects()
2242 return self._recursive_objects()
2250
2243
2251 def recursive_groups(self):
2244 def recursive_groups(self):
2252 """
2245 """
2253 Returns all children groups for this group including children of children
2246 Returns all children groups for this group including children of children
2254 """
2247 """
2255 return self._recursive_objects(include_repos=False)
2248 return self._recursive_objects(include_repos=False)
2256
2249
2257 def get_new_name(self, group_name):
2250 def get_new_name(self, group_name):
2258 """
2251 """
2259 returns new full group name based on parent and new name
2252 returns new full group name based on parent and new name
2260
2253
2261 :param group_name:
2254 :param group_name:
2262 """
2255 """
2263 path_prefix = (self.parent_group.full_path_splitted if
2256 path_prefix = (self.parent_group.full_path_splitted if
2264 self.parent_group else [])
2257 self.parent_group else [])
2265 return RepoGroup.url_sep().join(path_prefix + [group_name])
2258 return RepoGroup.url_sep().join(path_prefix + [group_name])
2266
2259
2267 def permissions(self, with_admins=True, with_owner=True):
2260 def permissions(self, with_admins=True, with_owner=True):
2268 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2261 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2269 q = q.options(joinedload(UserRepoGroupToPerm.group),
2262 q = q.options(joinedload(UserRepoGroupToPerm.group),
2270 joinedload(UserRepoGroupToPerm.user),
2263 joinedload(UserRepoGroupToPerm.user),
2271 joinedload(UserRepoGroupToPerm.permission),)
2264 joinedload(UserRepoGroupToPerm.permission),)
2272
2265
2273 # get owners and admins and permissions. We do a trick of re-writing
2266 # get owners and admins and permissions. We do a trick of re-writing
2274 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2267 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2275 # has a global reference and changing one object propagates to all
2268 # has a global reference and changing one object propagates to all
2276 # others. This means if admin is also an owner admin_row that change
2269 # others. This means if admin is also an owner admin_row that change
2277 # would propagate to both objects
2270 # would propagate to both objects
2278 perm_rows = []
2271 perm_rows = []
2279 for _usr in q.all():
2272 for _usr in q.all():
2280 usr = AttributeDict(_usr.user.get_dict())
2273 usr = AttributeDict(_usr.user.get_dict())
2281 usr.permission = _usr.permission.permission_name
2274 usr.permission = _usr.permission.permission_name
2282 perm_rows.append(usr)
2275 perm_rows.append(usr)
2283
2276
2284 # filter the perm rows by 'default' first and then sort them by
2277 # filter the perm rows by 'default' first and then sort them by
2285 # admin,write,read,none permissions sorted again alphabetically in
2278 # admin,write,read,none permissions sorted again alphabetically in
2286 # each group
2279 # each group
2287 perm_rows = sorted(perm_rows, key=display_sort)
2280 perm_rows = sorted(perm_rows, key=display_sort)
2288
2281
2289 _admin_perm = 'group.admin'
2282 _admin_perm = 'group.admin'
2290 owner_row = []
2283 owner_row = []
2291 if with_owner:
2284 if with_owner:
2292 usr = AttributeDict(self.user.get_dict())
2285 usr = AttributeDict(self.user.get_dict())
2293 usr.owner_row = True
2286 usr.owner_row = True
2294 usr.permission = _admin_perm
2287 usr.permission = _admin_perm
2295 owner_row.append(usr)
2288 owner_row.append(usr)
2296
2289
2297 super_admin_rows = []
2290 super_admin_rows = []
2298 if with_admins:
2291 if with_admins:
2299 for usr in User.get_all_super_admins():
2292 for usr in User.get_all_super_admins():
2300 # if this admin is also owner, don't double the record
2293 # if this admin is also owner, don't double the record
2301 if usr.user_id == owner_row[0].user_id:
2294 if usr.user_id == owner_row[0].user_id:
2302 owner_row[0].admin_row = True
2295 owner_row[0].admin_row = True
2303 else:
2296 else:
2304 usr = AttributeDict(usr.get_dict())
2297 usr = AttributeDict(usr.get_dict())
2305 usr.admin_row = True
2298 usr.admin_row = True
2306 usr.permission = _admin_perm
2299 usr.permission = _admin_perm
2307 super_admin_rows.append(usr)
2300 super_admin_rows.append(usr)
2308
2301
2309 return super_admin_rows + owner_row + perm_rows
2302 return super_admin_rows + owner_row + perm_rows
2310
2303
2311 def permission_user_groups(self):
2304 def permission_user_groups(self):
2312 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2305 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2313 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2306 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2314 joinedload(UserGroupRepoGroupToPerm.users_group),
2307 joinedload(UserGroupRepoGroupToPerm.users_group),
2315 joinedload(UserGroupRepoGroupToPerm.permission),)
2308 joinedload(UserGroupRepoGroupToPerm.permission),)
2316
2309
2317 perm_rows = []
2310 perm_rows = []
2318 for _user_group in q.all():
2311 for _user_group in q.all():
2319 usr = AttributeDict(_user_group.users_group.get_dict())
2312 usr = AttributeDict(_user_group.users_group.get_dict())
2320 usr.permission = _user_group.permission.permission_name
2313 usr.permission = _user_group.permission.permission_name
2321 perm_rows.append(usr)
2314 perm_rows.append(usr)
2322
2315
2323 return perm_rows
2316 return perm_rows
2324
2317
2325 def get_api_data(self):
2318 def get_api_data(self):
2326 """
2319 """
2327 Common function for generating api data
2320 Common function for generating api data
2328
2321
2329 """
2322 """
2330 group = self
2323 group = self
2331 data = {
2324 data = {
2332 'group_id': group.group_id,
2325 'group_id': group.group_id,
2333 'group_name': group.group_name,
2326 'group_name': group.group_name,
2334 'group_description': group.group_description,
2327 'group_description': group.group_description,
2335 'parent_group': group.parent_group.group_name if group.parent_group else None,
2328 'parent_group': group.parent_group.group_name if group.parent_group else None,
2336 'repositories': [x.repo_name for x in group.repositories],
2329 'repositories': [x.repo_name for x in group.repositories],
2337 'owner': group.user.username,
2330 'owner': group.user.username,
2338 }
2331 }
2339 return data
2332 return data
2340
2333
2341
2334
2342 class Permission(Base, BaseModel):
2335 class Permission(Base, BaseModel):
2343 __tablename__ = 'permissions'
2336 __tablename__ = 'permissions'
2344 __table_args__ = (
2337 __table_args__ = (
2345 Index('p_perm_name_idx', 'permission_name'),
2338 Index('p_perm_name_idx', 'permission_name'),
2346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2340 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2348 )
2341 )
2349 PERMS = [
2342 PERMS = [
2350 ('hg.admin', _('RhodeCode Super Administrator')),
2343 ('hg.admin', _('RhodeCode Super Administrator')),
2351
2344
2352 ('repository.none', _('Repository no access')),
2345 ('repository.none', _('Repository no access')),
2353 ('repository.read', _('Repository read access')),
2346 ('repository.read', _('Repository read access')),
2354 ('repository.write', _('Repository write access')),
2347 ('repository.write', _('Repository write access')),
2355 ('repository.admin', _('Repository admin access')),
2348 ('repository.admin', _('Repository admin access')),
2356
2349
2357 ('group.none', _('Repository group no access')),
2350 ('group.none', _('Repository group no access')),
2358 ('group.read', _('Repository group read access')),
2351 ('group.read', _('Repository group read access')),
2359 ('group.write', _('Repository group write access')),
2352 ('group.write', _('Repository group write access')),
2360 ('group.admin', _('Repository group admin access')),
2353 ('group.admin', _('Repository group admin access')),
2361
2354
2362 ('usergroup.none', _('User group no access')),
2355 ('usergroup.none', _('User group no access')),
2363 ('usergroup.read', _('User group read access')),
2356 ('usergroup.read', _('User group read access')),
2364 ('usergroup.write', _('User group write access')),
2357 ('usergroup.write', _('User group write access')),
2365 ('usergroup.admin', _('User group admin access')),
2358 ('usergroup.admin', _('User group admin access')),
2366
2359
2367 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2360 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2368 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2361 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2369
2362
2370 ('hg.usergroup.create.false', _('User Group creation disabled')),
2363 ('hg.usergroup.create.false', _('User Group creation disabled')),
2371 ('hg.usergroup.create.true', _('User Group creation enabled')),
2364 ('hg.usergroup.create.true', _('User Group creation enabled')),
2372
2365
2373 ('hg.create.none', _('Repository creation disabled')),
2366 ('hg.create.none', _('Repository creation disabled')),
2374 ('hg.create.repository', _('Repository creation enabled')),
2367 ('hg.create.repository', _('Repository creation enabled')),
2375 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2368 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2376 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2369 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2377
2370
2378 ('hg.fork.none', _('Repository forking disabled')),
2371 ('hg.fork.none', _('Repository forking disabled')),
2379 ('hg.fork.repository', _('Repository forking enabled')),
2372 ('hg.fork.repository', _('Repository forking enabled')),
2380
2373
2381 ('hg.register.none', _('Registration disabled')),
2374 ('hg.register.none', _('Registration disabled')),
2382 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2375 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2383 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2376 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2384
2377
2385 ('hg.password_reset.enabled', _('Password reset enabled')),
2378 ('hg.password_reset.enabled', _('Password reset enabled')),
2386 ('hg.password_reset.hidden', _('Password reset hidden')),
2379 ('hg.password_reset.hidden', _('Password reset hidden')),
2387 ('hg.password_reset.disabled', _('Password reset disabled')),
2380 ('hg.password_reset.disabled', _('Password reset disabled')),
2388
2381
2389 ('hg.extern_activate.manual', _('Manual activation of external account')),
2382 ('hg.extern_activate.manual', _('Manual activation of external account')),
2390 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2383 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2391
2384
2392 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2385 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2393 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2386 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2394 ]
2387 ]
2395
2388
2396 # definition of system default permissions for DEFAULT user
2389 # definition of system default permissions for DEFAULT user
2397 DEFAULT_USER_PERMISSIONS = [
2390 DEFAULT_USER_PERMISSIONS = [
2398 'repository.read',
2391 'repository.read',
2399 'group.read',
2392 'group.read',
2400 'usergroup.read',
2393 'usergroup.read',
2401 'hg.create.repository',
2394 'hg.create.repository',
2402 'hg.repogroup.create.false',
2395 'hg.repogroup.create.false',
2403 'hg.usergroup.create.false',
2396 'hg.usergroup.create.false',
2404 'hg.create.write_on_repogroup.true',
2397 'hg.create.write_on_repogroup.true',
2405 'hg.fork.repository',
2398 'hg.fork.repository',
2406 'hg.register.manual_activate',
2399 'hg.register.manual_activate',
2407 'hg.password_reset.enabled',
2400 'hg.password_reset.enabled',
2408 'hg.extern_activate.auto',
2401 'hg.extern_activate.auto',
2409 'hg.inherit_default_perms.true',
2402 'hg.inherit_default_perms.true',
2410 ]
2403 ]
2411
2404
2412 # defines which permissions are more important higher the more important
2405 # defines which permissions are more important higher the more important
2413 # Weight defines which permissions are more important.
2406 # Weight defines which permissions are more important.
2414 # The higher number the more important.
2407 # The higher number the more important.
2415 PERM_WEIGHTS = {
2408 PERM_WEIGHTS = {
2416 'repository.none': 0,
2409 'repository.none': 0,
2417 'repository.read': 1,
2410 'repository.read': 1,
2418 'repository.write': 3,
2411 'repository.write': 3,
2419 'repository.admin': 4,
2412 'repository.admin': 4,
2420
2413
2421 'group.none': 0,
2414 'group.none': 0,
2422 'group.read': 1,
2415 'group.read': 1,
2423 'group.write': 3,
2416 'group.write': 3,
2424 'group.admin': 4,
2417 'group.admin': 4,
2425
2418
2426 'usergroup.none': 0,
2419 'usergroup.none': 0,
2427 'usergroup.read': 1,
2420 'usergroup.read': 1,
2428 'usergroup.write': 3,
2421 'usergroup.write': 3,
2429 'usergroup.admin': 4,
2422 'usergroup.admin': 4,
2430
2423
2431 'hg.repogroup.create.false': 0,
2424 'hg.repogroup.create.false': 0,
2432 'hg.repogroup.create.true': 1,
2425 'hg.repogroup.create.true': 1,
2433
2426
2434 'hg.usergroup.create.false': 0,
2427 'hg.usergroup.create.false': 0,
2435 'hg.usergroup.create.true': 1,
2428 'hg.usergroup.create.true': 1,
2436
2429
2437 'hg.fork.none': 0,
2430 'hg.fork.none': 0,
2438 'hg.fork.repository': 1,
2431 'hg.fork.repository': 1,
2439 'hg.create.none': 0,
2432 'hg.create.none': 0,
2440 'hg.create.repository': 1
2433 'hg.create.repository': 1
2441 }
2434 }
2442
2435
2443 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2436 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2444 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2437 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2445 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2438 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2446
2439
2447 def __unicode__(self):
2440 def __unicode__(self):
2448 return u"<%s('%s:%s')>" % (
2441 return u"<%s('%s:%s')>" % (
2449 self.__class__.__name__, self.permission_id, self.permission_name
2442 self.__class__.__name__, self.permission_id, self.permission_name
2450 )
2443 )
2451
2444
2452 @classmethod
2445 @classmethod
2453 def get_by_key(cls, key):
2446 def get_by_key(cls, key):
2454 return cls.query().filter(cls.permission_name == key).scalar()
2447 return cls.query().filter(cls.permission_name == key).scalar()
2455
2448
2456 @classmethod
2449 @classmethod
2457 def get_default_repo_perms(cls, user_id, repo_id=None):
2450 def get_default_repo_perms(cls, user_id, repo_id=None):
2458 q = Session().query(UserRepoToPerm, Repository, Permission)\
2451 q = Session().query(UserRepoToPerm, Repository, Permission)\
2459 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2452 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2460 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2453 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2461 .filter(UserRepoToPerm.user_id == user_id)
2454 .filter(UserRepoToPerm.user_id == user_id)
2462 if repo_id:
2455 if repo_id:
2463 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2456 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2464 return q.all()
2457 return q.all()
2465
2458
2466 @classmethod
2459 @classmethod
2467 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2460 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2468 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2461 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2469 .join(
2462 .join(
2470 Permission,
2463 Permission,
2471 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2464 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2472 .join(
2465 .join(
2473 Repository,
2466 Repository,
2474 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2467 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2475 .join(
2468 .join(
2476 UserGroup,
2469 UserGroup,
2477 UserGroupRepoToPerm.users_group_id ==
2470 UserGroupRepoToPerm.users_group_id ==
2478 UserGroup.users_group_id)\
2471 UserGroup.users_group_id)\
2479 .join(
2472 .join(
2480 UserGroupMember,
2473 UserGroupMember,
2481 UserGroupRepoToPerm.users_group_id ==
2474 UserGroupRepoToPerm.users_group_id ==
2482 UserGroupMember.users_group_id)\
2475 UserGroupMember.users_group_id)\
2483 .filter(
2476 .filter(
2484 UserGroupMember.user_id == user_id,
2477 UserGroupMember.user_id == user_id,
2485 UserGroup.users_group_active == true())
2478 UserGroup.users_group_active == true())
2486 if repo_id:
2479 if repo_id:
2487 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2480 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2488 return q.all()
2481 return q.all()
2489
2482
2490 @classmethod
2483 @classmethod
2491 def get_default_group_perms(cls, user_id, repo_group_id=None):
2484 def get_default_group_perms(cls, user_id, repo_group_id=None):
2492 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2485 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2493 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2486 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2494 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2487 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2495 .filter(UserRepoGroupToPerm.user_id == user_id)
2488 .filter(UserRepoGroupToPerm.user_id == user_id)
2496 if repo_group_id:
2489 if repo_group_id:
2497 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2490 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2498 return q.all()
2491 return q.all()
2499
2492
2500 @classmethod
2493 @classmethod
2501 def get_default_group_perms_from_user_group(
2494 def get_default_group_perms_from_user_group(
2502 cls, user_id, repo_group_id=None):
2495 cls, user_id, repo_group_id=None):
2503 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2496 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2504 .join(
2497 .join(
2505 Permission,
2498 Permission,
2506 UserGroupRepoGroupToPerm.permission_id ==
2499 UserGroupRepoGroupToPerm.permission_id ==
2507 Permission.permission_id)\
2500 Permission.permission_id)\
2508 .join(
2501 .join(
2509 RepoGroup,
2502 RepoGroup,
2510 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2503 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2511 .join(
2504 .join(
2512 UserGroup,
2505 UserGroup,
2513 UserGroupRepoGroupToPerm.users_group_id ==
2506 UserGroupRepoGroupToPerm.users_group_id ==
2514 UserGroup.users_group_id)\
2507 UserGroup.users_group_id)\
2515 .join(
2508 .join(
2516 UserGroupMember,
2509 UserGroupMember,
2517 UserGroupRepoGroupToPerm.users_group_id ==
2510 UserGroupRepoGroupToPerm.users_group_id ==
2518 UserGroupMember.users_group_id)\
2511 UserGroupMember.users_group_id)\
2519 .filter(
2512 .filter(
2520 UserGroupMember.user_id == user_id,
2513 UserGroupMember.user_id == user_id,
2521 UserGroup.users_group_active == true())
2514 UserGroup.users_group_active == true())
2522 if repo_group_id:
2515 if repo_group_id:
2523 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2516 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2524 return q.all()
2517 return q.all()
2525
2518
2526 @classmethod
2519 @classmethod
2527 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2520 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2528 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2521 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2529 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2522 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2530 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2523 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2531 .filter(UserUserGroupToPerm.user_id == user_id)
2524 .filter(UserUserGroupToPerm.user_id == user_id)
2532 if user_group_id:
2525 if user_group_id:
2533 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2526 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2534 return q.all()
2527 return q.all()
2535
2528
2536 @classmethod
2529 @classmethod
2537 def get_default_user_group_perms_from_user_group(
2530 def get_default_user_group_perms_from_user_group(
2538 cls, user_id, user_group_id=None):
2531 cls, user_id, user_group_id=None):
2539 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2532 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2540 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2533 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2541 .join(
2534 .join(
2542 Permission,
2535 Permission,
2543 UserGroupUserGroupToPerm.permission_id ==
2536 UserGroupUserGroupToPerm.permission_id ==
2544 Permission.permission_id)\
2537 Permission.permission_id)\
2545 .join(
2538 .join(
2546 TargetUserGroup,
2539 TargetUserGroup,
2547 UserGroupUserGroupToPerm.target_user_group_id ==
2540 UserGroupUserGroupToPerm.target_user_group_id ==
2548 TargetUserGroup.users_group_id)\
2541 TargetUserGroup.users_group_id)\
2549 .join(
2542 .join(
2550 UserGroup,
2543 UserGroup,
2551 UserGroupUserGroupToPerm.user_group_id ==
2544 UserGroupUserGroupToPerm.user_group_id ==
2552 UserGroup.users_group_id)\
2545 UserGroup.users_group_id)\
2553 .join(
2546 .join(
2554 UserGroupMember,
2547 UserGroupMember,
2555 UserGroupUserGroupToPerm.user_group_id ==
2548 UserGroupUserGroupToPerm.user_group_id ==
2556 UserGroupMember.users_group_id)\
2549 UserGroupMember.users_group_id)\
2557 .filter(
2550 .filter(
2558 UserGroupMember.user_id == user_id,
2551 UserGroupMember.user_id == user_id,
2559 UserGroup.users_group_active == true())
2552 UserGroup.users_group_active == true())
2560 if user_group_id:
2553 if user_group_id:
2561 q = q.filter(
2554 q = q.filter(
2562 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2555 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2563
2556
2564 return q.all()
2557 return q.all()
2565
2558
2566
2559
2567 class UserRepoToPerm(Base, BaseModel):
2560 class UserRepoToPerm(Base, BaseModel):
2568 __tablename__ = 'repo_to_perm'
2561 __tablename__ = 'repo_to_perm'
2569 __table_args__ = (
2562 __table_args__ = (
2570 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2563 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2572 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2573 )
2566 )
2574 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2567 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2577 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2570 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2578
2571
2579 user = relationship('User')
2572 user = relationship('User')
2580 repository = relationship('Repository')
2573 repository = relationship('Repository')
2581 permission = relationship('Permission')
2574 permission = relationship('Permission')
2582
2575
2583 @classmethod
2576 @classmethod
2584 def create(cls, user, repository, permission):
2577 def create(cls, user, repository, permission):
2585 n = cls()
2578 n = cls()
2586 n.user = user
2579 n.user = user
2587 n.repository = repository
2580 n.repository = repository
2588 n.permission = permission
2581 n.permission = permission
2589 Session().add(n)
2582 Session().add(n)
2590 return n
2583 return n
2591
2584
2592 def __unicode__(self):
2585 def __unicode__(self):
2593 return u'<%s => %s >' % (self.user, self.repository)
2586 return u'<%s => %s >' % (self.user, self.repository)
2594
2587
2595
2588
2596 class UserUserGroupToPerm(Base, BaseModel):
2589 class UserUserGroupToPerm(Base, BaseModel):
2597 __tablename__ = 'user_user_group_to_perm'
2590 __tablename__ = 'user_user_group_to_perm'
2598 __table_args__ = (
2591 __table_args__ = (
2599 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2592 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2600 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2601 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2602 )
2595 )
2603 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2596 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2604 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2605 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2606 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2599 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2607
2600
2608 user = relationship('User')
2601 user = relationship('User')
2609 user_group = relationship('UserGroup')
2602 user_group = relationship('UserGroup')
2610 permission = relationship('Permission')
2603 permission = relationship('Permission')
2611
2604
2612 @classmethod
2605 @classmethod
2613 def create(cls, user, user_group, permission):
2606 def create(cls, user, user_group, permission):
2614 n = cls()
2607 n = cls()
2615 n.user = user
2608 n.user = user
2616 n.user_group = user_group
2609 n.user_group = user_group
2617 n.permission = permission
2610 n.permission = permission
2618 Session().add(n)
2611 Session().add(n)
2619 return n
2612 return n
2620
2613
2621 def __unicode__(self):
2614 def __unicode__(self):
2622 return u'<%s => %s >' % (self.user, self.user_group)
2615 return u'<%s => %s >' % (self.user, self.user_group)
2623
2616
2624
2617
2625 class UserToPerm(Base, BaseModel):
2618 class UserToPerm(Base, BaseModel):
2626 __tablename__ = 'user_to_perm'
2619 __tablename__ = 'user_to_perm'
2627 __table_args__ = (
2620 __table_args__ = (
2628 UniqueConstraint('user_id', 'permission_id'),
2621 UniqueConstraint('user_id', 'permission_id'),
2629 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2622 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2630 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2623 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2631 )
2624 )
2632 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2625 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2633 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2626 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2634 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2627 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2635
2628
2636 user = relationship('User')
2629 user = relationship('User')
2637 permission = relationship('Permission', lazy='joined')
2630 permission = relationship('Permission', lazy='joined')
2638
2631
2639 def __unicode__(self):
2632 def __unicode__(self):
2640 return u'<%s => %s >' % (self.user, self.permission)
2633 return u'<%s => %s >' % (self.user, self.permission)
2641
2634
2642
2635
2643 class UserGroupRepoToPerm(Base, BaseModel):
2636 class UserGroupRepoToPerm(Base, BaseModel):
2644 __tablename__ = 'users_group_repo_to_perm'
2637 __tablename__ = 'users_group_repo_to_perm'
2645 __table_args__ = (
2638 __table_args__ = (
2646 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2639 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2649 )
2642 )
2650 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2651 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2652 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2653 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2646 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2654
2647
2655 users_group = relationship('UserGroup')
2648 users_group = relationship('UserGroup')
2656 permission = relationship('Permission')
2649 permission = relationship('Permission')
2657 repository = relationship('Repository')
2650 repository = relationship('Repository')
2658
2651
2659 @classmethod
2652 @classmethod
2660 def create(cls, users_group, repository, permission):
2653 def create(cls, users_group, repository, permission):
2661 n = cls()
2654 n = cls()
2662 n.users_group = users_group
2655 n.users_group = users_group
2663 n.repository = repository
2656 n.repository = repository
2664 n.permission = permission
2657 n.permission = permission
2665 Session().add(n)
2658 Session().add(n)
2666 return n
2659 return n
2667
2660
2668 def __unicode__(self):
2661 def __unicode__(self):
2669 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2662 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2670
2663
2671
2664
2672 class UserGroupUserGroupToPerm(Base, BaseModel):
2665 class UserGroupUserGroupToPerm(Base, BaseModel):
2673 __tablename__ = 'user_group_user_group_to_perm'
2666 __tablename__ = 'user_group_user_group_to_perm'
2674 __table_args__ = (
2667 __table_args__ = (
2675 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2668 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2676 CheckConstraint('target_user_group_id != user_group_id'),
2669 CheckConstraint('target_user_group_id != user_group_id'),
2677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2670 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2671 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2679 )
2672 )
2680 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2673 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2681 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2674 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2675 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2683 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2676 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2684
2677
2685 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2678 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2686 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2679 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2687 permission = relationship('Permission')
2680 permission = relationship('Permission')
2688
2681
2689 @classmethod
2682 @classmethod
2690 def create(cls, target_user_group, user_group, permission):
2683 def create(cls, target_user_group, user_group, permission):
2691 n = cls()
2684 n = cls()
2692 n.target_user_group = target_user_group
2685 n.target_user_group = target_user_group
2693 n.user_group = user_group
2686 n.user_group = user_group
2694 n.permission = permission
2687 n.permission = permission
2695 Session().add(n)
2688 Session().add(n)
2696 return n
2689 return n
2697
2690
2698 def __unicode__(self):
2691 def __unicode__(self):
2699 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2692 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2700
2693
2701
2694
2702 class UserGroupToPerm(Base, BaseModel):
2695 class UserGroupToPerm(Base, BaseModel):
2703 __tablename__ = 'users_group_to_perm'
2696 __tablename__ = 'users_group_to_perm'
2704 __table_args__ = (
2697 __table_args__ = (
2705 UniqueConstraint('users_group_id', 'permission_id',),
2698 UniqueConstraint('users_group_id', 'permission_id',),
2706 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2699 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2707 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2700 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2708 )
2701 )
2709 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2702 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2710 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2703 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2711 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2704 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2712
2705
2713 users_group = relationship('UserGroup')
2706 users_group = relationship('UserGroup')
2714 permission = relationship('Permission')
2707 permission = relationship('Permission')
2715
2708
2716
2709
2717 class UserRepoGroupToPerm(Base, BaseModel):
2710 class UserRepoGroupToPerm(Base, BaseModel):
2718 __tablename__ = 'user_repo_group_to_perm'
2711 __tablename__ = 'user_repo_group_to_perm'
2719 __table_args__ = (
2712 __table_args__ = (
2720 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2713 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2723 )
2716 )
2724
2717
2725 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2718 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2726 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2719 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2727 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2720 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2728 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2721 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2729
2722
2730 user = relationship('User')
2723 user = relationship('User')
2731 group = relationship('RepoGroup')
2724 group = relationship('RepoGroup')
2732 permission = relationship('Permission')
2725 permission = relationship('Permission')
2733
2726
2734 @classmethod
2727 @classmethod
2735 def create(cls, user, repository_group, permission):
2728 def create(cls, user, repository_group, permission):
2736 n = cls()
2729 n = cls()
2737 n.user = user
2730 n.user = user
2738 n.group = repository_group
2731 n.group = repository_group
2739 n.permission = permission
2732 n.permission = permission
2740 Session().add(n)
2733 Session().add(n)
2741 return n
2734 return n
2742
2735
2743
2736
2744 class UserGroupRepoGroupToPerm(Base, BaseModel):
2737 class UserGroupRepoGroupToPerm(Base, BaseModel):
2745 __tablename__ = 'users_group_repo_group_to_perm'
2738 __tablename__ = 'users_group_repo_group_to_perm'
2746 __table_args__ = (
2739 __table_args__ = (
2747 UniqueConstraint('users_group_id', 'group_id'),
2740 UniqueConstraint('users_group_id', 'group_id'),
2748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2750 )
2743 )
2751
2744
2752 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)
2745 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)
2753 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2746 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2754 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2747 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2755 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2748 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2756
2749
2757 users_group = relationship('UserGroup')
2750 users_group = relationship('UserGroup')
2758 permission = relationship('Permission')
2751 permission = relationship('Permission')
2759 group = relationship('RepoGroup')
2752 group = relationship('RepoGroup')
2760
2753
2761 @classmethod
2754 @classmethod
2762 def create(cls, user_group, repository_group, permission):
2755 def create(cls, user_group, repository_group, permission):
2763 n = cls()
2756 n = cls()
2764 n.users_group = user_group
2757 n.users_group = user_group
2765 n.group = repository_group
2758 n.group = repository_group
2766 n.permission = permission
2759 n.permission = permission
2767 Session().add(n)
2760 Session().add(n)
2768 return n
2761 return n
2769
2762
2770 def __unicode__(self):
2763 def __unicode__(self):
2771 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2764 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2772
2765
2773
2766
2774 class Statistics(Base, BaseModel):
2767 class Statistics(Base, BaseModel):
2775 __tablename__ = 'statistics'
2768 __tablename__ = 'statistics'
2776 __table_args__ = (
2769 __table_args__ = (
2777 UniqueConstraint('repository_id'),
2770 UniqueConstraint('repository_id'),
2778 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2771 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2779 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2772 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2780 )
2773 )
2781 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2774 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2782 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2775 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2783 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2776 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2784 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2777 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2785 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2778 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2786 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2779 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2787
2780
2788 repository = relationship('Repository', single_parent=True)
2781 repository = relationship('Repository', single_parent=True)
2789
2782
2790
2783
2791 class UserFollowing(Base, BaseModel):
2784 class UserFollowing(Base, BaseModel):
2792 __tablename__ = 'user_followings'
2785 __tablename__ = 'user_followings'
2793 __table_args__ = (
2786 __table_args__ = (
2794 UniqueConstraint('user_id', 'follows_repository_id'),
2787 UniqueConstraint('user_id', 'follows_repository_id'),
2795 UniqueConstraint('user_id', 'follows_user_id'),
2788 UniqueConstraint('user_id', 'follows_user_id'),
2796 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2797 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2790 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2798 )
2791 )
2799
2792
2800 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2793 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2801 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2802 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2795 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2803 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2796 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2804 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2797 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2805
2798
2806 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2799 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2807
2800
2808 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2801 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2809 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2802 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2810
2803
2811 @classmethod
2804 @classmethod
2812 def get_repo_followers(cls, repo_id):
2805 def get_repo_followers(cls, repo_id):
2813 return cls.query().filter(cls.follows_repo_id == repo_id)
2806 return cls.query().filter(cls.follows_repo_id == repo_id)
2814
2807
2815
2808
2816 class CacheKey(Base, BaseModel):
2809 class CacheKey(Base, BaseModel):
2817 __tablename__ = 'cache_invalidation'
2810 __tablename__ = 'cache_invalidation'
2818 __table_args__ = (
2811 __table_args__ = (
2819 UniqueConstraint('cache_key'),
2812 UniqueConstraint('cache_key'),
2820 Index('key_idx', 'cache_key'),
2813 Index('key_idx', 'cache_key'),
2821 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2822 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2823 )
2816 )
2824 CACHE_TYPE_ATOM = 'ATOM'
2817 CACHE_TYPE_ATOM = 'ATOM'
2825 CACHE_TYPE_RSS = 'RSS'
2818 CACHE_TYPE_RSS = 'RSS'
2826 CACHE_TYPE_README = 'README'
2819 CACHE_TYPE_README = 'README'
2827
2820
2828 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2821 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2829 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2822 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2830 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2823 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2831 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2824 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2832
2825
2833 def __init__(self, cache_key, cache_args=''):
2826 def __init__(self, cache_key, cache_args=''):
2834 self.cache_key = cache_key
2827 self.cache_key = cache_key
2835 self.cache_args = cache_args
2828 self.cache_args = cache_args
2836 self.cache_active = False
2829 self.cache_active = False
2837
2830
2838 def __unicode__(self):
2831 def __unicode__(self):
2839 return u"<%s('%s:%s[%s]')>" % (
2832 return u"<%s('%s:%s[%s]')>" % (
2840 self.__class__.__name__,
2833 self.__class__.__name__,
2841 self.cache_id, self.cache_key, self.cache_active)
2834 self.cache_id, self.cache_key, self.cache_active)
2842
2835
2843 def _cache_key_partition(self):
2836 def _cache_key_partition(self):
2844 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2837 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2845 return prefix, repo_name, suffix
2838 return prefix, repo_name, suffix
2846
2839
2847 def get_prefix(self):
2840 def get_prefix(self):
2848 """
2841 """
2849 Try to extract prefix from existing cache key. The key could consist
2842 Try to extract prefix from existing cache key. The key could consist
2850 of prefix, repo_name, suffix
2843 of prefix, repo_name, suffix
2851 """
2844 """
2852 # this returns prefix, repo_name, suffix
2845 # this returns prefix, repo_name, suffix
2853 return self._cache_key_partition()[0]
2846 return self._cache_key_partition()[0]
2854
2847
2855 def get_suffix(self):
2848 def get_suffix(self):
2856 """
2849 """
2857 get suffix that might have been used in _get_cache_key to
2850 get suffix that might have been used in _get_cache_key to
2858 generate self.cache_key. Only used for informational purposes
2851 generate self.cache_key. Only used for informational purposes
2859 in repo_edit.mako.
2852 in repo_edit.mako.
2860 """
2853 """
2861 # prefix, repo_name, suffix
2854 # prefix, repo_name, suffix
2862 return self._cache_key_partition()[2]
2855 return self._cache_key_partition()[2]
2863
2856
2864 @classmethod
2857 @classmethod
2865 def delete_all_cache(cls):
2858 def delete_all_cache(cls):
2866 """
2859 """
2867 Delete all cache keys from database.
2860 Delete all cache keys from database.
2868 Should only be run when all instances are down and all entries
2861 Should only be run when all instances are down and all entries
2869 thus stale.
2862 thus stale.
2870 """
2863 """
2871 cls.query().delete()
2864 cls.query().delete()
2872 Session().commit()
2865 Session().commit()
2873
2866
2874 @classmethod
2867 @classmethod
2875 def get_cache_key(cls, repo_name, cache_type):
2868 def get_cache_key(cls, repo_name, cache_type):
2876 """
2869 """
2877
2870
2878 Generate a cache key for this process of RhodeCode instance.
2871 Generate a cache key for this process of RhodeCode instance.
2879 Prefix most likely will be process id or maybe explicitly set
2872 Prefix most likely will be process id or maybe explicitly set
2880 instance_id from .ini file.
2873 instance_id from .ini file.
2881 """
2874 """
2882 import rhodecode
2875 import rhodecode
2883 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2876 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2884
2877
2885 repo_as_unicode = safe_unicode(repo_name)
2878 repo_as_unicode = safe_unicode(repo_name)
2886 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2879 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2887 if cache_type else repo_as_unicode
2880 if cache_type else repo_as_unicode
2888
2881
2889 return u'{}{}'.format(prefix, key)
2882 return u'{}{}'.format(prefix, key)
2890
2883
2891 @classmethod
2884 @classmethod
2892 def set_invalidate(cls, repo_name, delete=False):
2885 def set_invalidate(cls, repo_name, delete=False):
2893 """
2886 """
2894 Mark all caches of a repo as invalid in the database.
2887 Mark all caches of a repo as invalid in the database.
2895 """
2888 """
2896
2889
2897 try:
2890 try:
2898 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2891 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2899 if delete:
2892 if delete:
2900 log.debug('cache objects deleted for repo %s',
2893 log.debug('cache objects deleted for repo %s',
2901 safe_str(repo_name))
2894 safe_str(repo_name))
2902 qry.delete()
2895 qry.delete()
2903 else:
2896 else:
2904 log.debug('cache objects marked as invalid for repo %s',
2897 log.debug('cache objects marked as invalid for repo %s',
2905 safe_str(repo_name))
2898 safe_str(repo_name))
2906 qry.update({"cache_active": False})
2899 qry.update({"cache_active": False})
2907
2900
2908 Session().commit()
2901 Session().commit()
2909 except Exception:
2902 except Exception:
2910 log.exception(
2903 log.exception(
2911 'Cache key invalidation failed for repository %s',
2904 'Cache key invalidation failed for repository %s',
2912 safe_str(repo_name))
2905 safe_str(repo_name))
2913 Session().rollback()
2906 Session().rollback()
2914
2907
2915 @classmethod
2908 @classmethod
2916 def get_active_cache(cls, cache_key):
2909 def get_active_cache(cls, cache_key):
2917 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2910 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2918 if inv_obj:
2911 if inv_obj:
2919 return inv_obj
2912 return inv_obj
2920 return None
2913 return None
2921
2914
2922 @classmethod
2915 @classmethod
2923 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2916 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2924 thread_scoped=False):
2917 thread_scoped=False):
2925 """
2918 """
2926 @cache_region('long_term')
2919 @cache_region('long_term')
2927 def _heavy_calculation(cache_key):
2920 def _heavy_calculation(cache_key):
2928 return 'result'
2921 return 'result'
2929
2922
2930 cache_context = CacheKey.repo_context_cache(
2923 cache_context = CacheKey.repo_context_cache(
2931 _heavy_calculation, repo_name, cache_type)
2924 _heavy_calculation, repo_name, cache_type)
2932
2925
2933 with cache_context as context:
2926 with cache_context as context:
2934 context.invalidate()
2927 context.invalidate()
2935 computed = context.compute()
2928 computed = context.compute()
2936
2929
2937 assert computed == 'result'
2930 assert computed == 'result'
2938 """
2931 """
2939 from rhodecode.lib import caches
2932 from rhodecode.lib import caches
2940 return caches.InvalidationContext(
2933 return caches.InvalidationContext(
2941 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2934 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2942
2935
2943
2936
2944 class ChangesetComment(Base, BaseModel):
2937 class ChangesetComment(Base, BaseModel):
2945 __tablename__ = 'changeset_comments'
2938 __tablename__ = 'changeset_comments'
2946 __table_args__ = (
2939 __table_args__ = (
2947 Index('cc_revision_idx', 'revision'),
2940 Index('cc_revision_idx', 'revision'),
2948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2950 )
2943 )
2951
2944
2952 COMMENT_OUTDATED = u'comment_outdated'
2945 COMMENT_OUTDATED = u'comment_outdated'
2953 COMMENT_TYPE_NOTE = u'note'
2946 COMMENT_TYPE_NOTE = u'note'
2954 COMMENT_TYPE_TODO = u'todo'
2947 COMMENT_TYPE_TODO = u'todo'
2955 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2948 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2956
2949
2957 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2950 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2958 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2951 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2959 revision = Column('revision', String(40), nullable=True)
2952 revision = Column('revision', String(40), nullable=True)
2960 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2953 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2961 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2954 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2962 line_no = Column('line_no', Unicode(10), nullable=True)
2955 line_no = Column('line_no', Unicode(10), nullable=True)
2963 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2956 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2964 f_path = Column('f_path', Unicode(1000), nullable=True)
2957 f_path = Column('f_path', Unicode(1000), nullable=True)
2965 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2958 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2966 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2959 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2967 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2960 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2968 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2961 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2969 renderer = Column('renderer', Unicode(64), nullable=True)
2962 renderer = Column('renderer', Unicode(64), nullable=True)
2970 display_state = Column('display_state', Unicode(128), nullable=True)
2963 display_state = Column('display_state', Unicode(128), nullable=True)
2971
2964
2972 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2965 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2973 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2966 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2974 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2967 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2975 author = relationship('User', lazy='joined')
2968 author = relationship('User', lazy='joined')
2976 repo = relationship('Repository')
2969 repo = relationship('Repository')
2977 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2970 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2978 pull_request = relationship('PullRequest', lazy='joined')
2971 pull_request = relationship('PullRequest', lazy='joined')
2979 pull_request_version = relationship('PullRequestVersion')
2972 pull_request_version = relationship('PullRequestVersion')
2980
2973
2981 @classmethod
2974 @classmethod
2982 def get_users(cls, revision=None, pull_request_id=None):
2975 def get_users(cls, revision=None, pull_request_id=None):
2983 """
2976 """
2984 Returns user associated with this ChangesetComment. ie those
2977 Returns user associated with this ChangesetComment. ie those
2985 who actually commented
2978 who actually commented
2986
2979
2987 :param cls:
2980 :param cls:
2988 :param revision:
2981 :param revision:
2989 """
2982 """
2990 q = Session().query(User)\
2983 q = Session().query(User)\
2991 .join(ChangesetComment.author)
2984 .join(ChangesetComment.author)
2992 if revision:
2985 if revision:
2993 q = q.filter(cls.revision == revision)
2986 q = q.filter(cls.revision == revision)
2994 elif pull_request_id:
2987 elif pull_request_id:
2995 q = q.filter(cls.pull_request_id == pull_request_id)
2988 q = q.filter(cls.pull_request_id == pull_request_id)
2996 return q.all()
2989 return q.all()
2997
2990
2998 @classmethod
2991 @classmethod
2999 def get_index_from_version(cls, pr_version, versions):
2992 def get_index_from_version(cls, pr_version, versions):
3000 num_versions = [x.pull_request_version_id for x in versions]
2993 num_versions = [x.pull_request_version_id for x in versions]
3001 try:
2994 try:
3002 return num_versions.index(pr_version) +1
2995 return num_versions.index(pr_version) +1
3003 except (IndexError, ValueError):
2996 except (IndexError, ValueError):
3004 return
2997 return
3005
2998
3006 @property
2999 @property
3007 def outdated(self):
3000 def outdated(self):
3008 return self.display_state == self.COMMENT_OUTDATED
3001 return self.display_state == self.COMMENT_OUTDATED
3009
3002
3010 def outdated_at_version(self, version):
3003 def outdated_at_version(self, version):
3011 """
3004 """
3012 Checks if comment is outdated for given pull request version
3005 Checks if comment is outdated for given pull request version
3013 """
3006 """
3014 return self.outdated and self.pull_request_version_id != version
3007 return self.outdated and self.pull_request_version_id != version
3015
3008
3016 def older_than_version(self, version):
3009 def older_than_version(self, version):
3017 """
3010 """
3018 Checks if comment is made from previous version than given
3011 Checks if comment is made from previous version than given
3019 """
3012 """
3020 if version is None:
3013 if version is None:
3021 return self.pull_request_version_id is not None
3014 return self.pull_request_version_id is not None
3022
3015
3023 return self.pull_request_version_id < version
3016 return self.pull_request_version_id < version
3024
3017
3025 @property
3018 @property
3026 def resolved(self):
3019 def resolved(self):
3027 return self.resolved_by[0] if self.resolved_by else None
3020 return self.resolved_by[0] if self.resolved_by else None
3028
3021
3029 @property
3022 @property
3030 def is_todo(self):
3023 def is_todo(self):
3031 return self.comment_type == self.COMMENT_TYPE_TODO
3024 return self.comment_type == self.COMMENT_TYPE_TODO
3032
3025
3033 def get_index_version(self, versions):
3026 def get_index_version(self, versions):
3034 return self.get_index_from_version(
3027 return self.get_index_from_version(
3035 self.pull_request_version_id, versions)
3028 self.pull_request_version_id, versions)
3036
3029
3037 def render(self, mentions=False):
3030 def render(self, mentions=False):
3038 from rhodecode.lib import helpers as h
3031 from rhodecode.lib import helpers as h
3039 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3032 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3040
3033
3041 def __repr__(self):
3034 def __repr__(self):
3042 if self.comment_id:
3035 if self.comment_id:
3043 return '<DB:Comment #%s>' % self.comment_id
3036 return '<DB:Comment #%s>' % self.comment_id
3044 else:
3037 else:
3045 return '<DB:Comment at %#x>' % id(self)
3038 return '<DB:Comment at %#x>' % id(self)
3046
3039
3047
3040
3048 class ChangesetStatus(Base, BaseModel):
3041 class ChangesetStatus(Base, BaseModel):
3049 __tablename__ = 'changeset_statuses'
3042 __tablename__ = 'changeset_statuses'
3050 __table_args__ = (
3043 __table_args__ = (
3051 Index('cs_revision_idx', 'revision'),
3044 Index('cs_revision_idx', 'revision'),
3052 Index('cs_version_idx', 'version'),
3045 Index('cs_version_idx', 'version'),
3053 UniqueConstraint('repo_id', 'revision', 'version'),
3046 UniqueConstraint('repo_id', 'revision', 'version'),
3054 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3055 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3048 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3056 )
3049 )
3057 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3050 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3058 STATUS_APPROVED = 'approved'
3051 STATUS_APPROVED = 'approved'
3059 STATUS_REJECTED = 'rejected'
3052 STATUS_REJECTED = 'rejected'
3060 STATUS_UNDER_REVIEW = 'under_review'
3053 STATUS_UNDER_REVIEW = 'under_review'
3061
3054
3062 STATUSES = [
3055 STATUSES = [
3063 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3056 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3064 (STATUS_APPROVED, _("Approved")),
3057 (STATUS_APPROVED, _("Approved")),
3065 (STATUS_REJECTED, _("Rejected")),
3058 (STATUS_REJECTED, _("Rejected")),
3066 (STATUS_UNDER_REVIEW, _("Under Review")),
3059 (STATUS_UNDER_REVIEW, _("Under Review")),
3067 ]
3060 ]
3068
3061
3069 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3062 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3070 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3063 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3071 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3072 revision = Column('revision', String(40), nullable=False)
3065 revision = Column('revision', String(40), nullable=False)
3073 status = Column('status', String(128), nullable=False, default=DEFAULT)
3066 status = Column('status', String(128), nullable=False, default=DEFAULT)
3074 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3067 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3075 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3068 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3076 version = Column('version', Integer(), nullable=False, default=0)
3069 version = Column('version', Integer(), nullable=False, default=0)
3077 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3070 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3078
3071
3079 author = relationship('User', lazy='joined')
3072 author = relationship('User', lazy='joined')
3080 repo = relationship('Repository')
3073 repo = relationship('Repository')
3081 comment = relationship('ChangesetComment', lazy='joined')
3074 comment = relationship('ChangesetComment', lazy='joined')
3082 pull_request = relationship('PullRequest', lazy='joined')
3075 pull_request = relationship('PullRequest', lazy='joined')
3083
3076
3084 def __unicode__(self):
3077 def __unicode__(self):
3085 return u"<%s('%s[v%s]:%s')>" % (
3078 return u"<%s('%s[v%s]:%s')>" % (
3086 self.__class__.__name__,
3079 self.__class__.__name__,
3087 self.status, self.version, self.author
3080 self.status, self.version, self.author
3088 )
3081 )
3089
3082
3090 @classmethod
3083 @classmethod
3091 def get_status_lbl(cls, value):
3084 def get_status_lbl(cls, value):
3092 return dict(cls.STATUSES).get(value)
3085 return dict(cls.STATUSES).get(value)
3093
3086
3094 @property
3087 @property
3095 def status_lbl(self):
3088 def status_lbl(self):
3096 return ChangesetStatus.get_status_lbl(self.status)
3089 return ChangesetStatus.get_status_lbl(self.status)
3097
3090
3098
3091
3099 class _PullRequestBase(BaseModel):
3092 class _PullRequestBase(BaseModel):
3100 """
3093 """
3101 Common attributes of pull request and version entries.
3094 Common attributes of pull request and version entries.
3102 """
3095 """
3103
3096
3104 # .status values
3097 # .status values
3105 STATUS_NEW = u'new'
3098 STATUS_NEW = u'new'
3106 STATUS_OPEN = u'open'
3099 STATUS_OPEN = u'open'
3107 STATUS_CLOSED = u'closed'
3100 STATUS_CLOSED = u'closed'
3108
3101
3109 title = Column('title', Unicode(255), nullable=True)
3102 title = Column('title', Unicode(255), nullable=True)
3110 description = Column(
3103 description = Column(
3111 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3104 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3112 nullable=True)
3105 nullable=True)
3113 # new/open/closed status of pull request (not approve/reject/etc)
3106 # new/open/closed status of pull request (not approve/reject/etc)
3114 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3107 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3115 created_on = Column(
3108 created_on = Column(
3116 'created_on', DateTime(timezone=False), nullable=False,
3109 'created_on', DateTime(timezone=False), nullable=False,
3117 default=datetime.datetime.now)
3110 default=datetime.datetime.now)
3118 updated_on = Column(
3111 updated_on = Column(
3119 'updated_on', DateTime(timezone=False), nullable=False,
3112 'updated_on', DateTime(timezone=False), nullable=False,
3120 default=datetime.datetime.now)
3113 default=datetime.datetime.now)
3121
3114
3122 @declared_attr
3115 @declared_attr
3123 def user_id(cls):
3116 def user_id(cls):
3124 return Column(
3117 return Column(
3125 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3118 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3126 unique=None)
3119 unique=None)
3127
3120
3128 # 500 revisions max
3121 # 500 revisions max
3129 _revisions = Column(
3122 _revisions = Column(
3130 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3123 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3131
3124
3132 @declared_attr
3125 @declared_attr
3133 def source_repo_id(cls):
3126 def source_repo_id(cls):
3134 # TODO: dan: rename column to source_repo_id
3127 # TODO: dan: rename column to source_repo_id
3135 return Column(
3128 return Column(
3136 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3129 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3137 nullable=False)
3130 nullable=False)
3138
3131
3139 source_ref = Column('org_ref', Unicode(255), nullable=False)
3132 source_ref = Column('org_ref', Unicode(255), nullable=False)
3140
3133
3141 @declared_attr
3134 @declared_attr
3142 def target_repo_id(cls):
3135 def target_repo_id(cls):
3143 # TODO: dan: rename column to target_repo_id
3136 # TODO: dan: rename column to target_repo_id
3144 return Column(
3137 return Column(
3145 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3138 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3146 nullable=False)
3139 nullable=False)
3147
3140
3148 target_ref = Column('other_ref', Unicode(255), nullable=False)
3141 target_ref = Column('other_ref', Unicode(255), nullable=False)
3149 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3142 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3150
3143
3151 # TODO: dan: rename column to last_merge_source_rev
3144 # TODO: dan: rename column to last_merge_source_rev
3152 _last_merge_source_rev = Column(
3145 _last_merge_source_rev = Column(
3153 'last_merge_org_rev', String(40), nullable=True)
3146 'last_merge_org_rev', String(40), nullable=True)
3154 # TODO: dan: rename column to last_merge_target_rev
3147 # TODO: dan: rename column to last_merge_target_rev
3155 _last_merge_target_rev = Column(
3148 _last_merge_target_rev = Column(
3156 'last_merge_other_rev', String(40), nullable=True)
3149 'last_merge_other_rev', String(40), nullable=True)
3157 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3150 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3158 merge_rev = Column('merge_rev', String(40), nullable=True)
3151 merge_rev = Column('merge_rev', String(40), nullable=True)
3159
3152
3160 @hybrid_property
3153 @hybrid_property
3161 def revisions(self):
3154 def revisions(self):
3162 return self._revisions.split(':') if self._revisions else []
3155 return self._revisions.split(':') if self._revisions else []
3163
3156
3164 @revisions.setter
3157 @revisions.setter
3165 def revisions(self, val):
3158 def revisions(self, val):
3166 self._revisions = ':'.join(val)
3159 self._revisions = ':'.join(val)
3167
3160
3168 @declared_attr
3161 @declared_attr
3169 def author(cls):
3162 def author(cls):
3170 return relationship('User', lazy='joined')
3163 return relationship('User', lazy='joined')
3171
3164
3172 @declared_attr
3165 @declared_attr
3173 def source_repo(cls):
3166 def source_repo(cls):
3174 return relationship(
3167 return relationship(
3175 'Repository',
3168 'Repository',
3176 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3169 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3177
3170
3178 @property
3171 @property
3179 def source_ref_parts(self):
3172 def source_ref_parts(self):
3180 return self.unicode_to_reference(self.source_ref)
3173 return self.unicode_to_reference(self.source_ref)
3181
3174
3182 @declared_attr
3175 @declared_attr
3183 def target_repo(cls):
3176 def target_repo(cls):
3184 return relationship(
3177 return relationship(
3185 'Repository',
3178 'Repository',
3186 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3179 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3187
3180
3188 @property
3181 @property
3189 def target_ref_parts(self):
3182 def target_ref_parts(self):
3190 return self.unicode_to_reference(self.target_ref)
3183 return self.unicode_to_reference(self.target_ref)
3191
3184
3192 @property
3185 @property
3193 def shadow_merge_ref(self):
3186 def shadow_merge_ref(self):
3194 return self.unicode_to_reference(self._shadow_merge_ref)
3187 return self.unicode_to_reference(self._shadow_merge_ref)
3195
3188
3196 @shadow_merge_ref.setter
3189 @shadow_merge_ref.setter
3197 def shadow_merge_ref(self, ref):
3190 def shadow_merge_ref(self, ref):
3198 self._shadow_merge_ref = self.reference_to_unicode(ref)
3191 self._shadow_merge_ref = self.reference_to_unicode(ref)
3199
3192
3200 def unicode_to_reference(self, raw):
3193 def unicode_to_reference(self, raw):
3201 """
3194 """
3202 Convert a unicode (or string) to a reference object.
3195 Convert a unicode (or string) to a reference object.
3203 If unicode evaluates to False it returns None.
3196 If unicode evaluates to False it returns None.
3204 """
3197 """
3205 if raw:
3198 if raw:
3206 refs = raw.split(':')
3199 refs = raw.split(':')
3207 return Reference(*refs)
3200 return Reference(*refs)
3208 else:
3201 else:
3209 return None
3202 return None
3210
3203
3211 def reference_to_unicode(self, ref):
3204 def reference_to_unicode(self, ref):
3212 """
3205 """
3213 Convert a reference object to unicode.
3206 Convert a reference object to unicode.
3214 If reference is None it returns None.
3207 If reference is None it returns None.
3215 """
3208 """
3216 if ref:
3209 if ref:
3217 return u':'.join(ref)
3210 return u':'.join(ref)
3218 else:
3211 else:
3219 return None
3212 return None
3220
3213
3221 def get_api_data(self):
3214 def get_api_data(self):
3222 from rhodecode.model.pull_request import PullRequestModel
3215 from rhodecode.model.pull_request import PullRequestModel
3223 pull_request = self
3216 pull_request = self
3224 merge_status = PullRequestModel().merge_status(pull_request)
3217 merge_status = PullRequestModel().merge_status(pull_request)
3225
3218
3226 pull_request_url = url(
3219 pull_request_url = url(
3227 'pullrequest_show', repo_name=self.target_repo.repo_name,
3220 'pullrequest_show', repo_name=self.target_repo.repo_name,
3228 pull_request_id=self.pull_request_id, qualified=True)
3221 pull_request_id=self.pull_request_id, qualified=True)
3229
3222
3230 merge_data = {
3223 merge_data = {
3231 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3224 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3232 'reference': (
3225 'reference': (
3233 pull_request.shadow_merge_ref._asdict()
3226 pull_request.shadow_merge_ref._asdict()
3234 if pull_request.shadow_merge_ref else None),
3227 if pull_request.shadow_merge_ref else None),
3235 }
3228 }
3236
3229
3237 data = {
3230 data = {
3238 'pull_request_id': pull_request.pull_request_id,
3231 'pull_request_id': pull_request.pull_request_id,
3239 'url': pull_request_url,
3232 'url': pull_request_url,
3240 'title': pull_request.title,
3233 'title': pull_request.title,
3241 'description': pull_request.description,
3234 'description': pull_request.description,
3242 'status': pull_request.status,
3235 'status': pull_request.status,
3243 'created_on': pull_request.created_on,
3236 'created_on': pull_request.created_on,
3244 'updated_on': pull_request.updated_on,
3237 'updated_on': pull_request.updated_on,
3245 'commit_ids': pull_request.revisions,
3238 'commit_ids': pull_request.revisions,
3246 'review_status': pull_request.calculated_review_status(),
3239 'review_status': pull_request.calculated_review_status(),
3247 'mergeable': {
3240 'mergeable': {
3248 'status': merge_status[0],
3241 'status': merge_status[0],
3249 'message': unicode(merge_status[1]),
3242 'message': unicode(merge_status[1]),
3250 },
3243 },
3251 'source': {
3244 'source': {
3252 'clone_url': pull_request.source_repo.clone_url(),
3245 'clone_url': pull_request.source_repo.clone_url(),
3253 'repository': pull_request.source_repo.repo_name,
3246 'repository': pull_request.source_repo.repo_name,
3254 'reference': {
3247 'reference': {
3255 'name': pull_request.source_ref_parts.name,
3248 'name': pull_request.source_ref_parts.name,
3256 'type': pull_request.source_ref_parts.type,
3249 'type': pull_request.source_ref_parts.type,
3257 'commit_id': pull_request.source_ref_parts.commit_id,
3250 'commit_id': pull_request.source_ref_parts.commit_id,
3258 },
3251 },
3259 },
3252 },
3260 'target': {
3253 'target': {
3261 'clone_url': pull_request.target_repo.clone_url(),
3254 'clone_url': pull_request.target_repo.clone_url(),
3262 'repository': pull_request.target_repo.repo_name,
3255 'repository': pull_request.target_repo.repo_name,
3263 'reference': {
3256 'reference': {
3264 'name': pull_request.target_ref_parts.name,
3257 'name': pull_request.target_ref_parts.name,
3265 'type': pull_request.target_ref_parts.type,
3258 'type': pull_request.target_ref_parts.type,
3266 'commit_id': pull_request.target_ref_parts.commit_id,
3259 'commit_id': pull_request.target_ref_parts.commit_id,
3267 },
3260 },
3268 },
3261 },
3269 'merge': merge_data,
3262 'merge': merge_data,
3270 'author': pull_request.author.get_api_data(include_secrets=False,
3263 'author': pull_request.author.get_api_data(include_secrets=False,
3271 details='basic'),
3264 details='basic'),
3272 'reviewers': [
3265 'reviewers': [
3273 {
3266 {
3274 'user': reviewer.get_api_data(include_secrets=False,
3267 'user': reviewer.get_api_data(include_secrets=False,
3275 details='basic'),
3268 details='basic'),
3276 'reasons': reasons,
3269 'reasons': reasons,
3277 'review_status': st[0][1].status if st else 'not_reviewed',
3270 'review_status': st[0][1].status if st else 'not_reviewed',
3278 }
3271 }
3279 for reviewer, reasons, st in pull_request.reviewers_statuses()
3272 for reviewer, reasons, st in pull_request.reviewers_statuses()
3280 ]
3273 ]
3281 }
3274 }
3282
3275
3283 return data
3276 return data
3284
3277
3285
3278
3286 class PullRequest(Base, _PullRequestBase):
3279 class PullRequest(Base, _PullRequestBase):
3287 __tablename__ = 'pull_requests'
3280 __tablename__ = 'pull_requests'
3288 __table_args__ = (
3281 __table_args__ = (
3289 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3282 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3290 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3283 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3291 )
3284 )
3292
3285
3293 pull_request_id = Column(
3286 pull_request_id = Column(
3294 'pull_request_id', Integer(), nullable=False, primary_key=True)
3287 'pull_request_id', Integer(), nullable=False, primary_key=True)
3295
3288
3296 def __repr__(self):
3289 def __repr__(self):
3297 if self.pull_request_id:
3290 if self.pull_request_id:
3298 return '<DB:PullRequest #%s>' % self.pull_request_id
3291 return '<DB:PullRequest #%s>' % self.pull_request_id
3299 else:
3292 else:
3300 return '<DB:PullRequest at %#x>' % id(self)
3293 return '<DB:PullRequest at %#x>' % id(self)
3301
3294
3302 reviewers = relationship('PullRequestReviewers',
3295 reviewers = relationship('PullRequestReviewers',
3303 cascade="all, delete, delete-orphan")
3296 cascade="all, delete, delete-orphan")
3304 statuses = relationship('ChangesetStatus')
3297 statuses = relationship('ChangesetStatus')
3305 comments = relationship('ChangesetComment',
3298 comments = relationship('ChangesetComment',
3306 cascade="all, delete, delete-orphan")
3299 cascade="all, delete, delete-orphan")
3307 versions = relationship('PullRequestVersion',
3300 versions = relationship('PullRequestVersion',
3308 cascade="all, delete, delete-orphan",
3301 cascade="all, delete, delete-orphan",
3309 lazy='dynamic')
3302 lazy='dynamic')
3310
3303
3311 @classmethod
3304 @classmethod
3312 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3305 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3313 internal_methods=None):
3306 internal_methods=None):
3314
3307
3315 class PullRequestDisplay(object):
3308 class PullRequestDisplay(object):
3316 """
3309 """
3317 Special object wrapper for showing PullRequest data via Versions
3310 Special object wrapper for showing PullRequest data via Versions
3318 It mimics PR object as close as possible. This is read only object
3311 It mimics PR object as close as possible. This is read only object
3319 just for display
3312 just for display
3320 """
3313 """
3321
3314
3322 def __init__(self, attrs, internal=None):
3315 def __init__(self, attrs, internal=None):
3323 self.attrs = attrs
3316 self.attrs = attrs
3324 # internal have priority over the given ones via attrs
3317 # internal have priority over the given ones via attrs
3325 self.internal = internal or ['versions']
3318 self.internal = internal or ['versions']
3326
3319
3327 def __getattr__(self, item):
3320 def __getattr__(self, item):
3328 if item in self.internal:
3321 if item in self.internal:
3329 return getattr(self, item)
3322 return getattr(self, item)
3330 try:
3323 try:
3331 return self.attrs[item]
3324 return self.attrs[item]
3332 except KeyError:
3325 except KeyError:
3333 raise AttributeError(
3326 raise AttributeError(
3334 '%s object has no attribute %s' % (self, item))
3327 '%s object has no attribute %s' % (self, item))
3335
3328
3336 def __repr__(self):
3329 def __repr__(self):
3337 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3330 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3338
3331
3339 def versions(self):
3332 def versions(self):
3340 return pull_request_obj.versions.order_by(
3333 return pull_request_obj.versions.order_by(
3341 PullRequestVersion.pull_request_version_id).all()
3334 PullRequestVersion.pull_request_version_id).all()
3342
3335
3343 def is_closed(self):
3336 def is_closed(self):
3344 return pull_request_obj.is_closed()
3337 return pull_request_obj.is_closed()
3345
3338
3346 @property
3339 @property
3347 def pull_request_version_id(self):
3340 def pull_request_version_id(self):
3348 return getattr(pull_request_obj, 'pull_request_version_id', None)
3341 return getattr(pull_request_obj, 'pull_request_version_id', None)
3349
3342
3350 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3343 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3351
3344
3352 attrs.author = StrictAttributeDict(
3345 attrs.author = StrictAttributeDict(
3353 pull_request_obj.author.get_api_data())
3346 pull_request_obj.author.get_api_data())
3354 if pull_request_obj.target_repo:
3347 if pull_request_obj.target_repo:
3355 attrs.target_repo = StrictAttributeDict(
3348 attrs.target_repo = StrictAttributeDict(
3356 pull_request_obj.target_repo.get_api_data())
3349 pull_request_obj.target_repo.get_api_data())
3357 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3350 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3358
3351
3359 if pull_request_obj.source_repo:
3352 if pull_request_obj.source_repo:
3360 attrs.source_repo = StrictAttributeDict(
3353 attrs.source_repo = StrictAttributeDict(
3361 pull_request_obj.source_repo.get_api_data())
3354 pull_request_obj.source_repo.get_api_data())
3362 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3355 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3363
3356
3364 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3357 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3365 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3358 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3366 attrs.revisions = pull_request_obj.revisions
3359 attrs.revisions = pull_request_obj.revisions
3367
3360
3368 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3361 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3369
3362
3370 return PullRequestDisplay(attrs, internal=internal_methods)
3363 return PullRequestDisplay(attrs, internal=internal_methods)
3371
3364
3372 def is_closed(self):
3365 def is_closed(self):
3373 return self.status == self.STATUS_CLOSED
3366 return self.status == self.STATUS_CLOSED
3374
3367
3375 def __json__(self):
3368 def __json__(self):
3376 return {
3369 return {
3377 'revisions': self.revisions,
3370 'revisions': self.revisions,
3378 }
3371 }
3379
3372
3380 def calculated_review_status(self):
3373 def calculated_review_status(self):
3381 from rhodecode.model.changeset_status import ChangesetStatusModel
3374 from rhodecode.model.changeset_status import ChangesetStatusModel
3382 return ChangesetStatusModel().calculated_review_status(self)
3375 return ChangesetStatusModel().calculated_review_status(self)
3383
3376
3384 def reviewers_statuses(self):
3377 def reviewers_statuses(self):
3385 from rhodecode.model.changeset_status import ChangesetStatusModel
3378 from rhodecode.model.changeset_status import ChangesetStatusModel
3386 return ChangesetStatusModel().reviewers_statuses(self)
3379 return ChangesetStatusModel().reviewers_statuses(self)
3387
3380
3388 @property
3381 @property
3389 def workspace_id(self):
3382 def workspace_id(self):
3390 from rhodecode.model.pull_request import PullRequestModel
3383 from rhodecode.model.pull_request import PullRequestModel
3391 return PullRequestModel()._workspace_id(self)
3384 return PullRequestModel()._workspace_id(self)
3392
3385
3393 def get_shadow_repo(self):
3386 def get_shadow_repo(self):
3394 workspace_id = self.workspace_id
3387 workspace_id = self.workspace_id
3395 vcs_obj = self.target_repo.scm_instance()
3388 vcs_obj = self.target_repo.scm_instance()
3396 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3389 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3397 workspace_id)
3390 workspace_id)
3398 return vcs_obj._get_shadow_instance(shadow_repository_path)
3391 return vcs_obj._get_shadow_instance(shadow_repository_path)
3399
3392
3400
3393
3401 class PullRequestVersion(Base, _PullRequestBase):
3394 class PullRequestVersion(Base, _PullRequestBase):
3402 __tablename__ = 'pull_request_versions'
3395 __tablename__ = 'pull_request_versions'
3403 __table_args__ = (
3396 __table_args__ = (
3404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3405 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3398 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3406 )
3399 )
3407
3400
3408 pull_request_version_id = Column(
3401 pull_request_version_id = Column(
3409 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3402 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3410 pull_request_id = Column(
3403 pull_request_id = Column(
3411 'pull_request_id', Integer(),
3404 'pull_request_id', Integer(),
3412 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3405 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3413 pull_request = relationship('PullRequest')
3406 pull_request = relationship('PullRequest')
3414
3407
3415 def __repr__(self):
3408 def __repr__(self):
3416 if self.pull_request_version_id:
3409 if self.pull_request_version_id:
3417 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3410 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3418 else:
3411 else:
3419 return '<DB:PullRequestVersion at %#x>' % id(self)
3412 return '<DB:PullRequestVersion at %#x>' % id(self)
3420
3413
3421 @property
3414 @property
3422 def reviewers(self):
3415 def reviewers(self):
3423 return self.pull_request.reviewers
3416 return self.pull_request.reviewers
3424
3417
3425 @property
3418 @property
3426 def versions(self):
3419 def versions(self):
3427 return self.pull_request.versions
3420 return self.pull_request.versions
3428
3421
3429 def is_closed(self):
3422 def is_closed(self):
3430 # calculate from original
3423 # calculate from original
3431 return self.pull_request.status == self.STATUS_CLOSED
3424 return self.pull_request.status == self.STATUS_CLOSED
3432
3425
3433 def calculated_review_status(self):
3426 def calculated_review_status(self):
3434 return self.pull_request.calculated_review_status()
3427 return self.pull_request.calculated_review_status()
3435
3428
3436 def reviewers_statuses(self):
3429 def reviewers_statuses(self):
3437 return self.pull_request.reviewers_statuses()
3430 return self.pull_request.reviewers_statuses()
3438
3431
3439
3432
3440 class PullRequestReviewers(Base, BaseModel):
3433 class PullRequestReviewers(Base, BaseModel):
3441 __tablename__ = 'pull_request_reviewers'
3434 __tablename__ = 'pull_request_reviewers'
3442 __table_args__ = (
3435 __table_args__ = (
3443 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3444 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3437 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3445 )
3438 )
3446
3439
3447 def __init__(self, user=None, pull_request=None, reasons=None):
3440 def __init__(self, user=None, pull_request=None, reasons=None):
3448 self.user = user
3441 self.user = user
3449 self.pull_request = pull_request
3442 self.pull_request = pull_request
3450 self.reasons = reasons or []
3443 self.reasons = reasons or []
3451
3444
3452 @hybrid_property
3445 @hybrid_property
3453 def reasons(self):
3446 def reasons(self):
3454 if not self._reasons:
3447 if not self._reasons:
3455 return []
3448 return []
3456 return self._reasons
3449 return self._reasons
3457
3450
3458 @reasons.setter
3451 @reasons.setter
3459 def reasons(self, val):
3452 def reasons(self, val):
3460 val = val or []
3453 val = val or []
3461 if any(not isinstance(x, basestring) for x in val):
3454 if any(not isinstance(x, basestring) for x in val):
3462 raise Exception('invalid reasons type, must be list of strings')
3455 raise Exception('invalid reasons type, must be list of strings')
3463 self._reasons = val
3456 self._reasons = val
3464
3457
3465 pull_requests_reviewers_id = Column(
3458 pull_requests_reviewers_id = Column(
3466 'pull_requests_reviewers_id', Integer(), nullable=False,
3459 'pull_requests_reviewers_id', Integer(), nullable=False,
3467 primary_key=True)
3460 primary_key=True)
3468 pull_request_id = Column(
3461 pull_request_id = Column(
3469 "pull_request_id", Integer(),
3462 "pull_request_id", Integer(),
3470 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3463 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3471 user_id = Column(
3464 user_id = Column(
3472 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3465 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3473 _reasons = Column(
3466 _reasons = Column(
3474 'reason', MutationList.as_mutable(
3467 'reason', MutationList.as_mutable(
3475 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3468 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3476
3469
3477 user = relationship('User')
3470 user = relationship('User')
3478 pull_request = relationship('PullRequest')
3471 pull_request = relationship('PullRequest')
3479
3472
3480
3473
3481 class Notification(Base, BaseModel):
3474 class Notification(Base, BaseModel):
3482 __tablename__ = 'notifications'
3475 __tablename__ = 'notifications'
3483 __table_args__ = (
3476 __table_args__ = (
3484 Index('notification_type_idx', 'type'),
3477 Index('notification_type_idx', 'type'),
3485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3486 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3479 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3487 )
3480 )
3488
3481
3489 TYPE_CHANGESET_COMMENT = u'cs_comment'
3482 TYPE_CHANGESET_COMMENT = u'cs_comment'
3490 TYPE_MESSAGE = u'message'
3483 TYPE_MESSAGE = u'message'
3491 TYPE_MENTION = u'mention'
3484 TYPE_MENTION = u'mention'
3492 TYPE_REGISTRATION = u'registration'
3485 TYPE_REGISTRATION = u'registration'
3493 TYPE_PULL_REQUEST = u'pull_request'
3486 TYPE_PULL_REQUEST = u'pull_request'
3494 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3487 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3495
3488
3496 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3489 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3497 subject = Column('subject', Unicode(512), nullable=True)
3490 subject = Column('subject', Unicode(512), nullable=True)
3498 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3491 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3499 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3492 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3493 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3501 type_ = Column('type', Unicode(255))
3494 type_ = Column('type', Unicode(255))
3502
3495
3503 created_by_user = relationship('User')
3496 created_by_user = relationship('User')
3504 notifications_to_users = relationship('UserNotification', lazy='joined',
3497 notifications_to_users = relationship('UserNotification', lazy='joined',
3505 cascade="all, delete, delete-orphan")
3498 cascade="all, delete, delete-orphan")
3506
3499
3507 @property
3500 @property
3508 def recipients(self):
3501 def recipients(self):
3509 return [x.user for x in UserNotification.query()\
3502 return [x.user for x in UserNotification.query()\
3510 .filter(UserNotification.notification == self)\
3503 .filter(UserNotification.notification == self)\
3511 .order_by(UserNotification.user_id.asc()).all()]
3504 .order_by(UserNotification.user_id.asc()).all()]
3512
3505
3513 @classmethod
3506 @classmethod
3514 def create(cls, created_by, subject, body, recipients, type_=None):
3507 def create(cls, created_by, subject, body, recipients, type_=None):
3515 if type_ is None:
3508 if type_ is None:
3516 type_ = Notification.TYPE_MESSAGE
3509 type_ = Notification.TYPE_MESSAGE
3517
3510
3518 notification = cls()
3511 notification = cls()
3519 notification.created_by_user = created_by
3512 notification.created_by_user = created_by
3520 notification.subject = subject
3513 notification.subject = subject
3521 notification.body = body
3514 notification.body = body
3522 notification.type_ = type_
3515 notification.type_ = type_
3523 notification.created_on = datetime.datetime.now()
3516 notification.created_on = datetime.datetime.now()
3524
3517
3525 for u in recipients:
3518 for u in recipients:
3526 assoc = UserNotification()
3519 assoc = UserNotification()
3527 assoc.notification = notification
3520 assoc.notification = notification
3528
3521
3529 # if created_by is inside recipients mark his notification
3522 # if created_by is inside recipients mark his notification
3530 # as read
3523 # as read
3531 if u.user_id == created_by.user_id:
3524 if u.user_id == created_by.user_id:
3532 assoc.read = True
3525 assoc.read = True
3533
3526
3534 u.notifications.append(assoc)
3527 u.notifications.append(assoc)
3535 Session().add(notification)
3528 Session().add(notification)
3536
3529
3537 return notification
3530 return notification
3538
3531
3539 @property
3532 @property
3540 def description(self):
3533 def description(self):
3541 from rhodecode.model.notification import NotificationModel
3534 from rhodecode.model.notification import NotificationModel
3542 return NotificationModel().make_description(self)
3535 return NotificationModel().make_description(self)
3543
3536
3544
3537
3545 class UserNotification(Base, BaseModel):
3538 class UserNotification(Base, BaseModel):
3546 __tablename__ = 'user_to_notification'
3539 __tablename__ = 'user_to_notification'
3547 __table_args__ = (
3540 __table_args__ = (
3548 UniqueConstraint('user_id', 'notification_id'),
3541 UniqueConstraint('user_id', 'notification_id'),
3549 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3550 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3543 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3551 )
3544 )
3552 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3545 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3553 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3546 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3554 read = Column('read', Boolean, default=False)
3547 read = Column('read', Boolean, default=False)
3555 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3548 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3556
3549
3557 user = relationship('User', lazy="joined")
3550 user = relationship('User', lazy="joined")
3558 notification = relationship('Notification', lazy="joined",
3551 notification = relationship('Notification', lazy="joined",
3559 order_by=lambda: Notification.created_on.desc(),)
3552 order_by=lambda: Notification.created_on.desc(),)
3560
3553
3561 def mark_as_read(self):
3554 def mark_as_read(self):
3562 self.read = True
3555 self.read = True
3563 Session().add(self)
3556 Session().add(self)
3564
3557
3565
3558
3566 class Gist(Base, BaseModel):
3559 class Gist(Base, BaseModel):
3567 __tablename__ = 'gists'
3560 __tablename__ = 'gists'
3568 __table_args__ = (
3561 __table_args__ = (
3569 Index('g_gist_access_id_idx', 'gist_access_id'),
3562 Index('g_gist_access_id_idx', 'gist_access_id'),
3570 Index('g_created_on_idx', 'created_on'),
3563 Index('g_created_on_idx', 'created_on'),
3571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3572 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3573 )
3566 )
3574 GIST_PUBLIC = u'public'
3567 GIST_PUBLIC = u'public'
3575 GIST_PRIVATE = u'private'
3568 GIST_PRIVATE = u'private'
3576 DEFAULT_FILENAME = u'gistfile1.txt'
3569 DEFAULT_FILENAME = u'gistfile1.txt'
3577
3570
3578 ACL_LEVEL_PUBLIC = u'acl_public'
3571 ACL_LEVEL_PUBLIC = u'acl_public'
3579 ACL_LEVEL_PRIVATE = u'acl_private'
3572 ACL_LEVEL_PRIVATE = u'acl_private'
3580
3573
3581 gist_id = Column('gist_id', Integer(), primary_key=True)
3574 gist_id = Column('gist_id', Integer(), primary_key=True)
3582 gist_access_id = Column('gist_access_id', Unicode(250))
3575 gist_access_id = Column('gist_access_id', Unicode(250))
3583 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3576 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3584 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3577 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3585 gist_expires = Column('gist_expires', Float(53), nullable=False)
3578 gist_expires = Column('gist_expires', Float(53), nullable=False)
3586 gist_type = Column('gist_type', Unicode(128), nullable=False)
3579 gist_type = Column('gist_type', Unicode(128), nullable=False)
3587 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3588 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3581 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3589 acl_level = Column('acl_level', Unicode(128), nullable=True)
3582 acl_level = Column('acl_level', Unicode(128), nullable=True)
3590
3583
3591 owner = relationship('User')
3584 owner = relationship('User')
3592
3585
3593 def __repr__(self):
3586 def __repr__(self):
3594 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3587 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3595
3588
3596 @classmethod
3589 @classmethod
3597 def get_or_404(cls, id_):
3590 def get_or_404(cls, id_):
3598 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3591 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3599 if not res:
3592 if not res:
3600 raise HTTPNotFound
3593 raise HTTPNotFound
3601 return res
3594 return res
3602
3595
3603 @classmethod
3596 @classmethod
3604 def get_by_access_id(cls, gist_access_id):
3597 def get_by_access_id(cls, gist_access_id):
3605 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3598 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3606
3599
3607 def gist_url(self):
3600 def gist_url(self):
3608 import rhodecode
3601 import rhodecode
3609 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3602 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3610 if alias_url:
3603 if alias_url:
3611 return alias_url.replace('{gistid}', self.gist_access_id)
3604 return alias_url.replace('{gistid}', self.gist_access_id)
3612
3605
3613 return url('gist', gist_id=self.gist_access_id, qualified=True)
3606 return url('gist', gist_id=self.gist_access_id, qualified=True)
3614
3607
3615 @classmethod
3608 @classmethod
3616 def base_path(cls):
3609 def base_path(cls):
3617 """
3610 """
3618 Returns base path when all gists are stored
3611 Returns base path when all gists are stored
3619
3612
3620 :param cls:
3613 :param cls:
3621 """
3614 """
3622 from rhodecode.model.gist import GIST_STORE_LOC
3615 from rhodecode.model.gist import GIST_STORE_LOC
3623 q = Session().query(RhodeCodeUi)\
3616 q = Session().query(RhodeCodeUi)\
3624 .filter(RhodeCodeUi.ui_key == URL_SEP)
3617 .filter(RhodeCodeUi.ui_key == URL_SEP)
3625 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3618 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3626 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3619 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3627
3620
3628 def get_api_data(self):
3621 def get_api_data(self):
3629 """
3622 """
3630 Common function for generating gist related data for API
3623 Common function for generating gist related data for API
3631 """
3624 """
3632 gist = self
3625 gist = self
3633 data = {
3626 data = {
3634 'gist_id': gist.gist_id,
3627 'gist_id': gist.gist_id,
3635 'type': gist.gist_type,
3628 'type': gist.gist_type,
3636 'access_id': gist.gist_access_id,
3629 'access_id': gist.gist_access_id,
3637 'description': gist.gist_description,
3630 'description': gist.gist_description,
3638 'url': gist.gist_url(),
3631 'url': gist.gist_url(),
3639 'expires': gist.gist_expires,
3632 'expires': gist.gist_expires,
3640 'created_on': gist.created_on,
3633 'created_on': gist.created_on,
3641 'modified_at': gist.modified_at,
3634 'modified_at': gist.modified_at,
3642 'content': None,
3635 'content': None,
3643 'acl_level': gist.acl_level,
3636 'acl_level': gist.acl_level,
3644 }
3637 }
3645 return data
3638 return data
3646
3639
3647 def __json__(self):
3640 def __json__(self):
3648 data = dict(
3641 data = dict(
3649 )
3642 )
3650 data.update(self.get_api_data())
3643 data.update(self.get_api_data())
3651 return data
3644 return data
3652 # SCM functions
3645 # SCM functions
3653
3646
3654 def scm_instance(self, **kwargs):
3647 def scm_instance(self, **kwargs):
3655 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3648 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3656 return get_vcs_instance(
3649 return get_vcs_instance(
3657 repo_path=safe_str(full_repo_path), create=False)
3650 repo_path=safe_str(full_repo_path), create=False)
3658
3651
3659
3652
3660 class ExternalIdentity(Base, BaseModel):
3653 class ExternalIdentity(Base, BaseModel):
3661 __tablename__ = 'external_identities'
3654 __tablename__ = 'external_identities'
3662 __table_args__ = (
3655 __table_args__ = (
3663 Index('local_user_id_idx', 'local_user_id'),
3656 Index('local_user_id_idx', 'local_user_id'),
3664 Index('external_id_idx', 'external_id'),
3657 Index('external_id_idx', 'external_id'),
3665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3658 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3666 'mysql_charset': 'utf8'})
3659 'mysql_charset': 'utf8'})
3667
3660
3668 external_id = Column('external_id', Unicode(255), default=u'',
3661 external_id = Column('external_id', Unicode(255), default=u'',
3669 primary_key=True)
3662 primary_key=True)
3670 external_username = Column('external_username', Unicode(1024), default=u'')
3663 external_username = Column('external_username', Unicode(1024), default=u'')
3671 local_user_id = Column('local_user_id', Integer(),
3664 local_user_id = Column('local_user_id', Integer(),
3672 ForeignKey('users.user_id'), primary_key=True)
3665 ForeignKey('users.user_id'), primary_key=True)
3673 provider_name = Column('provider_name', Unicode(255), default=u'',
3666 provider_name = Column('provider_name', Unicode(255), default=u'',
3674 primary_key=True)
3667 primary_key=True)
3675 access_token = Column('access_token', String(1024), default=u'')
3668 access_token = Column('access_token', String(1024), default=u'')
3676 alt_token = Column('alt_token', String(1024), default=u'')
3669 alt_token = Column('alt_token', String(1024), default=u'')
3677 token_secret = Column('token_secret', String(1024), default=u'')
3670 token_secret = Column('token_secret', String(1024), default=u'')
3678
3671
3679 @classmethod
3672 @classmethod
3680 def by_external_id_and_provider(cls, external_id, provider_name,
3673 def by_external_id_and_provider(cls, external_id, provider_name,
3681 local_user_id=None):
3674 local_user_id=None):
3682 """
3675 """
3683 Returns ExternalIdentity instance based on search params
3676 Returns ExternalIdentity instance based on search params
3684
3677
3685 :param external_id:
3678 :param external_id:
3686 :param provider_name:
3679 :param provider_name:
3687 :return: ExternalIdentity
3680 :return: ExternalIdentity
3688 """
3681 """
3689 query = cls.query()
3682 query = cls.query()
3690 query = query.filter(cls.external_id == external_id)
3683 query = query.filter(cls.external_id == external_id)
3691 query = query.filter(cls.provider_name == provider_name)
3684 query = query.filter(cls.provider_name == provider_name)
3692 if local_user_id:
3685 if local_user_id:
3693 query = query.filter(cls.local_user_id == local_user_id)
3686 query = query.filter(cls.local_user_id == local_user_id)
3694 return query.first()
3687 return query.first()
3695
3688
3696 @classmethod
3689 @classmethod
3697 def user_by_external_id_and_provider(cls, external_id, provider_name):
3690 def user_by_external_id_and_provider(cls, external_id, provider_name):
3698 """
3691 """
3699 Returns User instance based on search params
3692 Returns User instance based on search params
3700
3693
3701 :param external_id:
3694 :param external_id:
3702 :param provider_name:
3695 :param provider_name:
3703 :return: User
3696 :return: User
3704 """
3697 """
3705 query = User.query()
3698 query = User.query()
3706 query = query.filter(cls.external_id == external_id)
3699 query = query.filter(cls.external_id == external_id)
3707 query = query.filter(cls.provider_name == provider_name)
3700 query = query.filter(cls.provider_name == provider_name)
3708 query = query.filter(User.user_id == cls.local_user_id)
3701 query = query.filter(User.user_id == cls.local_user_id)
3709 return query.first()
3702 return query.first()
3710
3703
3711 @classmethod
3704 @classmethod
3712 def by_local_user_id(cls, local_user_id):
3705 def by_local_user_id(cls, local_user_id):
3713 """
3706 """
3714 Returns all tokens for user
3707 Returns all tokens for user
3715
3708
3716 :param local_user_id:
3709 :param local_user_id:
3717 :return: ExternalIdentity
3710 :return: ExternalIdentity
3718 """
3711 """
3719 query = cls.query()
3712 query = cls.query()
3720 query = query.filter(cls.local_user_id == local_user_id)
3713 query = query.filter(cls.local_user_id == local_user_id)
3721 return query
3714 return query
3722
3715
3723
3716
3724 class Integration(Base, BaseModel):
3717 class Integration(Base, BaseModel):
3725 __tablename__ = 'integrations'
3718 __tablename__ = 'integrations'
3726 __table_args__ = (
3719 __table_args__ = (
3727 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3728 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3721 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3729 )
3722 )
3730
3723
3731 integration_id = Column('integration_id', Integer(), primary_key=True)
3724 integration_id = Column('integration_id', Integer(), primary_key=True)
3732 integration_type = Column('integration_type', String(255))
3725 integration_type = Column('integration_type', String(255))
3733 enabled = Column('enabled', Boolean(), nullable=False)
3726 enabled = Column('enabled', Boolean(), nullable=False)
3734 name = Column('name', String(255), nullable=False)
3727 name = Column('name', String(255), nullable=False)
3735 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3728 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3736 default=False)
3729 default=False)
3737
3730
3738 settings = Column(
3731 settings = Column(
3739 'settings_json', MutationObj.as_mutable(
3732 'settings_json', MutationObj.as_mutable(
3740 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3733 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3741 repo_id = Column(
3734 repo_id = Column(
3742 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3735 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3743 nullable=True, unique=None, default=None)
3736 nullable=True, unique=None, default=None)
3744 repo = relationship('Repository', lazy='joined')
3737 repo = relationship('Repository', lazy='joined')
3745
3738
3746 repo_group_id = Column(
3739 repo_group_id = Column(
3747 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3740 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3748 nullable=True, unique=None, default=None)
3741 nullable=True, unique=None, default=None)
3749 repo_group = relationship('RepoGroup', lazy='joined')
3742 repo_group = relationship('RepoGroup', lazy='joined')
3750
3743
3751 @property
3744 @property
3752 def scope(self):
3745 def scope(self):
3753 if self.repo:
3746 if self.repo:
3754 return repr(self.repo)
3747 return repr(self.repo)
3755 if self.repo_group:
3748 if self.repo_group:
3756 if self.child_repos_only:
3749 if self.child_repos_only:
3757 return repr(self.repo_group) + ' (child repos only)'
3750 return repr(self.repo_group) + ' (child repos only)'
3758 else:
3751 else:
3759 return repr(self.repo_group) + ' (recursive)'
3752 return repr(self.repo_group) + ' (recursive)'
3760 if self.child_repos_only:
3753 if self.child_repos_only:
3761 return 'root_repos'
3754 return 'root_repos'
3762 return 'global'
3755 return 'global'
3763
3756
3764 def __repr__(self):
3757 def __repr__(self):
3765 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3758 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3766
3759
3767
3760
3768 class RepoReviewRuleUser(Base, BaseModel):
3761 class RepoReviewRuleUser(Base, BaseModel):
3769 __tablename__ = 'repo_review_rules_users'
3762 __tablename__ = 'repo_review_rules_users'
3770 __table_args__ = (
3763 __table_args__ = (
3771 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3764 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3772 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3765 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3773 )
3766 )
3774 repo_review_rule_user_id = Column(
3767 repo_review_rule_user_id = Column(
3775 'repo_review_rule_user_id', Integer(), primary_key=True)
3768 'repo_review_rule_user_id', Integer(), primary_key=True)
3776 repo_review_rule_id = Column("repo_review_rule_id",
3769 repo_review_rule_id = Column("repo_review_rule_id",
3777 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3770 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3778 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3771 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3779 nullable=False)
3772 nullable=False)
3780 user = relationship('User')
3773 user = relationship('User')
3781
3774
3782
3775
3783 class RepoReviewRuleUserGroup(Base, BaseModel):
3776 class RepoReviewRuleUserGroup(Base, BaseModel):
3784 __tablename__ = 'repo_review_rules_users_groups'
3777 __tablename__ = 'repo_review_rules_users_groups'
3785 __table_args__ = (
3778 __table_args__ = (
3786 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3787 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3780 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3788 )
3781 )
3789 repo_review_rule_users_group_id = Column(
3782 repo_review_rule_users_group_id = Column(
3790 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3783 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3791 repo_review_rule_id = Column("repo_review_rule_id",
3784 repo_review_rule_id = Column("repo_review_rule_id",
3792 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3785 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3793 users_group_id = Column("users_group_id", Integer(),
3786 users_group_id = Column("users_group_id", Integer(),
3794 ForeignKey('users_groups.users_group_id'), nullable=False)
3787 ForeignKey('users_groups.users_group_id'), nullable=False)
3795 users_group = relationship('UserGroup')
3788 users_group = relationship('UserGroup')
3796
3789
3797
3790
3798 class RepoReviewRule(Base, BaseModel):
3791 class RepoReviewRule(Base, BaseModel):
3799 __tablename__ = 'repo_review_rules'
3792 __tablename__ = 'repo_review_rules'
3800 __table_args__ = (
3793 __table_args__ = (
3801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3795 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3803 )
3796 )
3804
3797
3805 repo_review_rule_id = Column(
3798 repo_review_rule_id = Column(
3806 'repo_review_rule_id', Integer(), primary_key=True)
3799 'repo_review_rule_id', Integer(), primary_key=True)
3807 repo_id = Column(
3800 repo_id = Column(
3808 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3801 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3809 repo = relationship('Repository', backref='review_rules')
3802 repo = relationship('Repository', backref='review_rules')
3810
3803
3811 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3804 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3812 default=u'*') # glob
3805 default=u'*') # glob
3813 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3806 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3814 default=u'*') # glob
3807 default=u'*') # glob
3815
3808
3816 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3809 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3817 nullable=False, default=False)
3810 nullable=False, default=False)
3818 rule_users = relationship('RepoReviewRuleUser')
3811 rule_users = relationship('RepoReviewRuleUser')
3819 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3812 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3820
3813
3821 @hybrid_property
3814 @hybrid_property
3822 def branch_pattern(self):
3815 def branch_pattern(self):
3823 return self._branch_pattern or '*'
3816 return self._branch_pattern or '*'
3824
3817
3825 def _validate_glob(self, value):
3818 def _validate_glob(self, value):
3826 re.compile('^' + glob2re(value) + '$')
3819 re.compile('^' + glob2re(value) + '$')
3827
3820
3828 @branch_pattern.setter
3821 @branch_pattern.setter
3829 def branch_pattern(self, value):
3822 def branch_pattern(self, value):
3830 self._validate_glob(value)
3823 self._validate_glob(value)
3831 self._branch_pattern = value or '*'
3824 self._branch_pattern = value or '*'
3832
3825
3833 @hybrid_property
3826 @hybrid_property
3834 def file_pattern(self):
3827 def file_pattern(self):
3835 return self._file_pattern or '*'
3828 return self._file_pattern or '*'
3836
3829
3837 @file_pattern.setter
3830 @file_pattern.setter
3838 def file_pattern(self, value):
3831 def file_pattern(self, value):
3839 self._validate_glob(value)
3832 self._validate_glob(value)
3840 self._file_pattern = value or '*'
3833 self._file_pattern = value or '*'
3841
3834
3842 def matches(self, branch, files_changed):
3835 def matches(self, branch, files_changed):
3843 """
3836 """
3844 Check if this review rule matches a branch/files in a pull request
3837 Check if this review rule matches a branch/files in a pull request
3845
3838
3846 :param branch: branch name for the commit
3839 :param branch: branch name for the commit
3847 :param files_changed: list of file paths changed in the pull request
3840 :param files_changed: list of file paths changed in the pull request
3848 """
3841 """
3849
3842
3850 branch = branch or ''
3843 branch = branch or ''
3851 files_changed = files_changed or []
3844 files_changed = files_changed or []
3852
3845
3853 branch_matches = True
3846 branch_matches = True
3854 if branch:
3847 if branch:
3855 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3848 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3856 branch_matches = bool(branch_regex.search(branch))
3849 branch_matches = bool(branch_regex.search(branch))
3857
3850
3858 files_matches = True
3851 files_matches = True
3859 if self.file_pattern != '*':
3852 if self.file_pattern != '*':
3860 files_matches = False
3853 files_matches = False
3861 file_regex = re.compile(glob2re(self.file_pattern))
3854 file_regex = re.compile(glob2re(self.file_pattern))
3862 for filename in files_changed:
3855 for filename in files_changed:
3863 if file_regex.search(filename):
3856 if file_regex.search(filename):
3864 files_matches = True
3857 files_matches = True
3865 break
3858 break
3866
3859
3867 return branch_matches and files_matches
3860 return branch_matches and files_matches
3868
3861
3869 @property
3862 @property
3870 def review_users(self):
3863 def review_users(self):
3871 """ Returns the users which this rule applies to """
3864 """ Returns the users which this rule applies to """
3872
3865
3873 users = set()
3866 users = set()
3874 users |= set([
3867 users |= set([
3875 rule_user.user for rule_user in self.rule_users
3868 rule_user.user for rule_user in self.rule_users
3876 if rule_user.user.active])
3869 if rule_user.user.active])
3877 users |= set(
3870 users |= set(
3878 member.user
3871 member.user
3879 for rule_user_group in self.rule_user_groups
3872 for rule_user_group in self.rule_user_groups
3880 for member in rule_user_group.users_group.members
3873 for member in rule_user_group.users_group.members
3881 if member.user.active
3874 if member.user.active
3882 )
3875 )
3883 return users
3876 return users
3884
3877
3885 def __repr__(self):
3878 def __repr__(self):
3886 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3879 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3887 self.repo_review_rule_id, self.repo)
3880 self.repo_review_rule_id, self.repo)
3888
3881
3889
3882
3890 class DbMigrateVersion(Base, BaseModel):
3883 class DbMigrateVersion(Base, BaseModel):
3891 __tablename__ = 'db_migrate_version'
3884 __tablename__ = 'db_migrate_version'
3892 __table_args__ = (
3885 __table_args__ = (
3893 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3894 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3895 )
3888 )
3896 repository_id = Column('repository_id', String(250), primary_key=True)
3889 repository_id = Column('repository_id', String(250), primary_key=True)
3897 repository_path = Column('repository_path', Text)
3890 repository_path = Column('repository_path', Text)
3898 version = Column('version', Integer)
3891 version = Column('version', Integer)
3899
3892
3900
3893
3901 class DbSession(Base, BaseModel):
3894 class DbSession(Base, BaseModel):
3902 __tablename__ = 'db_session'
3895 __tablename__ = 'db_session'
3903 __table_args__ = (
3896 __table_args__ = (
3904 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3905 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3898 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3906 )
3899 )
3907
3900
3908 def __repr__(self):
3901 def __repr__(self):
3909 return '<DB:DbSession({})>'.format(self.id)
3902 return '<DB:DbSession({})>'.format(self.id)
3910
3903
3911 id = Column('id', Integer())
3904 id = Column('id', Integer())
3912 namespace = Column('namespace', String(255), primary_key=True)
3905 namespace = Column('namespace', String(255), primary_key=True)
3913 accessed = Column('accessed', DateTime, nullable=False)
3906 accessed = Column('accessed', DateTime, nullable=False)
3914 created = Column('created', DateTime, nullable=False)
3907 created = Column('created', DateTime, nullable=False)
3915 data = Column('data', PickleType, nullable=False)
3908 data = Column('data', PickleType, nullable=False)
@@ -1,106 +1,80
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <p>
6 <p>
7 ${_('Built-in tokens can be used to authenticate with all possible options.')}<br/>
8 ${_('Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations.')}
7 ${_('Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations.')}
9 </p>
8 </p>
10 <table class="rctable auth_tokens">
9 <table class="rctable auth_tokens">
11 <tr>
12 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${c.user.api_key}</code></div></td>
13 <td class="td-tags">
14 <span class="tag disabled">${_('Built-in')}</span>
15 </td>
16 <td class="td-tags">
17 % for token in c.user.builtin_token_roles:
18 <span class="tag disabled">
19 ${token}
20 </span>
21 % endfor
22 </td>
23 <td class="td-exp">${_('expires')}: ${_('never')}</td>
24 <td class="td-action">
25 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
26 ${h.hidden('del_auth_token',c.user.api_key)}
27 ${h.hidden('del_auth_token_builtin',1)}
28 <button class="btn-link btn-danger" type="submit"
29 onclick="return confirm('${_('Confirm to reset this auth token: %s') % c.user.api_key}');">
30 <i class="icon-refresh"></i>
31 ${_('Reset')}
32 </button>
33 ${h.end_form()}
34 </td>
35 </tr>
36 %if c.user_auth_tokens:
10 %if c.user_auth_tokens:
37 %for auth_token in c.user_auth_tokens:
11 %for auth_token in c.user_auth_tokens:
38 <tr class="${'expired' if auth_token.expired else ''}">
12 <tr class="${'expired' if auth_token.expired else ''}">
39 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
13 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
40 <td class="td-wrap">${auth_token.description}</td>
14 <td class="td-wrap">${auth_token.description}</td>
41 <td class="td-tags">
15 <td class="td-tags">
42 <span class="tag disabled">${auth_token.role_humanized}</span>
16 <span class="tag disabled">${auth_token.role_humanized}</span>
43 </td>
17 </td>
44 <td class="td-exp">
18 <td class="td-exp">
45 %if auth_token.expires == -1:
19 %if auth_token.expires == -1:
46 ${_('expires')}: ${_('never')}
20 ${_('expires')}: ${_('never')}
47 %else:
21 %else:
48 %if auth_token.expired:
22 %if auth_token.expired:
49 ${_('expired')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
23 ${_('expired')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
50 %else:
24 %else:
51 ${_('expires')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
25 ${_('expires')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
52 %endif
26 %endif
53 %endif
27 %endif
54 </td>
28 </td>
55 <td class="td-action">
29 <td class="td-action">
56 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
30 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
57 ${h.hidden('del_auth_token',auth_token.api_key)}
31 ${h.hidden('del_auth_token',auth_token.api_key)}
58 <button class="btn btn-link btn-danger" type="submit"
32 <button class="btn btn-link btn-danger" type="submit"
59 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
33 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
60 ${_('Delete')}
34 ${_('Delete')}
61 </button>
35 </button>
62 ${h.end_form()}
36 ${h.end_form()}
63 </td>
37 </td>
64 </tr>
38 </tr>
65 %endfor
39 %endfor
66 %else:
40 %else:
67 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
41 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
68 %endif
42 %endif
69 </table>
43 </table>
70
44
71 <div class="user_auth_tokens">
45 <div class="user_auth_tokens">
72 ${h.secure_form(url('my_account_auth_tokens'), method='post')}
46 ${h.secure_form(url('my_account_auth_tokens'), method='post')}
73 <div class="form form-vertical">
47 <div class="form form-vertical">
74 <!-- fields -->
48 <!-- fields -->
75 <div class="fields">
49 <div class="fields">
76 <div class="field">
50 <div class="field">
77 <div class="label">
51 <div class="label">
78 <label for="new_email">${_('New authentication token')}:</label>
52 <label for="new_email">${_('New authentication token')}:</label>
79 </div>
53 </div>
80 <div class="input">
54 <div class="input">
81 ${h.text('description', placeholder=_('Description'))}
55 ${h.text('description', placeholder=_('Description'))}
82 ${h.select('lifetime', '', c.lifetime_options)}
56 ${h.select('lifetime', '', c.lifetime_options)}
83 ${h.select('role', '', c.role_options)}
57 ${h.select('role', '', c.role_options)}
84 </div>
58 </div>
85 </div>
59 </div>
86 <div class="buttons">
60 <div class="buttons">
87 ${h.submit('save',_('Add'),class_="btn")}
61 ${h.submit('save',_('Add'),class_="btn")}
88 ${h.reset('reset',_('Reset'),class_="btn")}
62 ${h.reset('reset',_('Reset'),class_="btn")}
89 </div>
63 </div>
90 </div>
64 </div>
91 </div>
65 </div>
92 ${h.end_form()}
66 ${h.end_form()}
93 </div>
67 </div>
94 </div>
68 </div>
95 </div>
69 </div>
96 <script>
70 <script>
97 $(document).ready(function(){
71 $(document).ready(function(){
98 var select2Options = {
72 var select2Options = {
99 'containerCssClass': "drop-menu",
73 'containerCssClass': "drop-menu",
100 'dropdownCssClass': "drop-menu-dropdown",
74 'dropdownCssClass': "drop-menu-dropdown",
101 'dropdownAutoWidth': true
75 'dropdownAutoWidth': true
102 };
76 };
103 $("#lifetime").select2(select2Options);
77 $("#lifetime").select2(select2Options);
104 $("#role").select2(select2Options);
78 $("#role").select2(select2Options);
105 });
79 });
106 </script>
80 </script>
@@ -1,107 +1,83
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Access Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Access Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <table class="rctable auth_tokens">
7 <table class="rctable auth_tokens">
8 <tr>
9 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${c.user.api_key}</code></div></td>
10 <td class="td-tags">
11 <span class="tag disabled">${_('Built-in')}</span>
12 </td>
13 <td class="td-tags">
14 % for token in c.user.builtin_token_roles:
15 <span class="tag disabled">
16 ${token}
17 </span>
18 % endfor
19 </td>
20 <td class="td-exp">${_('expires')}: ${_('never')}</td>
21 <td class="td-action">
22 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id),method='delete')}
23 ${h.hidden('del_auth_token',c.user.api_key)}
24 ${h.hidden('del_auth_token_builtin',1)}
25 <button class="btn btn-link btn-danger" type="submit"
26 onclick="return confirm('${_('Confirm to reset this auth token: %s') % c.user.api_key}');">
27 ${_('Reset')}
28 </button>
29 ${h.end_form()}
30 </td>
31 </tr>
32 %if c.user_auth_tokens:
8 %if c.user_auth_tokens:
33 %for auth_token in c.user_auth_tokens:
9 %for auth_token in c.user_auth_tokens:
34 <tr class="${'expired' if auth_token.expired else ''}">
10 <tr class="${'expired' if auth_token.expired else ''}">
35 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
11 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
36 <td class="td-wrap">${auth_token.description}</td>
12 <td class="td-wrap">${auth_token.description}</td>
37 <td class="td-tags">
13 <td class="td-tags">
38 <span class="tag">${auth_token.role_humanized}</span>
14 <span class="tag">${auth_token.role_humanized}</span>
39 </td>
15 </td>
40 <td class="td-exp">
16 <td class="td-exp">
41 %if auth_token.expires == -1:
17 %if auth_token.expires == -1:
42 ${_('expires')}: ${_('never')}
18 ${_('expires')}: ${_('never')}
43 %else:
19 %else:
44 %if auth_token.expired:
20 %if auth_token.expired:
45 ${_('expired')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
21 ${_('expired')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
46 %else:
22 %else:
47 ${_('expires')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
23 ${_('expires')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
48 %endif
24 %endif
49 %endif
25 %endif
50 </td>
26 </td>
51 <td>
27 <td>
52 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id),method='delete')}
28 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id),method='delete')}
53 ${h.hidden('del_auth_token',auth_token.api_key)}
29 ${h.hidden('del_auth_token',auth_token.api_key)}
54 <button class="btn btn-link btn-danger" type="submit"
30 <button class="btn btn-link btn-danger" type="submit"
55 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
31 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
56 ${_('Delete')}
32 ${_('Delete')}
57 </button>
33 </button>
58 ${h.end_form()}
34 ${h.end_form()}
59 </td>
35 </td>
60 </tr>
36 </tr>
61 %endfor
37 %endfor
62 %else:
38 %else:
63 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
39 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
64 %endif
40 %endif
65 </table>
41 </table>
66 </div>
42 </div>
67
43
68 <div class="user_auth_tokens">
44 <div class="user_auth_tokens">
69 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id), method='put')}
45 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id), method='put')}
70 <div class="form form-vertical">
46 <div class="form form-vertical">
71 <!-- fields -->
47 <!-- fields -->
72 <div class="fields">
48 <div class="fields">
73 <div class="field">
49 <div class="field">
74 <div class="label">
50 <div class="label">
75 <label for="new_email">${_('New auth token')}:</label>
51 <label for="new_email">${_('New auth token')}:</label>
76 </div>
52 </div>
77 <div class="input">
53 <div class="input">
78 ${h.text('description', class_='medium', placeholder=_('Description'))}
54 ${h.text('description', class_='medium', placeholder=_('Description'))}
79 ${h.select('lifetime', '', c.lifetime_options)}
55 ${h.select('lifetime', '', c.lifetime_options)}
80 ${h.select('role', '', c.role_options)}
56 ${h.select('role', '', c.role_options)}
81 </div>
57 </div>
82 </div>
58 </div>
83 <div class="buttons">
59 <div class="buttons">
84 ${h.submit('save',_('Add'),class_="btn btn-small")}
60 ${h.submit('save',_('Add'),class_="btn btn-small")}
85 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
61 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
86 </div>
62 </div>
87 </div>
63 </div>
88 </div>
64 </div>
89 ${h.end_form()}
65 ${h.end_form()}
90 </div>
66 </div>
91 </div>
67 </div>
92 </div>
68 </div>
93
69
94 <script>
70 <script>
95 $(document).ready(function(){
71 $(document).ready(function(){
96 $("#lifetime").select2({
72 $("#lifetime").select2({
97 'containerCssClass': "drop-menu",
73 'containerCssClass': "drop-menu",
98 'dropdownCssClass': "drop-menu-dropdown",
74 'dropdownCssClass': "drop-menu-dropdown",
99 'dropdownAutoWidth': true
75 'dropdownAutoWidth': true
100 });
76 });
101 $("#role").select2({
77 $("#role").select2({
102 'containerCssClass': "drop-menu",
78 'containerCssClass': "drop-menu",
103 'dropdownCssClass': "drop-menu-dropdown",
79 'dropdownCssClass': "drop-menu-dropdown",
104 'dropdownAutoWidth': true
80 'dropdownAutoWidth': true
105 });
81 });
106 })
82 })
107 </script>
83 </script>
@@ -1,400 +1,384
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
29 assert_session_flash)
29 assert_session_flash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 class TestMyAccountController(TestController):
36 class TestMyAccountController(TestController):
37 test_user_1 = 'testme'
37 test_user_1 = 'testme'
38 test_user_1_password = '0jd83nHNS/d23n'
38 test_user_1_password = '0jd83nHNS/d23n'
39 destroy_users = set()
39 destroy_users = set()
40
40
41 @classmethod
41 @classmethod
42 def teardown_class(cls):
42 def teardown_class(cls):
43 fixture.destroy_users(cls.destroy_users)
43 fixture.destroy_users(cls.destroy_users)
44
44
45 def test_my_account(self):
45 def test_my_account(self):
46 self.log_user()
46 self.log_user()
47 response = self.app.get(url('my_account'))
47 response = self.app.get(url('my_account'))
48
48
49 response.mustcontain('test_admin')
49 response.mustcontain('test_admin')
50 response.mustcontain('href="/_admin/my_account/edit"')
50 response.mustcontain('href="/_admin/my_account/edit"')
51
51
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
53 response = self.app.get(url('my_account'))
53 response = self.app.get(url('my_account'))
54 assert_response = AssertResponse(response)
54 assert_response = AssertResponse(response)
55 element = assert_response.get_element('.logout #csrf_token')
55 element = assert_response.get_element('.logout #csrf_token')
56 assert element.value == csrf_token
56 assert element.value == csrf_token
57
57
58 def test_my_account_edit(self):
58 def test_my_account_edit(self):
59 self.log_user()
59 self.log_user()
60 response = self.app.get(url('my_account_edit'))
60 response = self.app.get(url('my_account_edit'))
61
61
62 response.mustcontain('value="test_admin')
62 response.mustcontain('value="test_admin')
63
63
64 def test_my_account_my_repos(self):
64 def test_my_account_my_repos(self):
65 self.log_user()
65 self.log_user()
66 response = self.app.get(url('my_account_repos'))
66 response = self.app.get(url('my_account_repos'))
67 repos = Repository.query().filter(
67 repos = Repository.query().filter(
68 Repository.user == User.get_by_username(
68 Repository.user == User.get_by_username(
69 TEST_USER_ADMIN_LOGIN)).all()
69 TEST_USER_ADMIN_LOGIN)).all()
70 for repo in repos:
70 for repo in repos:
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
72
72
73 def test_my_account_my_watched(self):
73 def test_my_account_my_watched(self):
74 self.log_user()
74 self.log_user()
75 response = self.app.get(url('my_account_watched'))
75 response = self.app.get(url('my_account_watched'))
76
76
77 repos = UserFollowing.query().filter(
77 repos = UserFollowing.query().filter(
78 UserFollowing.user == User.get_by_username(
78 UserFollowing.user == User.get_by_username(
79 TEST_USER_ADMIN_LOGIN)).all()
79 TEST_USER_ADMIN_LOGIN)).all()
80 for repo in repos:
80 for repo in repos:
81 response.mustcontain(
81 response.mustcontain(
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
83
83
84 @pytest.mark.backends("git", "hg")
84 @pytest.mark.backends("git", "hg")
85 def test_my_account_my_pullrequests(self, pr_util):
85 def test_my_account_my_pullrequests(self, pr_util):
86 self.log_user()
86 self.log_user()
87 response = self.app.get(url('my_account_pullrequests'))
87 response = self.app.get(url('my_account_pullrequests'))
88 response.mustcontain('There are currently no open pull '
88 response.mustcontain('There are currently no open pull '
89 'requests requiring your participation.')
89 'requests requiring your participation.')
90
90
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
92 response = self.app.get(url('my_account_pullrequests'))
92 response = self.app.get(url('my_account_pullrequests'))
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
94 response.mustcontain('TestMyAccountPR')
94 response.mustcontain('TestMyAccountPR')
95
95
96 def test_my_account_my_emails(self):
96 def test_my_account_my_emails(self):
97 self.log_user()
97 self.log_user()
98 response = self.app.get(url('my_account_emails'))
98 response = self.app.get(url('my_account_emails'))
99 response.mustcontain('No additional emails specified')
99 response.mustcontain('No additional emails specified')
100
100
101 def test_my_account_my_emails_add_existing_email(self):
101 def test_my_account_my_emails_add_existing_email(self):
102 self.log_user()
102 self.log_user()
103 response = self.app.get(url('my_account_emails'))
103 response = self.app.get(url('my_account_emails'))
104 response.mustcontain('No additional emails specified')
104 response.mustcontain('No additional emails specified')
105 response = self.app.post(url('my_account_emails'),
105 response = self.app.post(url('my_account_emails'),
106 {'new_email': TEST_USER_REGULAR_EMAIL,
106 {'new_email': TEST_USER_REGULAR_EMAIL,
107 'csrf_token': self.csrf_token})
107 'csrf_token': self.csrf_token})
108 assert_session_flash(response, 'This e-mail address is already taken')
108 assert_session_flash(response, 'This e-mail address is already taken')
109
109
110 def test_my_account_my_emails_add_mising_email_in_form(self):
110 def test_my_account_my_emails_add_mising_email_in_form(self):
111 self.log_user()
111 self.log_user()
112 response = self.app.get(url('my_account_emails'))
112 response = self.app.get(url('my_account_emails'))
113 response.mustcontain('No additional emails specified')
113 response.mustcontain('No additional emails specified')
114 response = self.app.post(url('my_account_emails'),
114 response = self.app.post(url('my_account_emails'),
115 {'csrf_token': self.csrf_token})
115 {'csrf_token': self.csrf_token})
116 assert_session_flash(response, 'Please enter an email address')
116 assert_session_flash(response, 'Please enter an email address')
117
117
118 def test_my_account_my_emails_add_remove(self):
118 def test_my_account_my_emails_add_remove(self):
119 self.log_user()
119 self.log_user()
120 response = self.app.get(url('my_account_emails'))
120 response = self.app.get(url('my_account_emails'))
121 response.mustcontain('No additional emails specified')
121 response.mustcontain('No additional emails specified')
122
122
123 response = self.app.post(url('my_account_emails'),
123 response = self.app.post(url('my_account_emails'),
124 {'new_email': 'foo@barz.com',
124 {'new_email': 'foo@barz.com',
125 'csrf_token': self.csrf_token})
125 'csrf_token': self.csrf_token})
126
126
127 response = self.app.get(url('my_account_emails'))
127 response = self.app.get(url('my_account_emails'))
128
128
129 from rhodecode.model.db import UserEmailMap
129 from rhodecode.model.db import UserEmailMap
130 email_id = UserEmailMap.query().filter(
130 email_id = UserEmailMap.query().filter(
131 UserEmailMap.user == User.get_by_username(
131 UserEmailMap.user == User.get_by_username(
132 TEST_USER_ADMIN_LOGIN)).filter(
132 TEST_USER_ADMIN_LOGIN)).filter(
133 UserEmailMap.email == 'foo@barz.com').one().email_id
133 UserEmailMap.email == 'foo@barz.com').one().email_id
134
134
135 response.mustcontain('foo@barz.com')
135 response.mustcontain('foo@barz.com')
136 response.mustcontain('<input id="del_email_id" name="del_email_id" '
136 response.mustcontain('<input id="del_email_id" name="del_email_id" '
137 'type="hidden" value="%s" />' % email_id)
137 'type="hidden" value="%s" />' % email_id)
138
138
139 response = self.app.post(
139 response = self.app.post(
140 url('my_account_emails'), {
140 url('my_account_emails'), {
141 'del_email_id': email_id, '_method': 'delete',
141 'del_email_id': email_id, '_method': 'delete',
142 'csrf_token': self.csrf_token})
142 'csrf_token': self.csrf_token})
143 assert_session_flash(response, 'Removed email address from user account')
143 assert_session_flash(response, 'Removed email address from user account')
144 response = self.app.get(url('my_account_emails'))
144 response = self.app.get(url('my_account_emails'))
145 response.mustcontain('No additional emails specified')
145 response.mustcontain('No additional emails specified')
146
146
147 @pytest.mark.parametrize(
147 @pytest.mark.parametrize(
148 "name, attrs", [
148 "name, attrs", [
149 ('firstname', {'firstname': 'new_username'}),
149 ('firstname', {'firstname': 'new_username'}),
150 ('lastname', {'lastname': 'new_username'}),
150 ('lastname', {'lastname': 'new_username'}),
151 ('admin', {'admin': True}),
151 ('admin', {'admin': True}),
152 ('admin', {'admin': False}),
152 ('admin', {'admin': False}),
153 ('extern_type', {'extern_type': 'ldap'}),
153 ('extern_type', {'extern_type': 'ldap'}),
154 ('extern_type', {'extern_type': None}),
154 ('extern_type', {'extern_type': None}),
155 # ('extern_name', {'extern_name': 'test'}),
155 # ('extern_name', {'extern_name': 'test'}),
156 # ('extern_name', {'extern_name': None}),
156 # ('extern_name', {'extern_name': None}),
157 ('active', {'active': False}),
157 ('active', {'active': False}),
158 ('active', {'active': True}),
158 ('active', {'active': True}),
159 ('email', {'email': 'some@email.com'}),
159 ('email', {'email': 'some@email.com'}),
160 ])
160 ])
161 def test_my_account_update(self, name, attrs):
161 def test_my_account_update(self, name, attrs):
162 usr = fixture.create_user(self.test_user_1,
162 usr = fixture.create_user(self.test_user_1,
163 password=self.test_user_1_password,
163 password=self.test_user_1_password,
164 email='testme@rhodecode.org',
164 email='testme@rhodecode.org',
165 extern_type='rhodecode',
165 extern_type='rhodecode',
166 extern_name=self.test_user_1,
166 extern_name=self.test_user_1,
167 skip_if_exists=True)
167 skip_if_exists=True)
168 self.destroy_users.add(self.test_user_1)
168 self.destroy_users.add(self.test_user_1)
169
169
170 params = usr.get_api_data() # current user data
170 params = usr.get_api_data() # current user data
171 user_id = usr.user_id
171 user_id = usr.user_id
172 self.log_user(
172 self.log_user(
173 username=self.test_user_1, password=self.test_user_1_password)
173 username=self.test_user_1, password=self.test_user_1_password)
174
174
175 params.update({'password_confirmation': ''})
175 params.update({'password_confirmation': ''})
176 params.update({'new_password': ''})
176 params.update({'new_password': ''})
177 params.update({'extern_type': 'rhodecode'})
177 params.update({'extern_type': 'rhodecode'})
178 params.update({'extern_name': self.test_user_1})
178 params.update({'extern_name': self.test_user_1})
179 params.update({'csrf_token': self.csrf_token})
179 params.update({'csrf_token': self.csrf_token})
180
180
181 params.update(attrs)
181 params.update(attrs)
182 # my account page cannot set language param yet, only for admins
182 # my account page cannot set language param yet, only for admins
183 del params['language']
183 del params['language']
184 response = self.app.post(url('my_account'), params)
184 response = self.app.post(url('my_account'), params)
185
185
186 assert_session_flash(
186 assert_session_flash(
187 response, 'Your account was updated successfully')
187 response, 'Your account was updated successfully')
188
188
189 del params['csrf_token']
189 del params['csrf_token']
190
190
191 updated_user = User.get_by_username(self.test_user_1)
191 updated_user = User.get_by_username(self.test_user_1)
192 updated_params = updated_user.get_api_data()
192 updated_params = updated_user.get_api_data()
193 updated_params.update({'password_confirmation': ''})
193 updated_params.update({'password_confirmation': ''})
194 updated_params.update({'new_password': ''})
194 updated_params.update({'new_password': ''})
195
195
196 params['last_login'] = updated_params['last_login']
196 params['last_login'] = updated_params['last_login']
197 # my account page cannot set language param yet, only for admins
197 # my account page cannot set language param yet, only for admins
198 # but we get this info from API anyway
198 # but we get this info from API anyway
199 params['language'] = updated_params['language']
199 params['language'] = updated_params['language']
200
200
201 if name == 'email':
201 if name == 'email':
202 params['emails'] = [attrs['email']]
202 params['emails'] = [attrs['email']]
203 if name == 'extern_type':
203 if name == 'extern_type':
204 # cannot update this via form, expected value is original one
204 # cannot update this via form, expected value is original one
205 params['extern_type'] = "rhodecode"
205 params['extern_type'] = "rhodecode"
206 if name == 'extern_name':
206 if name == 'extern_name':
207 # cannot update this via form, expected value is original one
207 # cannot update this via form, expected value is original one
208 params['extern_name'] = str(user_id)
208 params['extern_name'] = str(user_id)
209 if name == 'active':
209 if name == 'active':
210 # my account cannot deactivate account
210 # my account cannot deactivate account
211 params['active'] = True
211 params['active'] = True
212 if name == 'admin':
212 if name == 'admin':
213 # my account cannot make you an admin !
213 # my account cannot make you an admin !
214 params['admin'] = False
214 params['admin'] = False
215
215
216 assert params == updated_params
216 assert params == updated_params
217
217
218 def test_my_account_update_err_email_exists(self):
218 def test_my_account_update_err_email_exists(self):
219 self.log_user()
219 self.log_user()
220
220
221 new_email = 'test_regular@mail.com' # already exisitn email
221 new_email = 'test_regular@mail.com' # already exisitn email
222 response = self.app.post(url('my_account'),
222 response = self.app.post(url('my_account'),
223 params={
223 params={
224 'username': 'test_admin',
224 'username': 'test_admin',
225 'new_password': 'test12',
225 'new_password': 'test12',
226 'password_confirmation': 'test122',
226 'password_confirmation': 'test122',
227 'firstname': 'NewName',
227 'firstname': 'NewName',
228 'lastname': 'NewLastname',
228 'lastname': 'NewLastname',
229 'email': new_email,
229 'email': new_email,
230 'csrf_token': self.csrf_token,
230 'csrf_token': self.csrf_token,
231 })
231 })
232
232
233 response.mustcontain('This e-mail address is already taken')
233 response.mustcontain('This e-mail address is already taken')
234
234
235 def test_my_account_update_err(self):
235 def test_my_account_update_err(self):
236 self.log_user('test_regular2', 'test12')
236 self.log_user('test_regular2', 'test12')
237
237
238 new_email = 'newmail.pl'
238 new_email = 'newmail.pl'
239 response = self.app.post(url('my_account'),
239 response = self.app.post(url('my_account'),
240 params={
240 params={
241 'username': 'test_admin',
241 'username': 'test_admin',
242 'new_password': 'test12',
242 'new_password': 'test12',
243 'password_confirmation': 'test122',
243 'password_confirmation': 'test122',
244 'firstname': 'NewName',
244 'firstname': 'NewName',
245 'lastname': 'NewLastname',
245 'lastname': 'NewLastname',
246 'email': new_email,
246 'email': new_email,
247 'csrf_token': self.csrf_token,
247 'csrf_token': self.csrf_token,
248 })
248 })
249
249
250 response.mustcontain('An email address must contain a single @')
250 response.mustcontain('An email address must contain a single @')
251 from rhodecode.model import validators
251 from rhodecode.model import validators
252 msg = validators.ValidUsername(
252 msg = validators.ValidUsername(
253 edit=False, old_data={})._messages['username_exists']
253 edit=False, old_data={})._messages['username_exists']
254 msg = h.html_escape(msg % {'username': 'test_admin'})
254 msg = h.html_escape(msg % {'username': 'test_admin'})
255 response.mustcontain(u"%s" % msg)
255 response.mustcontain(u"%s" % msg)
256
256
257 def test_my_account_auth_tokens(self):
257 def test_my_account_auth_tokens(self):
258 usr = self.log_user('test_regular2', 'test12')
258 usr = self.log_user('test_regular2', 'test12')
259 user = User.get(usr['user_id'])
259 user = User.get(usr['user_id'])
260 response = self.app.get(url('my_account_auth_tokens'))
260 response = self.app.get(url('my_account_auth_tokens'))
261 response.mustcontain(user.api_key)
261 response.mustcontain(user.api_key)
262 response.mustcontain('expires: never')
262 response.mustcontain('expires: never')
263
263
264 @pytest.mark.parametrize("desc, lifetime", [
264 @pytest.mark.parametrize("desc, lifetime", [
265 ('forever', -1),
265 ('forever', -1),
266 ('5mins', 60*5),
266 ('5mins', 60*5),
267 ('30days', 60*60*24*30),
267 ('30days', 60*60*24*30),
268 ])
268 ])
269 def test_my_account_add_auth_tokens(self, desc, lifetime):
269 def test_my_account_add_auth_tokens(self, desc, lifetime):
270 usr = self.log_user('test_regular2', 'test12')
270 usr = self.log_user('test_regular2', 'test12')
271 user = User.get(usr['user_id'])
271 user = User.get(usr['user_id'])
272 response = self.app.post(url('my_account_auth_tokens'),
272 response = self.app.post(url('my_account_auth_tokens'),
273 {'description': desc, 'lifetime': lifetime,
273 {'description': desc, 'lifetime': lifetime,
274 'csrf_token': self.csrf_token})
274 'csrf_token': self.csrf_token})
275 assert_session_flash(response, 'Auth token successfully created')
275 assert_session_flash(response, 'Auth token successfully created')
276 try:
276 try:
277 response = response.follow()
277 response = response.follow()
278 user = User.get(usr['user_id'])
278 user = User.get(usr['user_id'])
279 for auth_token in user.auth_tokens:
279 for auth_token in user.auth_tokens:
280 response.mustcontain(auth_token)
280 response.mustcontain(auth_token)
281 finally:
281 finally:
282 for auth_token in UserApiKeys.query().all():
282 for auth_token in UserApiKeys.query().all():
283 Session().delete(auth_token)
283 Session().delete(auth_token)
284 Session().commit()
284 Session().commit()
285
285
286 def test_my_account_remove_auth_token(self, user_util):
286 def test_my_account_remove_auth_token(self, user_util):
287 user = user_util.create_user(password=self.test_user_1_password)
287 user = user_util.create_user(password=self.test_user_1_password)
288 user_id = user.user_id
288 user_id = user.user_id
289 self.log_user(user.username, self.test_user_1_password)
289 self.log_user(user.username, self.test_user_1_password)
290
290
291 user = User.get(user_id)
291 user = User.get(user_id)
292 keys = user.extra_auth_tokens
292 keys = user.extra_auth_tokens
293 assert 1 == len(keys)
293 assert 1 == len(keys)
294
294
295 response = self.app.post(url('my_account_auth_tokens'),
295 response = self.app.post(url('my_account_auth_tokens'),
296 {'description': 'desc', 'lifetime': -1,
296 {'description': 'desc', 'lifetime': -1,
297 'csrf_token': self.csrf_token})
297 'csrf_token': self.csrf_token})
298 assert_session_flash(response, 'Auth token successfully created')
298 assert_session_flash(response, 'Auth token successfully created')
299 response.follow()
299 response.follow()
300
300
301 user = User.get(user_id)
301 user = User.get(user_id)
302 keys = user.extra_auth_tokens
302 keys = user.extra_auth_tokens
303 assert 2 == len(keys)
303 assert 2 == len(keys)
304
304
305 response = self.app.post(
305 response = self.app.post(
306 url('my_account_auth_tokens'),
306 url('my_account_auth_tokens'),
307 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
307 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
308 'csrf_token': self.csrf_token})
308 'csrf_token': self.csrf_token})
309 assert_session_flash(response, 'Auth token successfully deleted')
309 assert_session_flash(response, 'Auth token successfully deleted')
310
310
311 user = User.get(user_id)
311 user = User.get(user_id)
312 keys = user.extra_auth_tokens
312 keys = user.extra_auth_tokens
313 assert 1 == len(keys)
313 assert 1 == len(keys)
314
314
315 def test_my_account_reset_main_auth_token(self):
316 usr = self.log_user('test_regular2', 'test12')
317 user = User.get(usr['user_id'])
318 api_key = user.api_key
319 response = self.app.get(url('my_account_auth_tokens'))
320 response.mustcontain(api_key)
321 response.mustcontain('expires: never')
322
323 response = self.app.post(
324 url('my_account_auth_tokens'),
325 {'_method': 'delete', 'del_auth_token_builtin': api_key,
326 'csrf_token': self.csrf_token})
327 assert_session_flash(response, 'Auth token successfully reset')
328 response = response.follow()
329 response.mustcontain(no=[api_key])
330
331 def test_valid_change_password(self, user_util):
315 def test_valid_change_password(self, user_util):
332 new_password = 'my_new_valid_password'
316 new_password = 'my_new_valid_password'
333 user = user_util.create_user(password=self.test_user_1_password)
317 user = user_util.create_user(password=self.test_user_1_password)
334 session = self.log_user(user.username, self.test_user_1_password)
318 session = self.log_user(user.username, self.test_user_1_password)
335 form_data = [
319 form_data = [
336 ('current_password', self.test_user_1_password),
320 ('current_password', self.test_user_1_password),
337 ('__start__', 'new_password:mapping'),
321 ('__start__', 'new_password:mapping'),
338 ('new_password', new_password),
322 ('new_password', new_password),
339 ('new_password-confirm', new_password),
323 ('new_password-confirm', new_password),
340 ('__end__', 'new_password:mapping'),
324 ('__end__', 'new_password:mapping'),
341 ('csrf_token', self.csrf_token),
325 ('csrf_token', self.csrf_token),
342 ]
326 ]
343 response = self.app.post(url('my_account_password'), form_data).follow()
327 response = self.app.post(url('my_account_password'), form_data).follow()
344 assert 'Successfully updated password' in response
328 assert 'Successfully updated password' in response
345
329
346 # check_password depends on user being in session
330 # check_password depends on user being in session
347 Session().add(user)
331 Session().add(user)
348 try:
332 try:
349 assert check_password(new_password, user.password)
333 assert check_password(new_password, user.password)
350 finally:
334 finally:
351 Session().expunge(user)
335 Session().expunge(user)
352
336
353 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
337 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
354 ('', 'abcdef123', 'abcdef123'),
338 ('', 'abcdef123', 'abcdef123'),
355 ('wrong_pw', 'abcdef123', 'abcdef123'),
339 ('wrong_pw', 'abcdef123', 'abcdef123'),
356 (test_user_1_password, test_user_1_password, test_user_1_password),
340 (test_user_1_password, test_user_1_password, test_user_1_password),
357 (test_user_1_password, '', ''),
341 (test_user_1_password, '', ''),
358 (test_user_1_password, 'abcdef123', ''),
342 (test_user_1_password, 'abcdef123', ''),
359 (test_user_1_password, '', 'abcdef123'),
343 (test_user_1_password, '', 'abcdef123'),
360 (test_user_1_password, 'not_the', 'same_pw'),
344 (test_user_1_password, 'not_the', 'same_pw'),
361 (test_user_1_password, 'short', 'short'),
345 (test_user_1_password, 'short', 'short'),
362 ])
346 ])
363 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
347 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
364 user_util):
348 user_util):
365 user = user_util.create_user(password=self.test_user_1_password)
349 user = user_util.create_user(password=self.test_user_1_password)
366 session = self.log_user(user.username, self.test_user_1_password)
350 session = self.log_user(user.username, self.test_user_1_password)
367 old_password_hash = session['password']
351 old_password_hash = session['password']
368 form_data = [
352 form_data = [
369 ('current_password', current_pw),
353 ('current_password', current_pw),
370 ('__start__', 'new_password:mapping'),
354 ('__start__', 'new_password:mapping'),
371 ('new_password', new_pw),
355 ('new_password', new_pw),
372 ('new_password-confirm', confirm_pw),
356 ('new_password-confirm', confirm_pw),
373 ('__end__', 'new_password:mapping'),
357 ('__end__', 'new_password:mapping'),
374 ('csrf_token', self.csrf_token),
358 ('csrf_token', self.csrf_token),
375 ]
359 ]
376 response = self.app.post(url('my_account_password'), form_data)
360 response = self.app.post(url('my_account_password'), form_data)
377 assert 'Error occurred' in response
361 assert 'Error occurred' in response
378
362
379 def test_password_is_updated_in_session_on_password_change(self, user_util):
363 def test_password_is_updated_in_session_on_password_change(self, user_util):
380 old_password = 'abcdef123'
364 old_password = 'abcdef123'
381 new_password = 'abcdef124'
365 new_password = 'abcdef124'
382
366
383 user = user_util.create_user(password=old_password)
367 user = user_util.create_user(password=old_password)
384 session = self.log_user(user.username, old_password)
368 session = self.log_user(user.username, old_password)
385 old_password_hash = session['password']
369 old_password_hash = session['password']
386
370
387 form_data = [
371 form_data = [
388 ('current_password', old_password),
372 ('current_password', old_password),
389 ('__start__', 'new_password:mapping'),
373 ('__start__', 'new_password:mapping'),
390 ('new_password', new_password),
374 ('new_password', new_password),
391 ('new_password-confirm', new_password),
375 ('new_password-confirm', new_password),
392 ('__end__', 'new_password:mapping'),
376 ('__end__', 'new_password:mapping'),
393 ('csrf_token', self.csrf_token),
377 ('csrf_token', self.csrf_token),
394 ]
378 ]
395 self.app.post(url('my_account_password'), form_data)
379 self.app.post(url('my_account_password'), form_data)
396
380
397 response = self.app.get(url('home'))
381 response = self.app.get(url('home'))
398 new_password_hash = response.session['rhodecode_user']['password']
382 new_password_hash = response.session['rhodecode_user']['password']
399
383
400 assert old_password_hash != new_password_hash
384 assert old_password_hash != new_password_hash
@@ -1,644 +1,627
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model import validators
26 from rhodecode.model import validators
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.user import UserModel
29 from rhodecode.model.user import UserModel
30 from rhodecode.tests import (
30 from rhodecode.tests import (
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
33 from rhodecode.tests.fixture import Fixture
33 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.utils import AssertResponse
34 from rhodecode.tests.utils import AssertResponse
35
35
36 fixture = Fixture()
36 fixture = Fixture()
37
37
38
38
39 class TestAdminUsersController(TestController):
39 class TestAdminUsersController(TestController):
40 test_user_1 = 'testme'
40 test_user_1 = 'testme'
41 destroy_users = set()
41 destroy_users = set()
42
42
43 @classmethod
43 @classmethod
44 def teardown_method(cls, method):
44 def teardown_method(cls, method):
45 fixture.destroy_users(cls.destroy_users)
45 fixture.destroy_users(cls.destroy_users)
46
46
47 def test_index(self):
47 def test_index(self):
48 self.log_user()
48 self.log_user()
49 self.app.get(url('users'))
49 self.app.get(url('users'))
50
50
51 def test_create(self):
51 def test_create(self):
52 self.log_user()
52 self.log_user()
53 username = 'newtestuser'
53 username = 'newtestuser'
54 password = 'test12'
54 password = 'test12'
55 password_confirmation = password
55 password_confirmation = password
56 name = 'name'
56 name = 'name'
57 lastname = 'lastname'
57 lastname = 'lastname'
58 email = 'mail@mail.com'
58 email = 'mail@mail.com'
59
59
60 response = self.app.get(url('new_user'))
60 response = self.app.get(url('new_user'))
61
61
62 response = self.app.post(url('users'), params={
62 response = self.app.post(url('users'), params={
63 'username': username,
63 'username': username,
64 'password': password,
64 'password': password,
65 'password_confirmation': password_confirmation,
65 'password_confirmation': password_confirmation,
66 'firstname': name,
66 'firstname': name,
67 'active': True,
67 'active': True,
68 'lastname': lastname,
68 'lastname': lastname,
69 'extern_name': 'rhodecode',
69 'extern_name': 'rhodecode',
70 'extern_type': 'rhodecode',
70 'extern_type': 'rhodecode',
71 'email': email,
71 'email': email,
72 'csrf_token': self.csrf_token,
72 'csrf_token': self.csrf_token,
73 })
73 })
74 user_link = link_to(
74 user_link = link_to(
75 username,
75 username,
76 url('edit_user', user_id=User.get_by_username(username).user_id))
76 url('edit_user', user_id=User.get_by_username(username).user_id))
77 assert_session_flash(response, 'Created user %s' % (user_link,))
77 assert_session_flash(response, 'Created user %s' % (user_link,))
78 self.destroy_users.add(username)
78 self.destroy_users.add(username)
79
79
80 new_user = User.query().filter(User.username == username).one()
80 new_user = User.query().filter(User.username == username).one()
81
81
82 assert new_user.username == username
82 assert new_user.username == username
83 assert check_password(password, new_user.password)
83 assert check_password(password, new_user.password)
84 assert new_user.name == name
84 assert new_user.name == name
85 assert new_user.lastname == lastname
85 assert new_user.lastname == lastname
86 assert new_user.email == email
86 assert new_user.email == email
87
87
88 response.follow()
88 response.follow()
89 response = response.follow()
89 response = response.follow()
90 response.mustcontain(username)
90 response.mustcontain(username)
91
91
92 def test_create_err(self):
92 def test_create_err(self):
93 self.log_user()
93 self.log_user()
94 username = 'new_user'
94 username = 'new_user'
95 password = ''
95 password = ''
96 name = 'name'
96 name = 'name'
97 lastname = 'lastname'
97 lastname = 'lastname'
98 email = 'errmail.com'
98 email = 'errmail.com'
99
99
100 response = self.app.get(url('new_user'))
100 response = self.app.get(url('new_user'))
101
101
102 response = self.app.post(url('users'), params={
102 response = self.app.post(url('users'), params={
103 'username': username,
103 'username': username,
104 'password': password,
104 'password': password,
105 'name': name,
105 'name': name,
106 'active': False,
106 'active': False,
107 'lastname': lastname,
107 'lastname': lastname,
108 'email': email,
108 'email': email,
109 'csrf_token': self.csrf_token,
109 'csrf_token': self.csrf_token,
110 })
110 })
111
111
112 msg = validators.ValidUsername(
112 msg = validators.ValidUsername(
113 False, {})._messages['system_invalid_username']
113 False, {})._messages['system_invalid_username']
114 msg = h.html_escape(msg % {'username': 'new_user'})
114 msg = h.html_escape(msg % {'username': 'new_user'})
115 response.mustcontain('<span class="error-message">%s</span>' % msg)
115 response.mustcontain('<span class="error-message">%s</span>' % msg)
116 response.mustcontain(
116 response.mustcontain(
117 '<span class="error-message">Please enter a value</span>')
117 '<span class="error-message">Please enter a value</span>')
118 response.mustcontain(
118 response.mustcontain(
119 '<span class="error-message">An email address must contain a'
119 '<span class="error-message">An email address must contain a'
120 ' single @</span>')
120 ' single @</span>')
121
121
122 def get_user():
122 def get_user():
123 Session().query(User).filter(User.username == username).one()
123 Session().query(User).filter(User.username == username).one()
124
124
125 with pytest.raises(NoResultFound):
125 with pytest.raises(NoResultFound):
126 get_user()
126 get_user()
127
127
128 def test_new(self):
128 def test_new(self):
129 self.log_user()
129 self.log_user()
130 self.app.get(url('new_user'))
130 self.app.get(url('new_user'))
131
131
132 @pytest.mark.parametrize("name, attrs", [
132 @pytest.mark.parametrize("name, attrs", [
133 ('firstname', {'firstname': 'new_username'}),
133 ('firstname', {'firstname': 'new_username'}),
134 ('lastname', {'lastname': 'new_username'}),
134 ('lastname', {'lastname': 'new_username'}),
135 ('admin', {'admin': True}),
135 ('admin', {'admin': True}),
136 ('admin', {'admin': False}),
136 ('admin', {'admin': False}),
137 ('extern_type', {'extern_type': 'ldap'}),
137 ('extern_type', {'extern_type': 'ldap'}),
138 ('extern_type', {'extern_type': None}),
138 ('extern_type', {'extern_type': None}),
139 ('extern_name', {'extern_name': 'test'}),
139 ('extern_name', {'extern_name': 'test'}),
140 ('extern_name', {'extern_name': None}),
140 ('extern_name', {'extern_name': None}),
141 ('active', {'active': False}),
141 ('active', {'active': False}),
142 ('active', {'active': True}),
142 ('active', {'active': True}),
143 ('email', {'email': 'some@email.com'}),
143 ('email', {'email': 'some@email.com'}),
144 ('language', {'language': 'de'}),
144 ('language', {'language': 'de'}),
145 ('language', {'language': 'en'}),
145 ('language', {'language': 'en'}),
146 # ('new_password', {'new_password': 'foobar123',
146 # ('new_password', {'new_password': 'foobar123',
147 # 'password_confirmation': 'foobar123'})
147 # 'password_confirmation': 'foobar123'})
148 ])
148 ])
149 def test_update(self, name, attrs):
149 def test_update(self, name, attrs):
150 self.log_user()
150 self.log_user()
151 usr = fixture.create_user(self.test_user_1, password='qweqwe',
151 usr = fixture.create_user(self.test_user_1, password='qweqwe',
152 email='testme@rhodecode.org',
152 email='testme@rhodecode.org',
153 extern_type='rhodecode',
153 extern_type='rhodecode',
154 extern_name=self.test_user_1,
154 extern_name=self.test_user_1,
155 skip_if_exists=True)
155 skip_if_exists=True)
156 Session().commit()
156 Session().commit()
157 self.destroy_users.add(self.test_user_1)
157 self.destroy_users.add(self.test_user_1)
158 params = usr.get_api_data()
158 params = usr.get_api_data()
159 cur_lang = params['language'] or 'en'
159 cur_lang = params['language'] or 'en'
160 params.update({
160 params.update({
161 'password_confirmation': '',
161 'password_confirmation': '',
162 'new_password': '',
162 'new_password': '',
163 'language': cur_lang,
163 'language': cur_lang,
164 '_method': 'put',
164 '_method': 'put',
165 'csrf_token': self.csrf_token,
165 'csrf_token': self.csrf_token,
166 })
166 })
167 params.update({'new_password': ''})
167 params.update({'new_password': ''})
168 params.update(attrs)
168 params.update(attrs)
169 if name == 'email':
169 if name == 'email':
170 params['emails'] = [attrs['email']]
170 params['emails'] = [attrs['email']]
171 elif name == 'extern_type':
171 elif name == 'extern_type':
172 # cannot update this via form, expected value is original one
172 # cannot update this via form, expected value is original one
173 params['extern_type'] = "rhodecode"
173 params['extern_type'] = "rhodecode"
174 elif name == 'extern_name':
174 elif name == 'extern_name':
175 # cannot update this via form, expected value is original one
175 # cannot update this via form, expected value is original one
176 params['extern_name'] = self.test_user_1
176 params['extern_name'] = self.test_user_1
177 # special case since this user is not
177 # special case since this user is not
178 # logged in yet his data is not filled
178 # logged in yet his data is not filled
179 # so we use creation data
179 # so we use creation data
180
180
181 response = self.app.post(url('user', user_id=usr.user_id), params)
181 response = self.app.post(url('user', user_id=usr.user_id), params)
182 assert response.status_int == 302
182 assert response.status_int == 302
183 assert_session_flash(response, 'User updated successfully')
183 assert_session_flash(response, 'User updated successfully')
184
184
185 updated_user = User.get_by_username(self.test_user_1)
185 updated_user = User.get_by_username(self.test_user_1)
186 updated_params = updated_user.get_api_data()
186 updated_params = updated_user.get_api_data()
187 updated_params.update({'password_confirmation': ''})
187 updated_params.update({'password_confirmation': ''})
188 updated_params.update({'new_password': ''})
188 updated_params.update({'new_password': ''})
189
189
190 del params['_method']
190 del params['_method']
191 del params['csrf_token']
191 del params['csrf_token']
192 assert params == updated_params
192 assert params == updated_params
193
193
194 def test_update_and_migrate_password(
194 def test_update_and_migrate_password(
195 self, autologin_user, real_crypto_backend):
195 self, autologin_user, real_crypto_backend):
196 from rhodecode.lib import auth
196 from rhodecode.lib import auth
197
197
198 # create new user, with sha256 password
198 # create new user, with sha256 password
199 temp_user = 'test_admin_sha256'
199 temp_user = 'test_admin_sha256'
200 user = fixture.create_user(temp_user)
200 user = fixture.create_user(temp_user)
201 user.password = auth._RhodeCodeCryptoSha256().hash_create(
201 user.password = auth._RhodeCodeCryptoSha256().hash_create(
202 b'test123')
202 b'test123')
203 Session().add(user)
203 Session().add(user)
204 Session().commit()
204 Session().commit()
205 self.destroy_users.add('test_admin_sha256')
205 self.destroy_users.add('test_admin_sha256')
206
206
207 params = user.get_api_data()
207 params = user.get_api_data()
208
208
209 params.update({
209 params.update({
210 'password_confirmation': 'qweqwe123',
210 'password_confirmation': 'qweqwe123',
211 'new_password': 'qweqwe123',
211 'new_password': 'qweqwe123',
212 'language': 'en',
212 'language': 'en',
213 '_method': 'put',
213 '_method': 'put',
214 'csrf_token': autologin_user.csrf_token,
214 'csrf_token': autologin_user.csrf_token,
215 })
215 })
216
216
217 response = self.app.post(url('user', user_id=user.user_id), params)
217 response = self.app.post(url('user', user_id=user.user_id), params)
218 assert response.status_int == 302
218 assert response.status_int == 302
219 assert_session_flash(response, 'User updated successfully')
219 assert_session_flash(response, 'User updated successfully')
220
220
221 # new password should be bcrypted, after log-in and transfer
221 # new password should be bcrypted, after log-in and transfer
222 user = User.get_by_username(temp_user)
222 user = User.get_by_username(temp_user)
223 assert user.password.startswith('$')
223 assert user.password.startswith('$')
224
224
225 updated_user = User.get_by_username(temp_user)
225 updated_user = User.get_by_username(temp_user)
226 updated_params = updated_user.get_api_data()
226 updated_params = updated_user.get_api_data()
227 updated_params.update({'password_confirmation': 'qweqwe123'})
227 updated_params.update({'password_confirmation': 'qweqwe123'})
228 updated_params.update({'new_password': 'qweqwe123'})
228 updated_params.update({'new_password': 'qweqwe123'})
229
229
230 del params['_method']
230 del params['_method']
231 del params['csrf_token']
231 del params['csrf_token']
232 assert params == updated_params
232 assert params == updated_params
233
233
234 def test_delete(self):
234 def test_delete(self):
235 self.log_user()
235 self.log_user()
236 username = 'newtestuserdeleteme'
236 username = 'newtestuserdeleteme'
237
237
238 fixture.create_user(name=username)
238 fixture.create_user(name=username)
239
239
240 new_user = Session().query(User)\
240 new_user = Session().query(User)\
241 .filter(User.username == username).one()
241 .filter(User.username == username).one()
242 response = self.app.post(url('user', user_id=new_user.user_id),
242 response = self.app.post(url('user', user_id=new_user.user_id),
243 params={'_method': 'delete',
243 params={'_method': 'delete',
244 'csrf_token': self.csrf_token})
244 'csrf_token': self.csrf_token})
245
245
246 assert_session_flash(response, 'Successfully deleted user')
246 assert_session_flash(response, 'Successfully deleted user')
247
247
248 def test_delete_owner_of_repository(self):
248 def test_delete_owner_of_repository(self):
249 self.log_user()
249 self.log_user()
250 username = 'newtestuserdeleteme_repo_owner'
250 username = 'newtestuserdeleteme_repo_owner'
251 obj_name = 'test_repo'
251 obj_name = 'test_repo'
252 usr = fixture.create_user(name=username)
252 usr = fixture.create_user(name=username)
253 self.destroy_users.add(username)
253 self.destroy_users.add(username)
254 fixture.create_repo(obj_name, cur_user=usr.username)
254 fixture.create_repo(obj_name, cur_user=usr.username)
255
255
256 new_user = Session().query(User)\
256 new_user = Session().query(User)\
257 .filter(User.username == username).one()
257 .filter(User.username == username).one()
258 response = self.app.post(url('user', user_id=new_user.user_id),
258 response = self.app.post(url('user', user_id=new_user.user_id),
259 params={'_method': 'delete',
259 params={'_method': 'delete',
260 'csrf_token': self.csrf_token})
260 'csrf_token': self.csrf_token})
261
261
262 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
262 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
263 'Switch owners or remove those repositories:%s' % (username,
263 'Switch owners or remove those repositories:%s' % (username,
264 obj_name)
264 obj_name)
265 assert_session_flash(response, msg)
265 assert_session_flash(response, msg)
266 fixture.destroy_repo(obj_name)
266 fixture.destroy_repo(obj_name)
267
267
268 def test_delete_owner_of_repository_detaching(self):
268 def test_delete_owner_of_repository_detaching(self):
269 self.log_user()
269 self.log_user()
270 username = 'newtestuserdeleteme_repo_owner_detach'
270 username = 'newtestuserdeleteme_repo_owner_detach'
271 obj_name = 'test_repo'
271 obj_name = 'test_repo'
272 usr = fixture.create_user(name=username)
272 usr = fixture.create_user(name=username)
273 self.destroy_users.add(username)
273 self.destroy_users.add(username)
274 fixture.create_repo(obj_name, cur_user=usr.username)
274 fixture.create_repo(obj_name, cur_user=usr.username)
275
275
276 new_user = Session().query(User)\
276 new_user = Session().query(User)\
277 .filter(User.username == username).one()
277 .filter(User.username == username).one()
278 response = self.app.post(url('user', user_id=new_user.user_id),
278 response = self.app.post(url('user', user_id=new_user.user_id),
279 params={'_method': 'delete',
279 params={'_method': 'delete',
280 'user_repos': 'detach',
280 'user_repos': 'detach',
281 'csrf_token': self.csrf_token})
281 'csrf_token': self.csrf_token})
282
282
283 msg = 'Detached 1 repositories'
283 msg = 'Detached 1 repositories'
284 assert_session_flash(response, msg)
284 assert_session_flash(response, msg)
285 fixture.destroy_repo(obj_name)
285 fixture.destroy_repo(obj_name)
286
286
287 def test_delete_owner_of_repository_deleting(self):
287 def test_delete_owner_of_repository_deleting(self):
288 self.log_user()
288 self.log_user()
289 username = 'newtestuserdeleteme_repo_owner_delete'
289 username = 'newtestuserdeleteme_repo_owner_delete'
290 obj_name = 'test_repo'
290 obj_name = 'test_repo'
291 usr = fixture.create_user(name=username)
291 usr = fixture.create_user(name=username)
292 self.destroy_users.add(username)
292 self.destroy_users.add(username)
293 fixture.create_repo(obj_name, cur_user=usr.username)
293 fixture.create_repo(obj_name, cur_user=usr.username)
294
294
295 new_user = Session().query(User)\
295 new_user = Session().query(User)\
296 .filter(User.username == username).one()
296 .filter(User.username == username).one()
297 response = self.app.post(url('user', user_id=new_user.user_id),
297 response = self.app.post(url('user', user_id=new_user.user_id),
298 params={'_method': 'delete',
298 params={'_method': 'delete',
299 'user_repos': 'delete',
299 'user_repos': 'delete',
300 'csrf_token': self.csrf_token})
300 'csrf_token': self.csrf_token})
301
301
302 msg = 'Deleted 1 repositories'
302 msg = 'Deleted 1 repositories'
303 assert_session_flash(response, msg)
303 assert_session_flash(response, msg)
304
304
305 def test_delete_owner_of_repository_group(self):
305 def test_delete_owner_of_repository_group(self):
306 self.log_user()
306 self.log_user()
307 username = 'newtestuserdeleteme_repo_group_owner'
307 username = 'newtestuserdeleteme_repo_group_owner'
308 obj_name = 'test_group'
308 obj_name = 'test_group'
309 usr = fixture.create_user(name=username)
309 usr = fixture.create_user(name=username)
310 self.destroy_users.add(username)
310 self.destroy_users.add(username)
311 fixture.create_repo_group(obj_name, cur_user=usr.username)
311 fixture.create_repo_group(obj_name, cur_user=usr.username)
312
312
313 new_user = Session().query(User)\
313 new_user = Session().query(User)\
314 .filter(User.username == username).one()
314 .filter(User.username == username).one()
315 response = self.app.post(url('user', user_id=new_user.user_id),
315 response = self.app.post(url('user', user_id=new_user.user_id),
316 params={'_method': 'delete',
316 params={'_method': 'delete',
317 'csrf_token': self.csrf_token})
317 'csrf_token': self.csrf_token})
318
318
319 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
319 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
320 'Switch owners or remove those repository groups:%s' % (username,
320 'Switch owners or remove those repository groups:%s' % (username,
321 obj_name)
321 obj_name)
322 assert_session_flash(response, msg)
322 assert_session_flash(response, msg)
323 fixture.destroy_repo_group(obj_name)
323 fixture.destroy_repo_group(obj_name)
324
324
325 def test_delete_owner_of_repository_group_detaching(self):
325 def test_delete_owner_of_repository_group_detaching(self):
326 self.log_user()
326 self.log_user()
327 username = 'newtestuserdeleteme_repo_group_owner_detach'
327 username = 'newtestuserdeleteme_repo_group_owner_detach'
328 obj_name = 'test_group'
328 obj_name = 'test_group'
329 usr = fixture.create_user(name=username)
329 usr = fixture.create_user(name=username)
330 self.destroy_users.add(username)
330 self.destroy_users.add(username)
331 fixture.create_repo_group(obj_name, cur_user=usr.username)
331 fixture.create_repo_group(obj_name, cur_user=usr.username)
332
332
333 new_user = Session().query(User)\
333 new_user = Session().query(User)\
334 .filter(User.username == username).one()
334 .filter(User.username == username).one()
335 response = self.app.post(url('user', user_id=new_user.user_id),
335 response = self.app.post(url('user', user_id=new_user.user_id),
336 params={'_method': 'delete',
336 params={'_method': 'delete',
337 'user_repo_groups': 'delete',
337 'user_repo_groups': 'delete',
338 'csrf_token': self.csrf_token})
338 'csrf_token': self.csrf_token})
339
339
340 msg = 'Deleted 1 repository groups'
340 msg = 'Deleted 1 repository groups'
341 assert_session_flash(response, msg)
341 assert_session_flash(response, msg)
342
342
343 def test_delete_owner_of_repository_group_deleting(self):
343 def test_delete_owner_of_repository_group_deleting(self):
344 self.log_user()
344 self.log_user()
345 username = 'newtestuserdeleteme_repo_group_owner_delete'
345 username = 'newtestuserdeleteme_repo_group_owner_delete'
346 obj_name = 'test_group'
346 obj_name = 'test_group'
347 usr = fixture.create_user(name=username)
347 usr = fixture.create_user(name=username)
348 self.destroy_users.add(username)
348 self.destroy_users.add(username)
349 fixture.create_repo_group(obj_name, cur_user=usr.username)
349 fixture.create_repo_group(obj_name, cur_user=usr.username)
350
350
351 new_user = Session().query(User)\
351 new_user = Session().query(User)\
352 .filter(User.username == username).one()
352 .filter(User.username == username).one()
353 response = self.app.post(url('user', user_id=new_user.user_id),
353 response = self.app.post(url('user', user_id=new_user.user_id),
354 params={'_method': 'delete',
354 params={'_method': 'delete',
355 'user_repo_groups': 'detach',
355 'user_repo_groups': 'detach',
356 'csrf_token': self.csrf_token})
356 'csrf_token': self.csrf_token})
357
357
358 msg = 'Detached 1 repository groups'
358 msg = 'Detached 1 repository groups'
359 assert_session_flash(response, msg)
359 assert_session_flash(response, msg)
360 fixture.destroy_repo_group(obj_name)
360 fixture.destroy_repo_group(obj_name)
361
361
362 def test_delete_owner_of_user_group(self):
362 def test_delete_owner_of_user_group(self):
363 self.log_user()
363 self.log_user()
364 username = 'newtestuserdeleteme_user_group_owner'
364 username = 'newtestuserdeleteme_user_group_owner'
365 obj_name = 'test_user_group'
365 obj_name = 'test_user_group'
366 usr = fixture.create_user(name=username)
366 usr = fixture.create_user(name=username)
367 self.destroy_users.add(username)
367 self.destroy_users.add(username)
368 fixture.create_user_group(obj_name, cur_user=usr.username)
368 fixture.create_user_group(obj_name, cur_user=usr.username)
369
369
370 new_user = Session().query(User)\
370 new_user = Session().query(User)\
371 .filter(User.username == username).one()
371 .filter(User.username == username).one()
372 response = self.app.post(url('user', user_id=new_user.user_id),
372 response = self.app.post(url('user', user_id=new_user.user_id),
373 params={'_method': 'delete',
373 params={'_method': 'delete',
374 'csrf_token': self.csrf_token})
374 'csrf_token': self.csrf_token})
375
375
376 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
376 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
377 'Switch owners or remove those user groups:%s' % (username,
377 'Switch owners or remove those user groups:%s' % (username,
378 obj_name)
378 obj_name)
379 assert_session_flash(response, msg)
379 assert_session_flash(response, msg)
380 fixture.destroy_user_group(obj_name)
380 fixture.destroy_user_group(obj_name)
381
381
382 def test_delete_owner_of_user_group_detaching(self):
382 def test_delete_owner_of_user_group_detaching(self):
383 self.log_user()
383 self.log_user()
384 username = 'newtestuserdeleteme_user_group_owner_detaching'
384 username = 'newtestuserdeleteme_user_group_owner_detaching'
385 obj_name = 'test_user_group'
385 obj_name = 'test_user_group'
386 usr = fixture.create_user(name=username)
386 usr = fixture.create_user(name=username)
387 self.destroy_users.add(username)
387 self.destroy_users.add(username)
388 fixture.create_user_group(obj_name, cur_user=usr.username)
388 fixture.create_user_group(obj_name, cur_user=usr.username)
389
389
390 new_user = Session().query(User)\
390 new_user = Session().query(User)\
391 .filter(User.username == username).one()
391 .filter(User.username == username).one()
392 try:
392 try:
393 response = self.app.post(url('user', user_id=new_user.user_id),
393 response = self.app.post(url('user', user_id=new_user.user_id),
394 params={'_method': 'delete',
394 params={'_method': 'delete',
395 'user_user_groups': 'detach',
395 'user_user_groups': 'detach',
396 'csrf_token': self.csrf_token})
396 'csrf_token': self.csrf_token})
397
397
398 msg = 'Detached 1 user groups'
398 msg = 'Detached 1 user groups'
399 assert_session_flash(response, msg)
399 assert_session_flash(response, msg)
400 finally:
400 finally:
401 fixture.destroy_user_group(obj_name)
401 fixture.destroy_user_group(obj_name)
402
402
403 def test_delete_owner_of_user_group_deleting(self):
403 def test_delete_owner_of_user_group_deleting(self):
404 self.log_user()
404 self.log_user()
405 username = 'newtestuserdeleteme_user_group_owner_deleting'
405 username = 'newtestuserdeleteme_user_group_owner_deleting'
406 obj_name = 'test_user_group'
406 obj_name = 'test_user_group'
407 usr = fixture.create_user(name=username)
407 usr = fixture.create_user(name=username)
408 self.destroy_users.add(username)
408 self.destroy_users.add(username)
409 fixture.create_user_group(obj_name, cur_user=usr.username)
409 fixture.create_user_group(obj_name, cur_user=usr.username)
410
410
411 new_user = Session().query(User)\
411 new_user = Session().query(User)\
412 .filter(User.username == username).one()
412 .filter(User.username == username).one()
413 response = self.app.post(url('user', user_id=new_user.user_id),
413 response = self.app.post(url('user', user_id=new_user.user_id),
414 params={'_method': 'delete',
414 params={'_method': 'delete',
415 'user_user_groups': 'delete',
415 'user_user_groups': 'delete',
416 'csrf_token': self.csrf_token})
416 'csrf_token': self.csrf_token})
417
417
418 msg = 'Deleted 1 user groups'
418 msg = 'Deleted 1 user groups'
419 assert_session_flash(response, msg)
419 assert_session_flash(response, msg)
420
420
421 def test_show(self):
421 def test_show(self):
422 self.app.get(url('user', user_id=1))
422 self.app.get(url('user', user_id=1))
423
423
424 def test_edit(self):
424 def test_edit(self):
425 self.log_user()
425 self.log_user()
426 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
426 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
427 self.app.get(url('edit_user', user_id=user.user_id))
427 self.app.get(url('edit_user', user_id=user.user_id))
428
428
429 @pytest.mark.parametrize(
429 @pytest.mark.parametrize(
430 'repo_create, repo_create_write, user_group_create, repo_group_create,'
430 'repo_create, repo_create_write, user_group_create, repo_group_create,'
431 'fork_create, inherit_default_permissions, expect_error,'
431 'fork_create, inherit_default_permissions, expect_error,'
432 'expect_form_error', [
432 'expect_form_error', [
433 ('hg.create.none', 'hg.create.write_on_repogroup.false',
433 ('hg.create.none', 'hg.create.write_on_repogroup.false',
434 'hg.usergroup.create.false', 'hg.repogroup.create.false',
434 'hg.usergroup.create.false', 'hg.repogroup.create.false',
435 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
435 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
436 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
436 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
437 'hg.usergroup.create.false', 'hg.repogroup.create.false',
437 'hg.usergroup.create.false', 'hg.repogroup.create.false',
438 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
438 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
439 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
439 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
440 'hg.usergroup.create.true', 'hg.repogroup.create.true',
440 'hg.usergroup.create.true', 'hg.repogroup.create.true',
441 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
441 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
442 False),
442 False),
443 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
443 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
444 'hg.usergroup.create.true', 'hg.repogroup.create.true',
444 'hg.usergroup.create.true', 'hg.repogroup.create.true',
445 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
445 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
446 True),
446 True),
447 ('', '', '', '', '', '', True, False),
447 ('', '', '', '', '', '', True, False),
448 ])
448 ])
449 def test_global_perms_on_user(
449 def test_global_perms_on_user(
450 self, repo_create, repo_create_write, user_group_create,
450 self, repo_create, repo_create_write, user_group_create,
451 repo_group_create, fork_create, expect_error, expect_form_error,
451 repo_group_create, fork_create, expect_error, expect_form_error,
452 inherit_default_permissions):
452 inherit_default_permissions):
453 self.log_user()
453 self.log_user()
454 user = fixture.create_user('dummy')
454 user = fixture.create_user('dummy')
455 uid = user.user_id
455 uid = user.user_id
456
456
457 # ENABLE REPO CREATE ON A GROUP
457 # ENABLE REPO CREATE ON A GROUP
458 perm_params = {
458 perm_params = {
459 'inherit_default_permissions': False,
459 'inherit_default_permissions': False,
460 'default_repo_create': repo_create,
460 'default_repo_create': repo_create,
461 'default_repo_create_on_write': repo_create_write,
461 'default_repo_create_on_write': repo_create_write,
462 'default_user_group_create': user_group_create,
462 'default_user_group_create': user_group_create,
463 'default_repo_group_create': repo_group_create,
463 'default_repo_group_create': repo_group_create,
464 'default_fork_create': fork_create,
464 'default_fork_create': fork_create,
465 'default_inherit_default_permissions': inherit_default_permissions,
465 'default_inherit_default_permissions': inherit_default_permissions,
466 '_method': 'put',
466 '_method': 'put',
467 'csrf_token': self.csrf_token,
467 'csrf_token': self.csrf_token,
468 }
468 }
469 response = self.app.post(
469 response = self.app.post(
470 url('edit_user_global_perms', user_id=uid),
470 url('edit_user_global_perms', user_id=uid),
471 params=perm_params)
471 params=perm_params)
472
472
473 if expect_form_error:
473 if expect_form_error:
474 assert response.status_int == 200
474 assert response.status_int == 200
475 response.mustcontain('Value must be one of')
475 response.mustcontain('Value must be one of')
476 else:
476 else:
477 if expect_error:
477 if expect_error:
478 msg = 'An error occurred during permissions saving'
478 msg = 'An error occurred during permissions saving'
479 else:
479 else:
480 msg = 'User global permissions updated successfully'
480 msg = 'User global permissions updated successfully'
481 ug = User.get(uid)
481 ug = User.get(uid)
482 del perm_params['_method']
482 del perm_params['_method']
483 del perm_params['inherit_default_permissions']
483 del perm_params['inherit_default_permissions']
484 del perm_params['csrf_token']
484 del perm_params['csrf_token']
485 assert perm_params == ug.get_default_perms()
485 assert perm_params == ug.get_default_perms()
486 assert_session_flash(response, msg)
486 assert_session_flash(response, msg)
487 fixture.destroy_user(uid)
487 fixture.destroy_user(uid)
488
488
489 def test_global_permissions_initial_values(self, user_util):
489 def test_global_permissions_initial_values(self, user_util):
490 self.log_user()
490 self.log_user()
491 user = user_util.create_user()
491 user = user_util.create_user()
492 uid = user.user_id
492 uid = user.user_id
493 response = self.app.get(url('edit_user_global_perms', user_id=uid))
493 response = self.app.get(url('edit_user_global_perms', user_id=uid))
494 default_user = User.get_default_user()
494 default_user = User.get_default_user()
495 default_permissions = default_user.get_default_perms()
495 default_permissions = default_user.get_default_perms()
496 assert_response = AssertResponse(response)
496 assert_response = AssertResponse(response)
497 expected_permissions = (
497 expected_permissions = (
498 'default_repo_create', 'default_repo_create_on_write',
498 'default_repo_create', 'default_repo_create_on_write',
499 'default_fork_create', 'default_repo_group_create',
499 'default_fork_create', 'default_repo_group_create',
500 'default_user_group_create', 'default_inherit_default_permissions')
500 'default_user_group_create', 'default_inherit_default_permissions')
501 for permission in expected_permissions:
501 for permission in expected_permissions:
502 css_selector = '[name={}][checked=checked]'.format(permission)
502 css_selector = '[name={}][checked=checked]'.format(permission)
503 element = assert_response.get_element(css_selector)
503 element = assert_response.get_element(css_selector)
504 assert element.value == default_permissions[permission]
504 assert element.value == default_permissions[permission]
505
505
506 def test_ips(self):
506 def test_ips(self):
507 self.log_user()
507 self.log_user()
508 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
508 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
509 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
509 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
510 response.mustcontain('All IP addresses are allowed')
510 response.mustcontain('All IP addresses are allowed')
511
511
512 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
512 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
513 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
513 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
514 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
514 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
515 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
515 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
516 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
516 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
517 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
517 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
518 ('127_bad_ip', 'foobar', 'foobar', True),
518 ('127_bad_ip', 'foobar', 'foobar', True),
519 ])
519 ])
520 def test_add_ip(self, test_name, ip, ip_range, failure):
520 def test_add_ip(self, test_name, ip, ip_range, failure):
521 self.log_user()
521 self.log_user()
522 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
522 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
523 user_id = user.user_id
523 user_id = user.user_id
524
524
525 response = self.app.post(url('edit_user_ips', user_id=user_id),
525 response = self.app.post(url('edit_user_ips', user_id=user_id),
526 params={'new_ip': ip, '_method': 'put',
526 params={'new_ip': ip, '_method': 'put',
527 'csrf_token': self.csrf_token})
527 'csrf_token': self.csrf_token})
528
528
529 if failure:
529 if failure:
530 assert_session_flash(
530 assert_session_flash(
531 response, 'Please enter a valid IPv4 or IpV6 address')
531 response, 'Please enter a valid IPv4 or IpV6 address')
532 response = self.app.get(url('edit_user_ips', user_id=user_id))
532 response = self.app.get(url('edit_user_ips', user_id=user_id))
533 response.mustcontain(no=[ip])
533 response.mustcontain(no=[ip])
534 response.mustcontain(no=[ip_range])
534 response.mustcontain(no=[ip_range])
535
535
536 else:
536 else:
537 response = self.app.get(url('edit_user_ips', user_id=user_id))
537 response = self.app.get(url('edit_user_ips', user_id=user_id))
538 response.mustcontain(ip)
538 response.mustcontain(ip)
539 response.mustcontain(ip_range)
539 response.mustcontain(ip_range)
540
540
541 # cleanup
541 # cleanup
542 for del_ip in UserIpMap.query().filter(
542 for del_ip in UserIpMap.query().filter(
543 UserIpMap.user_id == user_id).all():
543 UserIpMap.user_id == user_id).all():
544 Session().delete(del_ip)
544 Session().delete(del_ip)
545 Session().commit()
545 Session().commit()
546
546
547 def test_delete_ip(self):
547 def test_delete_ip(self):
548 self.log_user()
548 self.log_user()
549 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
549 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
550 user_id = user.user_id
550 user_id = user.user_id
551 ip = '127.0.0.1/32'
551 ip = '127.0.0.1/32'
552 ip_range = '127.0.0.1 - 127.0.0.1'
552 ip_range = '127.0.0.1 - 127.0.0.1'
553 new_ip = UserModel().add_extra_ip(user_id, ip)
553 new_ip = UserModel().add_extra_ip(user_id, ip)
554 Session().commit()
554 Session().commit()
555 new_ip_id = new_ip.ip_id
555 new_ip_id = new_ip.ip_id
556
556
557 response = self.app.get(url('edit_user_ips', user_id=user_id))
557 response = self.app.get(url('edit_user_ips', user_id=user_id))
558 response.mustcontain(ip)
558 response.mustcontain(ip)
559 response.mustcontain(ip_range)
559 response.mustcontain(ip_range)
560
560
561 self.app.post(url('edit_user_ips', user_id=user_id),
561 self.app.post(url('edit_user_ips', user_id=user_id),
562 params={'_method': 'delete', 'del_ip_id': new_ip_id,
562 params={'_method': 'delete', 'del_ip_id': new_ip_id,
563 'csrf_token': self.csrf_token})
563 'csrf_token': self.csrf_token})
564
564
565 response = self.app.get(url('edit_user_ips', user_id=user_id))
565 response = self.app.get(url('edit_user_ips', user_id=user_id))
566 response.mustcontain('All IP addresses are allowed')
566 response.mustcontain('All IP addresses are allowed')
567 response.mustcontain(no=[ip])
567 response.mustcontain(no=[ip])
568 response.mustcontain(no=[ip_range])
568 response.mustcontain(no=[ip_range])
569
569
570 def test_auth_tokens(self):
570 def test_auth_tokens(self):
571 self.log_user()
571 self.log_user()
572
572
573 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
573 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
574 response = self.app.get(
574 response = self.app.get(
575 url('edit_user_auth_tokens', user_id=user.user_id))
575 url('edit_user_auth_tokens', user_id=user.user_id))
576 response.mustcontain(user.api_key)
576 response.mustcontain(user.api_key)
577 response.mustcontain('expires: never')
577 response.mustcontain('expires: never')
578
578
579 @pytest.mark.parametrize("desc, lifetime", [
579 @pytest.mark.parametrize("desc, lifetime", [
580 ('forever', -1),
580 ('forever', -1),
581 ('5mins', 60*5),
581 ('5mins', 60*5),
582 ('30days', 60*60*24*30),
582 ('30days', 60*60*24*30),
583 ])
583 ])
584 def test_add_auth_token(self, desc, lifetime):
584 def test_add_auth_token(self, desc, lifetime):
585 self.log_user()
585 self.log_user()
586 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
586 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
587 user_id = user.user_id
587 user_id = user.user_id
588
588
589 response = self.app.post(
589 response = self.app.post(
590 url('edit_user_auth_tokens', user_id=user_id),
590 url('edit_user_auth_tokens', user_id=user_id),
591 {'_method': 'put', 'description': desc, 'lifetime': lifetime,
591 {'_method': 'put', 'description': desc, 'lifetime': lifetime,
592 'csrf_token': self.csrf_token})
592 'csrf_token': self.csrf_token})
593 assert_session_flash(response, 'Auth token successfully created')
593 assert_session_flash(response, 'Auth token successfully created')
594 try:
594 try:
595 response = response.follow()
595 response = response.follow()
596 user = User.get(user_id)
596 user = User.get(user_id)
597 for auth_token in user.auth_tokens:
597 for auth_token in user.auth_tokens:
598 response.mustcontain(auth_token)
598 response.mustcontain(auth_token)
599 finally:
599 finally:
600 for api_key in UserApiKeys.query().filter(
600 for api_key in UserApiKeys.query().filter(
601 UserApiKeys.user_id == user_id).all():
601 UserApiKeys.user_id == user_id).all():
602 Session().delete(api_key)
602 Session().delete(api_key)
603 Session().commit()
603 Session().commit()
604
604
605 def test_remove_auth_token(self):
605 def test_remove_auth_token(self):
606 self.log_user()
606 self.log_user()
607 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
607 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
608 user_id = user.user_id
608 user_id = user.user_id
609
609
610 response = self.app.post(
610 response = self.app.post(
611 url('edit_user_auth_tokens', user_id=user_id),
611 url('edit_user_auth_tokens', user_id=user_id),
612 {'_method': 'put', 'description': 'desc', 'lifetime': -1,
612 {'_method': 'put', 'description': 'desc', 'lifetime': -1,
613 'csrf_token': self.csrf_token})
613 'csrf_token': self.csrf_token})
614 assert_session_flash(response, 'Auth token successfully created')
614 assert_session_flash(response, 'Auth token successfully created')
615 response = response.follow()
615 response = response.follow()
616
616
617 # now delete our key
617 # now delete our key
618 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
618 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
619 assert 1 == len(keys)
619 assert 1 == len(keys)
620
620
621 response = self.app.post(
621 response = self.app.post(
622 url('edit_user_auth_tokens', user_id=user_id),
622 url('edit_user_auth_tokens', user_id=user_id),
623 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
623 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
624 'csrf_token': self.csrf_token})
624 'csrf_token': self.csrf_token})
625 assert_session_flash(response, 'Auth token successfully deleted')
625 assert_session_flash(response, 'Auth token successfully deleted')
626 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
626 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
627 assert 0 == len(keys)
627 assert 0 == len(keys)
628
629 def test_reset_main_auth_token(self):
630 self.log_user()
631 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
632 user_id = user.user_id
633 api_key = user.api_key
634 response = self.app.get(url('edit_user_auth_tokens', user_id=user_id))
635 response.mustcontain(api_key)
636 response.mustcontain('expires: never')
637
638 response = self.app.post(
639 url('edit_user_auth_tokens', user_id=user_id),
640 {'_method': 'delete', 'del_auth_token_builtin': api_key,
641 'csrf_token': self.csrf_token})
642 assert_session_flash(response, 'Auth token successfully reset')
643 response = response.follow()
644 response.mustcontain(no=[api_key])
General Comments 0
You need to be logged in to leave comments. Login now