##// END OF EJS Templates
refactor: renamed get_first_admin to get_first_super_admin which...
marcink -
r278:25d706fe default
parent child Browse files
Show More
@@ -1,406 +1,406 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Repository groups controller for RhodeCode
23 Repository groups controller for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 import formencode
27 import formencode
28
28
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from pylons import request, tmpl_context as c, url
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _, ungettext
33 from pylons.i18n.translation import _, ungettext
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, NotAnonymous, HasPermissionAll,
39 LoginRequired, NotAnonymous, HasPermissionAll,
40 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
40 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.db import RepoGroup, User
42 from rhodecode.model.db import RepoGroup, User
43 from rhodecode.model.scm import RepoGroupList
43 from rhodecode.model.scm import RepoGroupList
44 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
45 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.lib.utils2 import safe_int
47 from rhodecode.lib.utils2 import safe_int
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class RepoGroupsController(BaseController):
53 class RepoGroupsController(BaseController):
54 """REST Controller styled on the Atom Publishing Protocol"""
54 """REST Controller styled on the Atom Publishing Protocol"""
55
55
56 @LoginRequired()
56 @LoginRequired()
57 def __before__(self):
57 def __before__(self):
58 super(RepoGroupsController, self).__before__()
58 super(RepoGroupsController, self).__before__()
59
59
60 def __load_defaults(self, allow_empty_group=False, repo_group=None):
60 def __load_defaults(self, allow_empty_group=False, repo_group=None):
61 if self._can_create_repo_group():
61 if self._can_create_repo_group():
62 # we're global admin, we're ok and we can create TOP level groups
62 # we're global admin, we're ok and we can create TOP level groups
63 allow_empty_group = True
63 allow_empty_group = True
64
64
65 # override the choices for this form, we need to filter choices
65 # override the choices for this form, we need to filter choices
66 # and display only those we have ADMIN right
66 # and display only those we have ADMIN right
67 groups_with_admin_rights = RepoGroupList(
67 groups_with_admin_rights = RepoGroupList(
68 RepoGroup.query().all(),
68 RepoGroup.query().all(),
69 perm_set=['group.admin'])
69 perm_set=['group.admin'])
70 c.repo_groups = RepoGroup.groups_choices(
70 c.repo_groups = RepoGroup.groups_choices(
71 groups=groups_with_admin_rights,
71 groups=groups_with_admin_rights,
72 show_empty_group=allow_empty_group)
72 show_empty_group=allow_empty_group)
73
73
74 if repo_group:
74 if repo_group:
75 # exclude filtered ids
75 # exclude filtered ids
76 exclude_group_ids = [repo_group.group_id]
76 exclude_group_ids = [repo_group.group_id]
77 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
77 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
78 c.repo_groups)
78 c.repo_groups)
79 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
79 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 parent_group = repo_group.parent_group
80 parent_group = repo_group.parent_group
81
81
82 add_parent_group = (parent_group and (
82 add_parent_group = (parent_group and (
83 unicode(parent_group.group_id) not in c.repo_groups_choices))
83 unicode(parent_group.group_id) not in c.repo_groups_choices))
84 if add_parent_group:
84 if add_parent_group:
85 c.repo_groups_choices.append(unicode(parent_group.group_id))
85 c.repo_groups_choices.append(unicode(parent_group.group_id))
86 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
86 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
87
87
88 def __load_data(self, group_id):
88 def __load_data(self, group_id):
89 """
89 """
90 Load defaults settings for edit, and update
90 Load defaults settings for edit, and update
91
91
92 :param group_id:
92 :param group_id:
93 """
93 """
94 repo_group = RepoGroup.get_or_404(group_id)
94 repo_group = RepoGroup.get_or_404(group_id)
95 data = repo_group.get_dict()
95 data = repo_group.get_dict()
96 data['group_name'] = repo_group.name
96 data['group_name'] = repo_group.name
97
97
98 # fill owner
98 # fill owner
99 if repo_group.user:
99 if repo_group.user:
100 data.update({'user': repo_group.user.username})
100 data.update({'user': repo_group.user.username})
101 else:
101 else:
102 replacement_user = User.get_first_admin().username
102 replacement_user = User.get_first_super_admin().username
103 data.update({'user': replacement_user})
103 data.update({'user': replacement_user})
104
104
105 # fill repository group users
105 # fill repository group users
106 for p in repo_group.repo_group_to_perm:
106 for p in repo_group.repo_group_to_perm:
107 data.update({
107 data.update({
108 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
108 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
109
109
110 # fill repository group user groups
110 # fill repository group user groups
111 for p in repo_group.users_group_to_perm:
111 for p in repo_group.users_group_to_perm:
112 data.update({
112 data.update({
113 'g_perm_%s' % p.users_group.users_group_id:
113 'g_perm_%s' % p.users_group.users_group_id:
114 p.permission.permission_name})
114 p.permission.permission_name})
115 # html and form expects -1 as empty parent group
115 # html and form expects -1 as empty parent group
116 data['group_parent_id'] = data['group_parent_id'] or -1
116 data['group_parent_id'] = data['group_parent_id'] or -1
117 return data
117 return data
118
118
119 def _revoke_perms_on_yourself(self, form_result):
119 def _revoke_perms_on_yourself(self, form_result):
120 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
120 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
121 form_result['perm_updates'])
121 form_result['perm_updates'])
122 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
122 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
123 form_result['perm_additions'])
123 form_result['perm_additions'])
124 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
124 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
125 form_result['perm_deletions'])
125 form_result['perm_deletions'])
126 admin_perm = 'group.admin'
126 admin_perm = 'group.admin'
127 if _updates and _updates[0][1] != admin_perm or \
127 if _updates and _updates[0][1] != admin_perm or \
128 _additions and _additions[0][1] != admin_perm or \
128 _additions and _additions[0][1] != admin_perm or \
129 _deletions and _deletions[0][1] != admin_perm:
129 _deletions and _deletions[0][1] != admin_perm:
130 return True
130 return True
131 return False
131 return False
132
132
133 def _can_create_repo_group(self, parent_group_id=None):
133 def _can_create_repo_group(self, parent_group_id=None):
134 is_admin = HasPermissionAll('hg.admin')('group create controller')
134 is_admin = HasPermissionAll('hg.admin')('group create controller')
135 create_repo_group = HasPermissionAll(
135 create_repo_group = HasPermissionAll(
136 'hg.repogroup.create.true')('group create controller')
136 'hg.repogroup.create.true')('group create controller')
137 if is_admin or (create_repo_group and not parent_group_id):
137 if is_admin or (create_repo_group and not parent_group_id):
138 # we're global admin, or we have global repo group create
138 # we're global admin, or we have global repo group create
139 # permission
139 # permission
140 # we're ok and we can create TOP level groups
140 # we're ok and we can create TOP level groups
141 return True
141 return True
142 elif parent_group_id:
142 elif parent_group_id:
143 # we check the permission if we can write to parent group
143 # we check the permission if we can write to parent group
144 group = RepoGroup.get(parent_group_id)
144 group = RepoGroup.get(parent_group_id)
145 group_name = group.group_name if group else None
145 group_name = group.group_name if group else None
146 if HasRepoGroupPermissionAll('group.admin')(
146 if HasRepoGroupPermissionAll('group.admin')(
147 group_name, 'check if user is an admin of group'):
147 group_name, 'check if user is an admin of group'):
148 # we're an admin of passed in group, we're ok.
148 # we're an admin of passed in group, we're ok.
149 return True
149 return True
150 else:
150 else:
151 return False
151 return False
152 return False
152 return False
153
153
154 @NotAnonymous()
154 @NotAnonymous()
155 def index(self):
155 def index(self):
156 """GET /repo_groups: All items in the collection"""
156 """GET /repo_groups: All items in the collection"""
157 # url('repo_groups')
157 # url('repo_groups')
158
158
159 repo_group_list = RepoGroup.get_all_repo_groups()
159 repo_group_list = RepoGroup.get_all_repo_groups()
160 _perms = ['group.admin']
160 _perms = ['group.admin']
161 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
161 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
162 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
162 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
163 repo_group_list=repo_group_list_acl, admin=True)
163 repo_group_list=repo_group_list_acl, admin=True)
164 c.data = json.dumps(repo_group_data)
164 c.data = json.dumps(repo_group_data)
165 return render('admin/repo_groups/repo_groups.html')
165 return render('admin/repo_groups/repo_groups.html')
166
166
167 # perm checks inside
167 # perm checks inside
168 @NotAnonymous()
168 @NotAnonymous()
169 @auth.CSRFRequired()
169 @auth.CSRFRequired()
170 def create(self):
170 def create(self):
171 """POST /repo_groups: Create a new item"""
171 """POST /repo_groups: Create a new item"""
172 # url('repo_groups')
172 # url('repo_groups')
173
173
174 parent_group_id = safe_int(request.POST.get('group_parent_id'))
174 parent_group_id = safe_int(request.POST.get('group_parent_id'))
175 can_create = self._can_create_repo_group(parent_group_id)
175 can_create = self._can_create_repo_group(parent_group_id)
176
176
177 self.__load_defaults()
177 self.__load_defaults()
178 # permissions for can create group based on parent_id are checked
178 # permissions for can create group based on parent_id are checked
179 # here in the Form
179 # here in the Form
180 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
180 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
181 repo_group_form = RepoGroupForm(available_groups=available_groups,
181 repo_group_form = RepoGroupForm(available_groups=available_groups,
182 can_create_in_root=can_create)()
182 can_create_in_root=can_create)()
183 try:
183 try:
184 owner = c.rhodecode_user
184 owner = c.rhodecode_user
185 form_result = repo_group_form.to_python(dict(request.POST))
185 form_result = repo_group_form.to_python(dict(request.POST))
186 RepoGroupModel().create(
186 RepoGroupModel().create(
187 group_name=form_result['group_name_full'],
187 group_name=form_result['group_name_full'],
188 group_description=form_result['group_description'],
188 group_description=form_result['group_description'],
189 owner=owner.user_id,
189 owner=owner.user_id,
190 copy_permissions=form_result['group_copy_permissions']
190 copy_permissions=form_result['group_copy_permissions']
191 )
191 )
192 Session().commit()
192 Session().commit()
193 _new_group_name = form_result['group_name_full']
193 _new_group_name = form_result['group_name_full']
194 repo_group_url = h.link_to(
194 repo_group_url = h.link_to(
195 _new_group_name,
195 _new_group_name,
196 h.url('repo_group_home', group_name=_new_group_name))
196 h.url('repo_group_home', group_name=_new_group_name))
197 h.flash(h.literal(_('Created repository group %s')
197 h.flash(h.literal(_('Created repository group %s')
198 % repo_group_url), category='success')
198 % repo_group_url), category='success')
199 # TODO: in futureaction_logger(, '', '', '', self.sa)
199 # TODO: in futureaction_logger(, '', '', '', self.sa)
200 except formencode.Invalid as errors:
200 except formencode.Invalid as errors:
201 return htmlfill.render(
201 return htmlfill.render(
202 render('admin/repo_groups/repo_group_add.html'),
202 render('admin/repo_groups/repo_group_add.html'),
203 defaults=errors.value,
203 defaults=errors.value,
204 errors=errors.error_dict or {},
204 errors=errors.error_dict or {},
205 prefix_error=False,
205 prefix_error=False,
206 encoding="UTF-8",
206 encoding="UTF-8",
207 force_defaults=False)
207 force_defaults=False)
208 except Exception:
208 except Exception:
209 log.exception("Exception during creation of repository group")
209 log.exception("Exception during creation of repository group")
210 h.flash(_('Error occurred during creation of repository group %s')
210 h.flash(_('Error occurred during creation of repository group %s')
211 % request.POST.get('group_name'), category='error')
211 % request.POST.get('group_name'), category='error')
212
212
213 # TODO: maybe we should get back to the main view, not the admin one
213 # TODO: maybe we should get back to the main view, not the admin one
214 return redirect(url('repo_groups', parent_group=parent_group_id))
214 return redirect(url('repo_groups', parent_group=parent_group_id))
215
215
216 # perm checks inside
216 # perm checks inside
217 @NotAnonymous()
217 @NotAnonymous()
218 def new(self):
218 def new(self):
219 """GET /repo_groups/new: Form to create a new item"""
219 """GET /repo_groups/new: Form to create a new item"""
220 # url('new_repo_group')
220 # url('new_repo_group')
221 # perm check for admin, create_group perm or admin of parent_group
221 # perm check for admin, create_group perm or admin of parent_group
222 parent_group_id = safe_int(request.GET.get('parent_group'))
222 parent_group_id = safe_int(request.GET.get('parent_group'))
223 if not self._can_create_repo_group(parent_group_id):
223 if not self._can_create_repo_group(parent_group_id):
224 return abort(403)
224 return abort(403)
225
225
226 self.__load_defaults()
226 self.__load_defaults()
227 return render('admin/repo_groups/repo_group_add.html')
227 return render('admin/repo_groups/repo_group_add.html')
228
228
229 @HasRepoGroupPermissionAnyDecorator('group.admin')
229 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 @auth.CSRFRequired()
230 @auth.CSRFRequired()
231 def update(self, group_name):
231 def update(self, group_name):
232 """PUT /repo_groups/group_name: Update an existing item"""
232 """PUT /repo_groups/group_name: Update an existing item"""
233 # Forms posted to this method should contain a hidden field:
233 # Forms posted to this method should contain a hidden field:
234 # <input type="hidden" name="_method" value="PUT" />
234 # <input type="hidden" name="_method" value="PUT" />
235 # Or using helpers:
235 # Or using helpers:
236 # h.form(url('repos_group', group_name=GROUP_NAME), method='put')
236 # h.form(url('repos_group', group_name=GROUP_NAME), method='put')
237 # url('repo_group_home', group_name=GROUP_NAME)
237 # url('repo_group_home', group_name=GROUP_NAME)
238
238
239 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
239 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
240 can_create_in_root = self._can_create_repo_group()
240 can_create_in_root = self._can_create_repo_group()
241 show_root_location = can_create_in_root
241 show_root_location = can_create_in_root
242 if not c.repo_group.parent_group:
242 if not c.repo_group.parent_group:
243 # this group don't have a parrent so we should show empty value
243 # this group don't have a parrent so we should show empty value
244 show_root_location = True
244 show_root_location = True
245 self.__load_defaults(allow_empty_group=show_root_location,
245 self.__load_defaults(allow_empty_group=show_root_location,
246 repo_group=c.repo_group)
246 repo_group=c.repo_group)
247
247
248 repo_group_form = RepoGroupForm(
248 repo_group_form = RepoGroupForm(
249 edit=True, old_data=c.repo_group.get_dict(),
249 edit=True, old_data=c.repo_group.get_dict(),
250 available_groups=c.repo_groups_choices,
250 available_groups=c.repo_groups_choices,
251 can_create_in_root=can_create_in_root, allow_disabled=True)()
251 can_create_in_root=can_create_in_root, allow_disabled=True)()
252
252
253 try:
253 try:
254 form_result = repo_group_form.to_python(dict(request.POST))
254 form_result = repo_group_form.to_python(dict(request.POST))
255 gr_name = form_result['group_name']
255 gr_name = form_result['group_name']
256 new_gr = RepoGroupModel().update(group_name, form_result)
256 new_gr = RepoGroupModel().update(group_name, form_result)
257 Session().commit()
257 Session().commit()
258 h.flash(_('Updated repository group %s') % (gr_name,),
258 h.flash(_('Updated repository group %s') % (gr_name,),
259 category='success')
259 category='success')
260 # we now have new name !
260 # we now have new name !
261 group_name = new_gr.group_name
261 group_name = new_gr.group_name
262 # TODO: in future action_logger(, '', '', '', self.sa)
262 # TODO: in future action_logger(, '', '', '', self.sa)
263 except formencode.Invalid as errors:
263 except formencode.Invalid as errors:
264 c.active = 'settings'
264 c.active = 'settings'
265 return htmlfill.render(
265 return htmlfill.render(
266 render('admin/repo_groups/repo_group_edit.html'),
266 render('admin/repo_groups/repo_group_edit.html'),
267 defaults=errors.value,
267 defaults=errors.value,
268 errors=errors.error_dict or {},
268 errors=errors.error_dict or {},
269 prefix_error=False,
269 prefix_error=False,
270 encoding="UTF-8",
270 encoding="UTF-8",
271 force_defaults=False)
271 force_defaults=False)
272 except Exception:
272 except Exception:
273 log.exception("Exception during update or repository group")
273 log.exception("Exception during update or repository group")
274 h.flash(_('Error occurred during update of repository group %s')
274 h.flash(_('Error occurred during update of repository group %s')
275 % request.POST.get('group_name'), category='error')
275 % request.POST.get('group_name'), category='error')
276
276
277 return redirect(url('edit_repo_group', group_name=group_name))
277 return redirect(url('edit_repo_group', group_name=group_name))
278
278
279 @HasRepoGroupPermissionAnyDecorator('group.admin')
279 @HasRepoGroupPermissionAnyDecorator('group.admin')
280 @auth.CSRFRequired()
280 @auth.CSRFRequired()
281 def delete(self, group_name):
281 def delete(self, group_name):
282 """DELETE /repo_groups/group_name: Delete an existing item"""
282 """DELETE /repo_groups/group_name: Delete an existing item"""
283 # Forms posted to this method should contain a hidden field:
283 # Forms posted to this method should contain a hidden field:
284 # <input type="hidden" name="_method" value="DELETE" />
284 # <input type="hidden" name="_method" value="DELETE" />
285 # Or using helpers:
285 # Or using helpers:
286 # h.form(url('repos_group', group_name=GROUP_NAME), method='delete')
286 # h.form(url('repos_group', group_name=GROUP_NAME), method='delete')
287 # url('repo_group_home', group_name=GROUP_NAME)
287 # url('repo_group_home', group_name=GROUP_NAME)
288
288
289 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
289 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
290 repos = gr.repositories.all()
290 repos = gr.repositories.all()
291 if repos:
291 if repos:
292 msg = ungettext(
292 msg = ungettext(
293 'This group contains %(num)d repository and cannot be deleted',
293 'This group contains %(num)d repository and cannot be deleted',
294 'This group contains %(num)d repositories and cannot be'
294 'This group contains %(num)d repositories and cannot be'
295 ' deleted',
295 ' deleted',
296 len(repos)) % {'num': len(repos)}
296 len(repos)) % {'num': len(repos)}
297 h.flash(msg, category='warning')
297 h.flash(msg, category='warning')
298 return redirect(url('repo_groups'))
298 return redirect(url('repo_groups'))
299
299
300 children = gr.children.all()
300 children = gr.children.all()
301 if children:
301 if children:
302 msg = ungettext(
302 msg = ungettext(
303 'This group contains %(num)d subgroup and cannot be deleted',
303 'This group contains %(num)d subgroup and cannot be deleted',
304 'This group contains %(num)d subgroups and cannot be deleted',
304 'This group contains %(num)d subgroups and cannot be deleted',
305 len(children)) % {'num': len(children)}
305 len(children)) % {'num': len(children)}
306 h.flash(msg, category='warning')
306 h.flash(msg, category='warning')
307 return redirect(url('repo_groups'))
307 return redirect(url('repo_groups'))
308
308
309 try:
309 try:
310 RepoGroupModel().delete(group_name)
310 RepoGroupModel().delete(group_name)
311 Session().commit()
311 Session().commit()
312 h.flash(_('Removed repository group %s') % group_name,
312 h.flash(_('Removed repository group %s') % group_name,
313 category='success')
313 category='success')
314 # TODO: in future action_logger(, '', '', '', self.sa)
314 # TODO: in future action_logger(, '', '', '', self.sa)
315 except Exception:
315 except Exception:
316 log.exception("Exception during deletion of repository group")
316 log.exception("Exception during deletion of repository group")
317 h.flash(_('Error occurred during deletion of repository group %s')
317 h.flash(_('Error occurred during deletion of repository group %s')
318 % group_name, category='error')
318 % group_name, category='error')
319
319
320 return redirect(url('repo_groups'))
320 return redirect(url('repo_groups'))
321
321
322 @HasRepoGroupPermissionAnyDecorator('group.admin')
322 @HasRepoGroupPermissionAnyDecorator('group.admin')
323 def edit(self, group_name):
323 def edit(self, group_name):
324 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
324 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
325 # url('edit_repo_group', group_name=GROUP_NAME)
325 # url('edit_repo_group', group_name=GROUP_NAME)
326 c.active = 'settings'
326 c.active = 'settings'
327
327
328 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
328 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
329 # we can only allow moving empty group if it's already a top-level
329 # we can only allow moving empty group if it's already a top-level
330 # group, ie has no parents, or we're admin
330 # group, ie has no parents, or we're admin
331 can_create_in_root = self._can_create_repo_group()
331 can_create_in_root = self._can_create_repo_group()
332 show_root_location = can_create_in_root
332 show_root_location = can_create_in_root
333 if not c.repo_group.parent_group:
333 if not c.repo_group.parent_group:
334 # this group don't have a parrent so we should show empty value
334 # this group don't have a parrent so we should show empty value
335 show_root_location = True
335 show_root_location = True
336 self.__load_defaults(allow_empty_group=show_root_location,
336 self.__load_defaults(allow_empty_group=show_root_location,
337 repo_group=c.repo_group)
337 repo_group=c.repo_group)
338 defaults = self.__load_data(c.repo_group.group_id)
338 defaults = self.__load_data(c.repo_group.group_id)
339
339
340 return htmlfill.render(
340 return htmlfill.render(
341 render('admin/repo_groups/repo_group_edit.html'),
341 render('admin/repo_groups/repo_group_edit.html'),
342 defaults=defaults,
342 defaults=defaults,
343 encoding="UTF-8",
343 encoding="UTF-8",
344 force_defaults=False
344 force_defaults=False
345 )
345 )
346
346
347 @HasRepoGroupPermissionAnyDecorator('group.admin')
347 @HasRepoGroupPermissionAnyDecorator('group.admin')
348 def edit_repo_group_advanced(self, group_name):
348 def edit_repo_group_advanced(self, group_name):
349 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
349 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
350 # url('edit_repo_group', group_name=GROUP_NAME)
350 # url('edit_repo_group', group_name=GROUP_NAME)
351 c.active = 'advanced'
351 c.active = 'advanced'
352 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
352 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
353
353
354 return render('admin/repo_groups/repo_group_edit.html')
354 return render('admin/repo_groups/repo_group_edit.html')
355
355
356 @HasRepoGroupPermissionAnyDecorator('group.admin')
356 @HasRepoGroupPermissionAnyDecorator('group.admin')
357 def edit_repo_group_perms(self, group_name):
357 def edit_repo_group_perms(self, group_name):
358 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
358 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
359 # url('edit_repo_group', group_name=GROUP_NAME)
359 # url('edit_repo_group', group_name=GROUP_NAME)
360 c.active = 'perms'
360 c.active = 'perms'
361 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
361 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
362 self.__load_defaults()
362 self.__load_defaults()
363 defaults = self.__load_data(c.repo_group.group_id)
363 defaults = self.__load_data(c.repo_group.group_id)
364
364
365 return htmlfill.render(
365 return htmlfill.render(
366 render('admin/repo_groups/repo_group_edit.html'),
366 render('admin/repo_groups/repo_group_edit.html'),
367 defaults=defaults,
367 defaults=defaults,
368 encoding="UTF-8",
368 encoding="UTF-8",
369 force_defaults=False
369 force_defaults=False
370 )
370 )
371
371
372 @HasRepoGroupPermissionAnyDecorator('group.admin')
372 @HasRepoGroupPermissionAnyDecorator('group.admin')
373 @auth.CSRFRequired()
373 @auth.CSRFRequired()
374 def update_perms(self, group_name):
374 def update_perms(self, group_name):
375 """
375 """
376 Update permissions for given repository group
376 Update permissions for given repository group
377
377
378 :param group_name:
378 :param group_name:
379 """
379 """
380
380
381 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
381 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
382 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
382 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
383 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
383 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
384 request.POST)
384 request.POST)
385
385
386 if not c.rhodecode_user.is_admin:
386 if not c.rhodecode_user.is_admin:
387 if self._revoke_perms_on_yourself(form):
387 if self._revoke_perms_on_yourself(form):
388 msg = _('Cannot change permission for yourself as admin')
388 msg = _('Cannot change permission for yourself as admin')
389 h.flash(msg, category='warning')
389 h.flash(msg, category='warning')
390 return redirect(
390 return redirect(
391 url('edit_repo_group_perms', group_name=group_name))
391 url('edit_repo_group_perms', group_name=group_name))
392
392
393 # iterate over all members(if in recursive mode) of this groups and
393 # iterate over all members(if in recursive mode) of this groups and
394 # set the permissions !
394 # set the permissions !
395 # this can be potentially heavy operation
395 # this can be potentially heavy operation
396 RepoGroupModel().update_permissions(
396 RepoGroupModel().update_permissions(
397 c.repo_group,
397 c.repo_group,
398 form['perm_additions'], form['perm_updates'],
398 form['perm_additions'], form['perm_updates'],
399 form['perm_deletions'], form['recursive'])
399 form['perm_deletions'], form['recursive'])
400
400
401 # TODO: implement this
401 # TODO: implement this
402 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
402 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
403 # repo_name, self.ip_addr, self.sa)
403 # repo_name, self.ip_addr, self.sa)
404 Session().commit()
404 Session().commit()
405 h.flash(_('Repository Group permissions updated'), category='success')
405 h.flash(_('Repository Group permissions updated'), category='success')
406 return redirect(url('edit_repo_group_perms', group_name=group_name))
406 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -1,480 +1,480 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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 User Groups crud controller for pylons
22 User Groups 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 sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 RepoGroupAssignmentError
38 RepoGroupAssignmentError
39 from rhodecode.lib.utils import jsonify, action_logger
39 from rhodecode.lib.utils import jsonify, action_logger
40 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
40 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
41 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
42 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
43 HasPermissionAnyDecorator)
43 HasPermissionAnyDecorator)
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.model.permission import PermissionModel
45 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.scm import UserGroupList
46 from rhodecode.model.scm import UserGroupList
47 from rhodecode.model.user_group import UserGroupModel
47 from rhodecode.model.user_group import UserGroupModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
49 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
50 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
51 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
51 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
52 UserPermissionsForm)
52 UserPermissionsForm)
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.lib.utils import action_logger
54 from rhodecode.lib.utils import action_logger
55 from rhodecode.lib.ext_json import json
55 from rhodecode.lib.ext_json import json
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class UserGroupsController(BaseController):
60 class UserGroupsController(BaseController):
61 """REST Controller styled on the Atom Publishing Protocol"""
61 """REST Controller styled on the Atom Publishing Protocol"""
62
62
63 @LoginRequired()
63 @LoginRequired()
64 def __before__(self):
64 def __before__(self):
65 super(UserGroupsController, self).__before__()
65 super(UserGroupsController, self).__before__()
66 c.available_permissions = config['available_permissions']
66 c.available_permissions = config['available_permissions']
67 PermissionModel().set_global_permission_choices(c, translator=_)
67 PermissionModel().set_global_permission_choices(c, translator=_)
68
68
69 def __load_data(self, user_group_id):
69 def __load_data(self, user_group_id):
70 c.group_members_obj = [x.user for x in c.user_group.members]
70 c.group_members_obj = [x.user for x in c.user_group.members]
71 c.group_members_obj.sort(key=lambda u: u.username.lower())
71 c.group_members_obj.sort(key=lambda u: u.username.lower())
72
72
73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74
74
75 c.available_members = [(x.user_id, x.username)
75 c.available_members = [(x.user_id, x.username)
76 for x in User.query().all()]
76 for x in User.query().all()]
77 c.available_members.sort(key=lambda u: u[1].lower())
77 c.available_members.sort(key=lambda u: u[1].lower())
78
78
79 def __load_defaults(self, user_group_id):
79 def __load_defaults(self, user_group_id):
80 """
80 """
81 Load defaults settings for edit, and update
81 Load defaults settings for edit, and update
82
82
83 :param user_group_id:
83 :param user_group_id:
84 """
84 """
85 user_group = UserGroup.get_or_404(user_group_id)
85 user_group = UserGroup.get_or_404(user_group_id)
86 data = user_group.get_dict()
86 data = user_group.get_dict()
87 # fill owner
87 # fill owner
88 if user_group.user:
88 if user_group.user:
89 data.update({'user': user_group.user.username})
89 data.update({'user': user_group.user.username})
90 else:
90 else:
91 replacement_user = User.get_first_admin().username
91 replacement_user = User.get_first_super_admin().username
92 data.update({'user': replacement_user})
92 data.update({'user': replacement_user})
93 return data
93 return data
94
94
95 def _revoke_perms_on_yourself(self, form_result):
95 def _revoke_perms_on_yourself(self, form_result):
96 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 form_result['perm_updates'])
97 form_result['perm_updates'])
98 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
99 form_result['perm_additions'])
99 form_result['perm_additions'])
100 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
100 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
101 form_result['perm_deletions'])
101 form_result['perm_deletions'])
102 admin_perm = 'usergroup.admin'
102 admin_perm = 'usergroup.admin'
103 if _updates and _updates[0][1] != admin_perm or \
103 if _updates and _updates[0][1] != admin_perm or \
104 _additions and _additions[0][1] != admin_perm or \
104 _additions and _additions[0][1] != admin_perm or \
105 _deletions and _deletions[0][1] != admin_perm:
105 _deletions and _deletions[0][1] != admin_perm:
106 return True
106 return True
107 return False
107 return False
108
108
109 # permission check inside
109 # permission check inside
110 @NotAnonymous()
110 @NotAnonymous()
111 def index(self):
111 def index(self):
112 """GET /users_groups: All items in the collection"""
112 """GET /users_groups: All items in the collection"""
113 # url('users_groups')
113 # url('users_groups')
114
114
115 from rhodecode.lib.utils import PartialRenderer
115 from rhodecode.lib.utils import PartialRenderer
116 _render = PartialRenderer('data_table/_dt_elements.html')
116 _render = PartialRenderer('data_table/_dt_elements.html')
117
117
118 def user_group_name(user_group_id, user_group_name):
118 def user_group_name(user_group_id, user_group_name):
119 return _render("user_group_name", user_group_id, user_group_name)
119 return _render("user_group_name", user_group_id, user_group_name)
120
120
121 def user_group_actions(user_group_id, user_group_name):
121 def user_group_actions(user_group_id, user_group_name):
122 return _render("user_group_actions", user_group_id, user_group_name)
122 return _render("user_group_actions", user_group_id, user_group_name)
123
123
124 ## json generate
124 ## json generate
125 group_iter = UserGroupList(UserGroup.query().all(),
125 group_iter = UserGroupList(UserGroup.query().all(),
126 perm_set=['usergroup.admin'])
126 perm_set=['usergroup.admin'])
127
127
128 user_groups_data = []
128 user_groups_data = []
129 for user_gr in group_iter:
129 for user_gr in group_iter:
130 user_groups_data.append({
130 user_groups_data.append({
131 "group_name": user_group_name(
131 "group_name": user_group_name(
132 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
132 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
133 "group_name_raw": user_gr.users_group_name,
133 "group_name_raw": user_gr.users_group_name,
134 "desc": h.escape(user_gr.user_group_description),
134 "desc": h.escape(user_gr.user_group_description),
135 "members": len(user_gr.members),
135 "members": len(user_gr.members),
136 "active": h.bool2icon(user_gr.users_group_active),
136 "active": h.bool2icon(user_gr.users_group_active),
137 "owner": h.escape(h.link_to_user(user_gr.user.username)),
137 "owner": h.escape(h.link_to_user(user_gr.user.username)),
138 "action": user_group_actions(
138 "action": user_group_actions(
139 user_gr.users_group_id, user_gr.users_group_name)
139 user_gr.users_group_id, user_gr.users_group_name)
140 })
140 })
141
141
142 c.data = json.dumps(user_groups_data)
142 c.data = json.dumps(user_groups_data)
143 return render('admin/user_groups/user_groups.html')
143 return render('admin/user_groups/user_groups.html')
144
144
145 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
145 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
146 @auth.CSRFRequired()
146 @auth.CSRFRequired()
147 def create(self):
147 def create(self):
148 """POST /users_groups: Create a new item"""
148 """POST /users_groups: Create a new item"""
149 # url('users_groups')
149 # url('users_groups')
150
150
151 users_group_form = UserGroupForm()()
151 users_group_form = UserGroupForm()()
152 try:
152 try:
153 form_result = users_group_form.to_python(dict(request.POST))
153 form_result = users_group_form.to_python(dict(request.POST))
154 user_group = UserGroupModel().create(
154 user_group = UserGroupModel().create(
155 name=form_result['users_group_name'],
155 name=form_result['users_group_name'],
156 description=form_result['user_group_description'],
156 description=form_result['user_group_description'],
157 owner=c.rhodecode_user.user_id,
157 owner=c.rhodecode_user.user_id,
158 active=form_result['users_group_active'])
158 active=form_result['users_group_active'])
159 Session().flush()
159 Session().flush()
160
160
161 user_group_name = form_result['users_group_name']
161 user_group_name = form_result['users_group_name']
162 action_logger(c.rhodecode_user,
162 action_logger(c.rhodecode_user,
163 'admin_created_users_group:%s' % user_group_name,
163 'admin_created_users_group:%s' % user_group_name,
164 None, self.ip_addr, self.sa)
164 None, self.ip_addr, self.sa)
165 user_group_link = h.link_to(h.escape(user_group_name),
165 user_group_link = h.link_to(h.escape(user_group_name),
166 url('edit_users_group',
166 url('edit_users_group',
167 user_group_id=user_group.users_group_id))
167 user_group_id=user_group.users_group_id))
168 h.flash(h.literal(_('Created user group %(user_group_link)s')
168 h.flash(h.literal(_('Created user group %(user_group_link)s')
169 % {'user_group_link': user_group_link}),
169 % {'user_group_link': user_group_link}),
170 category='success')
170 category='success')
171 Session().commit()
171 Session().commit()
172 except formencode.Invalid as errors:
172 except formencode.Invalid as errors:
173 return htmlfill.render(
173 return htmlfill.render(
174 render('admin/user_groups/user_group_add.html'),
174 render('admin/user_groups/user_group_add.html'),
175 defaults=errors.value,
175 defaults=errors.value,
176 errors=errors.error_dict or {},
176 errors=errors.error_dict or {},
177 prefix_error=False,
177 prefix_error=False,
178 encoding="UTF-8",
178 encoding="UTF-8",
179 force_defaults=False)
179 force_defaults=False)
180 except Exception:
180 except Exception:
181 log.exception("Exception creating user group")
181 log.exception("Exception creating user group")
182 h.flash(_('Error occurred during creation of user group %s') \
182 h.flash(_('Error occurred during creation of user group %s') \
183 % request.POST.get('users_group_name'), category='error')
183 % request.POST.get('users_group_name'), category='error')
184
184
185 return redirect(
185 return redirect(
186 url('edit_users_group', user_group_id=user_group.users_group_id))
186 url('edit_users_group', user_group_id=user_group.users_group_id))
187
187
188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
189 def new(self):
189 def new(self):
190 """GET /user_groups/new: Form to create a new item"""
190 """GET /user_groups/new: Form to create a new item"""
191 # url('new_users_group')
191 # url('new_users_group')
192 return render('admin/user_groups/user_group_add.html')
192 return render('admin/user_groups/user_group_add.html')
193
193
194 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
194 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
195 @auth.CSRFRequired()
195 @auth.CSRFRequired()
196 def update(self, user_group_id):
196 def update(self, user_group_id):
197 """PUT /user_groups/user_group_id: Update an existing item"""
197 """PUT /user_groups/user_group_id: Update an existing item"""
198 # Forms posted to this method should contain a hidden field:
198 # Forms posted to this method should contain a hidden field:
199 # <input type="hidden" name="_method" value="PUT" />
199 # <input type="hidden" name="_method" value="PUT" />
200 # Or using helpers:
200 # Or using helpers:
201 # h.form(url('users_group', user_group_id=ID),
201 # h.form(url('users_group', user_group_id=ID),
202 # method='put')
202 # method='put')
203 # url('users_group', user_group_id=ID)
203 # url('users_group', user_group_id=ID)
204
204
205 user_group_id = safe_int(user_group_id)
205 user_group_id = safe_int(user_group_id)
206 c.user_group = UserGroup.get_or_404(user_group_id)
206 c.user_group = UserGroup.get_or_404(user_group_id)
207 c.active = 'settings'
207 c.active = 'settings'
208 self.__load_data(user_group_id)
208 self.__load_data(user_group_id)
209
209
210 available_members = [safe_unicode(x[0]) for x in c.available_members]
210 available_members = [safe_unicode(x[0]) for x in c.available_members]
211
211
212 users_group_form = UserGroupForm(
212 users_group_form = UserGroupForm(
213 edit=True, old_data=c.user_group.get_dict(),
213 edit=True, old_data=c.user_group.get_dict(),
214 available_members=available_members, allow_disabled=True)()
214 available_members=available_members, allow_disabled=True)()
215
215
216 try:
216 try:
217 form_result = users_group_form.to_python(request.POST)
217 form_result = users_group_form.to_python(request.POST)
218 UserGroupModel().update(c.user_group, form_result)
218 UserGroupModel().update(c.user_group, form_result)
219 gr = form_result['users_group_name']
219 gr = form_result['users_group_name']
220 action_logger(c.rhodecode_user,
220 action_logger(c.rhodecode_user,
221 'admin_updated_users_group:%s' % gr,
221 'admin_updated_users_group:%s' % gr,
222 None, self.ip_addr, self.sa)
222 None, self.ip_addr, self.sa)
223 h.flash(_('Updated user group %s') % gr, category='success')
223 h.flash(_('Updated user group %s') % gr, category='success')
224 Session().commit()
224 Session().commit()
225 except formencode.Invalid as errors:
225 except formencode.Invalid as errors:
226 defaults = errors.value
226 defaults = errors.value
227 e = errors.error_dict or {}
227 e = errors.error_dict or {}
228
228
229 return htmlfill.render(
229 return htmlfill.render(
230 render('admin/user_groups/user_group_edit.html'),
230 render('admin/user_groups/user_group_edit.html'),
231 defaults=defaults,
231 defaults=defaults,
232 errors=e,
232 errors=e,
233 prefix_error=False,
233 prefix_error=False,
234 encoding="UTF-8",
234 encoding="UTF-8",
235 force_defaults=False)
235 force_defaults=False)
236 except Exception:
236 except Exception:
237 log.exception("Exception during update of user group")
237 log.exception("Exception during update of user group")
238 h.flash(_('Error occurred during update of user group %s')
238 h.flash(_('Error occurred during update of user group %s')
239 % request.POST.get('users_group_name'), category='error')
239 % request.POST.get('users_group_name'), category='error')
240
240
241 return redirect(url('edit_users_group', user_group_id=user_group_id))
241 return redirect(url('edit_users_group', user_group_id=user_group_id))
242
242
243 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
243 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
244 @auth.CSRFRequired()
244 @auth.CSRFRequired()
245 def delete(self, user_group_id):
245 def delete(self, user_group_id):
246 """DELETE /user_groups/user_group_id: Delete an existing item"""
246 """DELETE /user_groups/user_group_id: Delete an existing item"""
247 # Forms posted to this method should contain a hidden field:
247 # Forms posted to this method should contain a hidden field:
248 # <input type="hidden" name="_method" value="DELETE" />
248 # <input type="hidden" name="_method" value="DELETE" />
249 # Or using helpers:
249 # Or using helpers:
250 # h.form(url('users_group', user_group_id=ID),
250 # h.form(url('users_group', user_group_id=ID),
251 # method='delete')
251 # method='delete')
252 # url('users_group', user_group_id=ID)
252 # url('users_group', user_group_id=ID)
253 user_group_id = safe_int(user_group_id)
253 user_group_id = safe_int(user_group_id)
254 c.user_group = UserGroup.get_or_404(user_group_id)
254 c.user_group = UserGroup.get_or_404(user_group_id)
255 force = str2bool(request.POST.get('force'))
255 force = str2bool(request.POST.get('force'))
256
256
257 try:
257 try:
258 UserGroupModel().delete(c.user_group, force=force)
258 UserGroupModel().delete(c.user_group, force=force)
259 Session().commit()
259 Session().commit()
260 h.flash(_('Successfully deleted user group'), category='success')
260 h.flash(_('Successfully deleted user group'), category='success')
261 except UserGroupAssignedException as e:
261 except UserGroupAssignedException as e:
262 h.flash(str(e), category='error')
262 h.flash(str(e), category='error')
263 except Exception:
263 except Exception:
264 log.exception("Exception during deletion of user group")
264 log.exception("Exception during deletion of user group")
265 h.flash(_('An error occurred during deletion of user group'),
265 h.flash(_('An error occurred during deletion of user group'),
266 category='error')
266 category='error')
267 return redirect(url('users_groups'))
267 return redirect(url('users_groups'))
268
268
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
270 def edit(self, user_group_id):
270 def edit(self, user_group_id):
271 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
271 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
272 # url('edit_users_group', user_group_id=ID)
272 # url('edit_users_group', user_group_id=ID)
273
273
274 user_group_id = safe_int(user_group_id)
274 user_group_id = safe_int(user_group_id)
275 c.user_group = UserGroup.get_or_404(user_group_id)
275 c.user_group = UserGroup.get_or_404(user_group_id)
276 c.active = 'settings'
276 c.active = 'settings'
277 self.__load_data(user_group_id)
277 self.__load_data(user_group_id)
278
278
279 defaults = self.__load_defaults(user_group_id)
279 defaults = self.__load_defaults(user_group_id)
280
280
281 return htmlfill.render(
281 return htmlfill.render(
282 render('admin/user_groups/user_group_edit.html'),
282 render('admin/user_groups/user_group_edit.html'),
283 defaults=defaults,
283 defaults=defaults,
284 encoding="UTF-8",
284 encoding="UTF-8",
285 force_defaults=False
285 force_defaults=False
286 )
286 )
287
287
288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
289 def edit_perms(self, user_group_id):
289 def edit_perms(self, user_group_id):
290 user_group_id = safe_int(user_group_id)
290 user_group_id = safe_int(user_group_id)
291 c.user_group = UserGroup.get_or_404(user_group_id)
291 c.user_group = UserGroup.get_or_404(user_group_id)
292 c.active = 'perms'
292 c.active = 'perms'
293
293
294 defaults = {}
294 defaults = {}
295 # fill user group users
295 # fill user group users
296 for p in c.user_group.user_user_group_to_perm:
296 for p in c.user_group.user_user_group_to_perm:
297 defaults.update({'u_perm_%s' % p.user.user_id:
297 defaults.update({'u_perm_%s' % p.user.user_id:
298 p.permission.permission_name})
298 p.permission.permission_name})
299
299
300 for p in c.user_group.user_group_user_group_to_perm:
300 for p in c.user_group.user_group_user_group_to_perm:
301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
302 p.permission.permission_name})
302 p.permission.permission_name})
303
303
304 return htmlfill.render(
304 return htmlfill.render(
305 render('admin/user_groups/user_group_edit.html'),
305 render('admin/user_groups/user_group_edit.html'),
306 defaults=defaults,
306 defaults=defaults,
307 encoding="UTF-8",
307 encoding="UTF-8",
308 force_defaults=False
308 force_defaults=False
309 )
309 )
310
310
311 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
311 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
312 @auth.CSRFRequired()
312 @auth.CSRFRequired()
313 def update_perms(self, user_group_id):
313 def update_perms(self, user_group_id):
314 """
314 """
315 grant permission for given usergroup
315 grant permission for given usergroup
316
316
317 :param user_group_id:
317 :param user_group_id:
318 """
318 """
319 user_group_id = safe_int(user_group_id)
319 user_group_id = safe_int(user_group_id)
320 c.user_group = UserGroup.get_or_404(user_group_id)
320 c.user_group = UserGroup.get_or_404(user_group_id)
321 form = UserGroupPermsForm()().to_python(request.POST)
321 form = UserGroupPermsForm()().to_python(request.POST)
322
322
323 if not c.rhodecode_user.is_admin:
323 if not c.rhodecode_user.is_admin:
324 if self._revoke_perms_on_yourself(form):
324 if self._revoke_perms_on_yourself(form):
325 msg = _('Cannot change permission for yourself as admin')
325 msg = _('Cannot change permission for yourself as admin')
326 h.flash(msg, category='warning')
326 h.flash(msg, category='warning')
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
328
328
329 try:
329 try:
330 UserGroupModel().update_permissions(user_group_id,
330 UserGroupModel().update_permissions(user_group_id,
331 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
331 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
332 except RepoGroupAssignmentError:
332 except RepoGroupAssignmentError:
333 h.flash(_('Target group cannot be the same'), category='error')
333 h.flash(_('Target group cannot be the same'), category='error')
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
335 #TODO: implement this
335 #TODO: implement this
336 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
336 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
337 # repo_name, self.ip_addr, self.sa)
337 # repo_name, self.ip_addr, self.sa)
338 Session().commit()
338 Session().commit()
339 h.flash(_('User Group permissions updated'), category='success')
339 h.flash(_('User Group permissions updated'), category='success')
340 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
340 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
341
341
342 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
342 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
343 def edit_perms_summary(self, user_group_id):
343 def edit_perms_summary(self, user_group_id):
344 user_group_id = safe_int(user_group_id)
344 user_group_id = safe_int(user_group_id)
345 c.user_group = UserGroup.get_or_404(user_group_id)
345 c.user_group = UserGroup.get_or_404(user_group_id)
346 c.active = 'perms_summary'
346 c.active = 'perms_summary'
347 permissions = {
347 permissions = {
348 'repositories': {},
348 'repositories': {},
349 'repositories_groups': {},
349 'repositories_groups': {},
350 }
350 }
351 ugroup_repo_perms = UserGroupRepoToPerm.query()\
351 ugroup_repo_perms = UserGroupRepoToPerm.query()\
352 .options(joinedload(UserGroupRepoToPerm.permission))\
352 .options(joinedload(UserGroupRepoToPerm.permission))\
353 .options(joinedload(UserGroupRepoToPerm.repository))\
353 .options(joinedload(UserGroupRepoToPerm.repository))\
354 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
354 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
355 .all()
355 .all()
356
356
357 for gr in ugroup_repo_perms:
357 for gr in ugroup_repo_perms:
358 permissions['repositories'][gr.repository.repo_name] \
358 permissions['repositories'][gr.repository.repo_name] \
359 = gr.permission.permission_name
359 = gr.permission.permission_name
360
360
361 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
361 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
362 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
362 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
363 .options(joinedload(UserGroupRepoGroupToPerm.group))\
363 .options(joinedload(UserGroupRepoGroupToPerm.group))\
364 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
364 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
365 .all()
365 .all()
366
366
367 for gr in ugroup_group_perms:
367 for gr in ugroup_group_perms:
368 permissions['repositories_groups'][gr.group.group_name] \
368 permissions['repositories_groups'][gr.group.group_name] \
369 = gr.permission.permission_name
369 = gr.permission.permission_name
370 c.permissions = permissions
370 c.permissions = permissions
371 return render('admin/user_groups/user_group_edit.html')
371 return render('admin/user_groups/user_group_edit.html')
372
372
373 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
373 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
374 def edit_global_perms(self, user_group_id):
374 def edit_global_perms(self, user_group_id):
375 user_group_id = safe_int(user_group_id)
375 user_group_id = safe_int(user_group_id)
376 c.user_group = UserGroup.get_or_404(user_group_id)
376 c.user_group = UserGroup.get_or_404(user_group_id)
377 c.active = 'global_perms'
377 c.active = 'global_perms'
378
378
379 c.default_user = User.get_default_user()
379 c.default_user = User.get_default_user()
380 defaults = c.user_group.get_dict()
380 defaults = c.user_group.get_dict()
381 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
381 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
382 defaults.update(c.user_group.get_default_perms())
382 defaults.update(c.user_group.get_default_perms())
383
383
384 return htmlfill.render(
384 return htmlfill.render(
385 render('admin/user_groups/user_group_edit.html'),
385 render('admin/user_groups/user_group_edit.html'),
386 defaults=defaults,
386 defaults=defaults,
387 encoding="UTF-8",
387 encoding="UTF-8",
388 force_defaults=False
388 force_defaults=False
389 )
389 )
390
390
391 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
391 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
392 @auth.CSRFRequired()
392 @auth.CSRFRequired()
393 def update_global_perms(self, user_group_id):
393 def update_global_perms(self, user_group_id):
394 """PUT /users_perm/user_group_id: Update an existing item"""
394 """PUT /users_perm/user_group_id: Update an existing item"""
395 # url('users_group_perm', user_group_id=ID, method='put')
395 # url('users_group_perm', user_group_id=ID, method='put')
396 user_group_id = safe_int(user_group_id)
396 user_group_id = safe_int(user_group_id)
397 user_group = UserGroup.get_or_404(user_group_id)
397 user_group = UserGroup.get_or_404(user_group_id)
398 c.active = 'global_perms'
398 c.active = 'global_perms'
399
399
400 try:
400 try:
401 # first stage that verifies the checkbox
401 # first stage that verifies the checkbox
402 _form = UserIndividualPermissionsForm()
402 _form = UserIndividualPermissionsForm()
403 form_result = _form.to_python(dict(request.POST))
403 form_result = _form.to_python(dict(request.POST))
404 inherit_perms = form_result['inherit_default_permissions']
404 inherit_perms = form_result['inherit_default_permissions']
405 user_group.inherit_default_permissions = inherit_perms
405 user_group.inherit_default_permissions = inherit_perms
406 Session().add(user_group)
406 Session().add(user_group)
407
407
408 if not inherit_perms:
408 if not inherit_perms:
409 # only update the individual ones if we un check the flag
409 # only update the individual ones if we un check the flag
410 _form = UserPermissionsForm(
410 _form = UserPermissionsForm(
411 [x[0] for x in c.repo_create_choices],
411 [x[0] for x in c.repo_create_choices],
412 [x[0] for x in c.repo_create_on_write_choices],
412 [x[0] for x in c.repo_create_on_write_choices],
413 [x[0] for x in c.repo_group_create_choices],
413 [x[0] for x in c.repo_group_create_choices],
414 [x[0] for x in c.user_group_create_choices],
414 [x[0] for x in c.user_group_create_choices],
415 [x[0] for x in c.fork_choices],
415 [x[0] for x in c.fork_choices],
416 [x[0] for x in c.inherit_default_permission_choices])()
416 [x[0] for x in c.inherit_default_permission_choices])()
417
417
418 form_result = _form.to_python(dict(request.POST))
418 form_result = _form.to_python(dict(request.POST))
419 form_result.update({'perm_user_group_id': user_group.users_group_id})
419 form_result.update({'perm_user_group_id': user_group.users_group_id})
420
420
421 PermissionModel().update_user_group_permissions(form_result)
421 PermissionModel().update_user_group_permissions(form_result)
422
422
423 Session().commit()
423 Session().commit()
424 h.flash(_('User Group global permissions updated successfully'),
424 h.flash(_('User Group global permissions updated successfully'),
425 category='success')
425 category='success')
426
426
427 except formencode.Invalid as errors:
427 except formencode.Invalid as errors:
428 defaults = errors.value
428 defaults = errors.value
429 c.user_group = user_group
429 c.user_group = user_group
430 return htmlfill.render(
430 return htmlfill.render(
431 render('admin/user_groups/user_group_edit.html'),
431 render('admin/user_groups/user_group_edit.html'),
432 defaults=defaults,
432 defaults=defaults,
433 errors=errors.error_dict or {},
433 errors=errors.error_dict or {},
434 prefix_error=False,
434 prefix_error=False,
435 encoding="UTF-8",
435 encoding="UTF-8",
436 force_defaults=False)
436 force_defaults=False)
437
437
438 except Exception:
438 except Exception:
439 log.exception("Exception during permissions saving")
439 log.exception("Exception during permissions saving")
440 h.flash(_('An error occurred during permissions saving'),
440 h.flash(_('An error occurred during permissions saving'),
441 category='error')
441 category='error')
442
442
443 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
443 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
444
444
445 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
445 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
446 def edit_advanced(self, user_group_id):
446 def edit_advanced(self, user_group_id):
447 user_group_id = safe_int(user_group_id)
447 user_group_id = safe_int(user_group_id)
448 c.user_group = UserGroup.get_or_404(user_group_id)
448 c.user_group = UserGroup.get_or_404(user_group_id)
449 c.active = 'advanced'
449 c.active = 'advanced'
450 c.group_members_obj = sorted(
450 c.group_members_obj = sorted(
451 (x.user for x in c.user_group.members),
451 (x.user for x in c.user_group.members),
452 key=lambda u: u.username.lower())
452 key=lambda u: u.username.lower())
453
453
454 c.group_to_repos = sorted(
454 c.group_to_repos = sorted(
455 (x.repository for x in c.user_group.users_group_repo_to_perm),
455 (x.repository for x in c.user_group.users_group_repo_to_perm),
456 key=lambda u: u.repo_name.lower())
456 key=lambda u: u.repo_name.lower())
457
457
458 c.group_to_repo_groups = sorted(
458 c.group_to_repo_groups = sorted(
459 (x.group for x in c.user_group.users_group_repo_group_to_perm),
459 (x.group for x in c.user_group.users_group_repo_group_to_perm),
460 key=lambda u: u.group_name.lower())
460 key=lambda u: u.group_name.lower())
461
461
462 return render('admin/user_groups/user_group_edit.html')
462 return render('admin/user_groups/user_group_edit.html')
463
463
464 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
464 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
465 def edit_members(self, user_group_id):
465 def edit_members(self, user_group_id):
466 user_group_id = safe_int(user_group_id)
466 user_group_id = safe_int(user_group_id)
467 c.user_group = UserGroup.get_or_404(user_group_id)
467 c.user_group = UserGroup.get_or_404(user_group_id)
468 c.active = 'members'
468 c.active = 'members'
469 c.group_members_obj = sorted((x.user for x in c.user_group.members),
469 c.group_members_obj = sorted((x.user for x in c.user_group.members),
470 key=lambda u: u.username.lower())
470 key=lambda u: u.username.lower())
471
471
472 group_members = [(x.user_id, x.username) for x in c.group_members_obj]
472 group_members = [(x.user_id, x.username) for x in c.group_members_obj]
473
473
474 if request.is_xhr:
474 if request.is_xhr:
475 return jsonify(lambda *a, **k: {
475 return jsonify(lambda *a, **k: {
476 'members': group_members
476 'members': group_members
477 })
477 })
478
478
479 c.group_members = group_members
479 c.group_members = group_members
480 return render('admin/user_groups/user_group_edit.html')
480 return render('admin/user_groups/user_group_edit.html')
@@ -1,719 +1,719 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.lib.utils import action_logger
51 from rhodecode.lib.utils import action_logger
52 from rhodecode.lib.ext_json import json
52 from rhodecode.lib.ext_json import json
53 from rhodecode.lib.utils2 import datetime_to_time, safe_int
53 from rhodecode.lib.utils2 import datetime_to_time, safe_int
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UsersController(BaseController):
58 class UsersController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
60
60
61 @LoginRequired()
61 @LoginRequired()
62 def __before__(self):
62 def __before__(self):
63 super(UsersController, self).__before__()
63 super(UsersController, self).__before__()
64 c.available_permissions = config['available_permissions']
64 c.available_permissions = config['available_permissions']
65 c.allowed_languages = [
65 c.allowed_languages = [
66 ('en', 'English (en)'),
66 ('en', 'English (en)'),
67 ('de', 'German (de)'),
67 ('de', 'German (de)'),
68 ('fr', 'French (fr)'),
68 ('fr', 'French (fr)'),
69 ('it', 'Italian (it)'),
69 ('it', 'Italian (it)'),
70 ('ja', 'Japanese (ja)'),
70 ('ja', 'Japanese (ja)'),
71 ('pl', 'Polish (pl)'),
71 ('pl', 'Polish (pl)'),
72 ('pt', 'Portuguese (pt)'),
72 ('pt', 'Portuguese (pt)'),
73 ('ru', 'Russian (ru)'),
73 ('ru', 'Russian (ru)'),
74 ('zh', 'Chinese (zh)'),
74 ('zh', 'Chinese (zh)'),
75 ]
75 ]
76 PermissionModel().set_global_permission_choices(c, translator=_)
76 PermissionModel().set_global_permission_choices(c, translator=_)
77
77
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 def index(self):
79 def index(self):
80 """GET /users: All items in the collection"""
80 """GET /users: All items in the collection"""
81 # url('users')
81 # url('users')
82
82
83 from rhodecode.lib.utils import PartialRenderer
83 from rhodecode.lib.utils import PartialRenderer
84 _render = PartialRenderer('data_table/_dt_elements.html')
84 _render = PartialRenderer('data_table/_dt_elements.html')
85
85
86 def grav_tmpl(user_email, size):
86 def grav_tmpl(user_email, size):
87 return _render("user_gravatar", user_email, size)
87 return _render("user_gravatar", user_email, size)
88
88
89 def username(user_id, username):
89 def username(user_id, username):
90 return _render("user_name", user_id, username)
90 return _render("user_name", user_id, username)
91
91
92 def user_actions(user_id, username):
92 def user_actions(user_id, username):
93 return _render("user_actions", user_id, username)
93 return _render("user_actions", user_id, username)
94
94
95 # json generate
95 # json generate
96 c.users_list = User.query()\
96 c.users_list = User.query()\
97 .filter(User.username != User.DEFAULT_USER) \
97 .filter(User.username != User.DEFAULT_USER) \
98 .all()
98 .all()
99
99
100 users_data = []
100 users_data = []
101 for user in c.users_list:
101 for user in c.users_list:
102 users_data.append({
102 users_data.append({
103 "gravatar": grav_tmpl(user.email, 20),
103 "gravatar": grav_tmpl(user.email, 20),
104 "username": h.link_to(
104 "username": h.link_to(
105 user.username, h.url('user_profile', username=user.username)),
105 user.username, h.url('user_profile', username=user.username)),
106 "username_raw": user.username,
106 "username_raw": user.username,
107 "email": user.email,
107 "email": user.email,
108 "first_name": h.escape(user.name),
108 "first_name": h.escape(user.name),
109 "last_name": h.escape(user.lastname),
109 "last_name": h.escape(user.lastname),
110 "last_login": h.format_date(user.last_login),
110 "last_login": h.format_date(user.last_login),
111 "last_login_raw": datetime_to_time(user.last_login),
111 "last_login_raw": datetime_to_time(user.last_login),
112 "last_activity": h.format_date(
112 "last_activity": h.format_date(
113 h.time_to_datetime(user.user_data.get('last_activity', 0))),
113 h.time_to_datetime(user.user_data.get('last_activity', 0))),
114 "last_activity_raw": user.user_data.get('last_activity', 0),
114 "last_activity_raw": user.user_data.get('last_activity', 0),
115 "active": h.bool2icon(user.active),
115 "active": h.bool2icon(user.active),
116 "active_raw": user.active,
116 "active_raw": user.active,
117 "admin": h.bool2icon(user.admin),
117 "admin": h.bool2icon(user.admin),
118 "admin_raw": user.admin,
118 "admin_raw": user.admin,
119 "extern_type": user.extern_type,
119 "extern_type": user.extern_type,
120 "extern_name": user.extern_name,
120 "extern_name": user.extern_name,
121 "action": user_actions(user.user_id, user.username),
121 "action": user_actions(user.user_id, user.username),
122 })
122 })
123
123
124
124
125 c.data = json.dumps(users_data)
125 c.data = json.dumps(users_data)
126 return render('admin/users/users.html')
126 return render('admin/users/users.html')
127
127
128 @HasPermissionAllDecorator('hg.admin')
128 @HasPermissionAllDecorator('hg.admin')
129 @auth.CSRFRequired()
129 @auth.CSRFRequired()
130 def create(self):
130 def create(self):
131 """POST /users: Create a new item"""
131 """POST /users: Create a new item"""
132 # url('users')
132 # url('users')
133 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
133 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
134 user_model = UserModel()
134 user_model = UserModel()
135 user_form = UserForm()()
135 user_form = UserForm()()
136 try:
136 try:
137 form_result = user_form.to_python(dict(request.POST))
137 form_result = user_form.to_python(dict(request.POST))
138 user = user_model.create(form_result)
138 user = user_model.create(form_result)
139 Session().flush()
139 Session().flush()
140 username = form_result['username']
140 username = form_result['username']
141 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
141 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
142 None, self.ip_addr, self.sa)
142 None, self.ip_addr, self.sa)
143
143
144 user_link = h.link_to(h.escape(username),
144 user_link = h.link_to(h.escape(username),
145 url('edit_user',
145 url('edit_user',
146 user_id=user.user_id))
146 user_id=user.user_id))
147 h.flash(h.literal(_('Created user %(user_link)s')
147 h.flash(h.literal(_('Created user %(user_link)s')
148 % {'user_link': user_link}), category='success')
148 % {'user_link': user_link}), category='success')
149 Session().commit()
149 Session().commit()
150 except formencode.Invalid as errors:
150 except formencode.Invalid as errors:
151 return htmlfill.render(
151 return htmlfill.render(
152 render('admin/users/user_add.html'),
152 render('admin/users/user_add.html'),
153 defaults=errors.value,
153 defaults=errors.value,
154 errors=errors.error_dict or {},
154 errors=errors.error_dict or {},
155 prefix_error=False,
155 prefix_error=False,
156 encoding="UTF-8",
156 encoding="UTF-8",
157 force_defaults=False)
157 force_defaults=False)
158 except UserCreationError as e:
158 except UserCreationError as e:
159 h.flash(e, 'error')
159 h.flash(e, 'error')
160 except Exception:
160 except Exception:
161 log.exception("Exception creation of user")
161 log.exception("Exception creation of user")
162 h.flash(_('Error occurred during creation of user %s')
162 h.flash(_('Error occurred during creation of user %s')
163 % request.POST.get('username'), category='error')
163 % request.POST.get('username'), category='error')
164 return redirect(url('users'))
164 return redirect(url('users'))
165
165
166 @HasPermissionAllDecorator('hg.admin')
166 @HasPermissionAllDecorator('hg.admin')
167 def new(self):
167 def new(self):
168 """GET /users/new: Form to create a new item"""
168 """GET /users/new: Form to create a new item"""
169 # url('new_user')
169 # url('new_user')
170 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
170 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
171 return render('admin/users/user_add.html')
171 return render('admin/users/user_add.html')
172
172
173 @HasPermissionAllDecorator('hg.admin')
173 @HasPermissionAllDecorator('hg.admin')
174 @auth.CSRFRequired()
174 @auth.CSRFRequired()
175 def update(self, user_id):
175 def update(self, user_id):
176 """PUT /users/user_id: Update an existing item"""
176 """PUT /users/user_id: Update an existing item"""
177 # Forms posted to this method should contain a hidden field:
177 # Forms posted to this method should contain a hidden field:
178 # <input type="hidden" name="_method" value="PUT" />
178 # <input type="hidden" name="_method" value="PUT" />
179 # Or using helpers:
179 # Or using helpers:
180 # h.form(url('update_user', user_id=ID),
180 # h.form(url('update_user', user_id=ID),
181 # method='put')
181 # method='put')
182 # url('user', user_id=ID)
182 # url('user', user_id=ID)
183 user_id = safe_int(user_id)
183 user_id = safe_int(user_id)
184 c.user = User.get_or_404(user_id)
184 c.user = User.get_or_404(user_id)
185 c.active = 'profile'
185 c.active = 'profile'
186 c.extern_type = c.user.extern_type
186 c.extern_type = c.user.extern_type
187 c.extern_name = c.user.extern_name
187 c.extern_name = c.user.extern_name
188 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
188 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
189 available_languages = [x[0] for x in c.allowed_languages]
189 available_languages = [x[0] for x in c.allowed_languages]
190 _form = UserForm(edit=True, available_languages=available_languages,
190 _form = UserForm(edit=True, available_languages=available_languages,
191 old_data={'user_id': user_id,
191 old_data={'user_id': user_id,
192 'email': c.user.email})()
192 'email': c.user.email})()
193 form_result = {}
193 form_result = {}
194 try:
194 try:
195 form_result = _form.to_python(dict(request.POST))
195 form_result = _form.to_python(dict(request.POST))
196 skip_attrs = ['extern_type', 'extern_name']
196 skip_attrs = ['extern_type', 'extern_name']
197 # TODO: plugin should define if username can be updated
197 # TODO: plugin should define if username can be updated
198 if c.extern_type != "rhodecode":
198 if c.extern_type != "rhodecode":
199 # forbid updating username for external accounts
199 # forbid updating username for external accounts
200 skip_attrs.append('username')
200 skip_attrs.append('username')
201
201
202 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
202 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
203 usr = form_result['username']
203 usr = form_result['username']
204 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
204 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
205 None, self.ip_addr, self.sa)
205 None, self.ip_addr, self.sa)
206 h.flash(_('User updated successfully'), category='success')
206 h.flash(_('User updated successfully'), category='success')
207 Session().commit()
207 Session().commit()
208 except formencode.Invalid as errors:
208 except formencode.Invalid as errors:
209 defaults = errors.value
209 defaults = errors.value
210 e = errors.error_dict or {}
210 e = errors.error_dict or {}
211
211
212 return htmlfill.render(
212 return htmlfill.render(
213 render('admin/users/user_edit.html'),
213 render('admin/users/user_edit.html'),
214 defaults=defaults,
214 defaults=defaults,
215 errors=e,
215 errors=e,
216 prefix_error=False,
216 prefix_error=False,
217 encoding="UTF-8",
217 encoding="UTF-8",
218 force_defaults=False)
218 force_defaults=False)
219 except UserCreationError as e:
219 except UserCreationError as e:
220 h.flash(e, 'error')
220 h.flash(e, 'error')
221 except Exception:
221 except Exception:
222 log.exception("Exception updating user")
222 log.exception("Exception updating user")
223 h.flash(_('Error occurred during update of user %s')
223 h.flash(_('Error occurred during update of user %s')
224 % form_result.get('username'), category='error')
224 % form_result.get('username'), category='error')
225 return redirect(url('edit_user', user_id=user_id))
225 return redirect(url('edit_user', user_id=user_id))
226
226
227 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
228 @auth.CSRFRequired()
228 @auth.CSRFRequired()
229 def delete(self, user_id):
229 def delete(self, user_id):
230 """DELETE /users/user_id: Delete an existing item"""
230 """DELETE /users/user_id: Delete an existing item"""
231 # Forms posted to this method should contain a hidden field:
231 # Forms posted to this method should contain a hidden field:
232 # <input type="hidden" name="_method" value="DELETE" />
232 # <input type="hidden" name="_method" value="DELETE" />
233 # Or using helpers:
233 # Or using helpers:
234 # h.form(url('delete_user', user_id=ID),
234 # h.form(url('delete_user', user_id=ID),
235 # method='delete')
235 # method='delete')
236 # url('user', user_id=ID)
236 # url('user', user_id=ID)
237 user_id = safe_int(user_id)
237 user_id = safe_int(user_id)
238 c.user = User.get_or_404(user_id)
238 c.user = User.get_or_404(user_id)
239
239
240 _repos = c.user.repositories
240 _repos = c.user.repositories
241 _repo_groups = c.user.repository_groups
241 _repo_groups = c.user.repository_groups
242 _user_groups = c.user.user_groups
242 _user_groups = c.user.user_groups
243
243
244 handle_repos = None
244 handle_repos = None
245 handle_repo_groups = None
245 handle_repo_groups = None
246 handle_user_groups = None
246 handle_user_groups = None
247 # dummy call for flash of handle
247 # dummy call for flash of handle
248 set_handle_flash_repos = lambda: None
248 set_handle_flash_repos = lambda: None
249 set_handle_flash_repo_groups = lambda: None
249 set_handle_flash_repo_groups = lambda: None
250 set_handle_flash_user_groups = lambda: None
250 set_handle_flash_user_groups = lambda: None
251
251
252 if _repos and request.POST.get('user_repos'):
252 if _repos and request.POST.get('user_repos'):
253 do = request.POST['user_repos']
253 do = request.POST['user_repos']
254 if do == 'detach':
254 if do == 'detach':
255 handle_repos = 'detach'
255 handle_repos = 'detach'
256 set_handle_flash_repos = lambda: h.flash(
256 set_handle_flash_repos = lambda: h.flash(
257 _('Detached %s repositories') % len(_repos),
257 _('Detached %s repositories') % len(_repos),
258 category='success')
258 category='success')
259 elif do == 'delete':
259 elif do == 'delete':
260 handle_repos = 'delete'
260 handle_repos = 'delete'
261 set_handle_flash_repos = lambda: h.flash(
261 set_handle_flash_repos = lambda: h.flash(
262 _('Deleted %s repositories') % len(_repos),
262 _('Deleted %s repositories') % len(_repos),
263 category='success')
263 category='success')
264
264
265 if _repo_groups and request.POST.get('user_repo_groups'):
265 if _repo_groups and request.POST.get('user_repo_groups'):
266 do = request.POST['user_repo_groups']
266 do = request.POST['user_repo_groups']
267 if do == 'detach':
267 if do == 'detach':
268 handle_repo_groups = 'detach'
268 handle_repo_groups = 'detach'
269 set_handle_flash_repo_groups = lambda: h.flash(
269 set_handle_flash_repo_groups = lambda: h.flash(
270 _('Detached %s repository groups') % len(_repo_groups),
270 _('Detached %s repository groups') % len(_repo_groups),
271 category='success')
271 category='success')
272 elif do == 'delete':
272 elif do == 'delete':
273 handle_repo_groups = 'delete'
273 handle_repo_groups = 'delete'
274 set_handle_flash_repo_groups = lambda: h.flash(
274 set_handle_flash_repo_groups = lambda: h.flash(
275 _('Deleted %s repository groups') % len(_repo_groups),
275 _('Deleted %s repository groups') % len(_repo_groups),
276 category='success')
276 category='success')
277
277
278 if _user_groups and request.POST.get('user_user_groups'):
278 if _user_groups and request.POST.get('user_user_groups'):
279 do = request.POST['user_user_groups']
279 do = request.POST['user_user_groups']
280 if do == 'detach':
280 if do == 'detach':
281 handle_user_groups = 'detach'
281 handle_user_groups = 'detach'
282 set_handle_flash_user_groups = lambda: h.flash(
282 set_handle_flash_user_groups = lambda: h.flash(
283 _('Detached %s user groups') % len(_user_groups),
283 _('Detached %s user groups') % len(_user_groups),
284 category='success')
284 category='success')
285 elif do == 'delete':
285 elif do == 'delete':
286 handle_user_groups = 'delete'
286 handle_user_groups = 'delete'
287 set_handle_flash_user_groups = lambda: h.flash(
287 set_handle_flash_user_groups = lambda: h.flash(
288 _('Deleted %s user groups') % len(_user_groups),
288 _('Deleted %s user groups') % len(_user_groups),
289 category='success')
289 category='success')
290
290
291 try:
291 try:
292 UserModel().delete(c.user, handle_repos=handle_repos,
292 UserModel().delete(c.user, handle_repos=handle_repos,
293 handle_repo_groups=handle_repo_groups,
293 handle_repo_groups=handle_repo_groups,
294 handle_user_groups=handle_user_groups)
294 handle_user_groups=handle_user_groups)
295 Session().commit()
295 Session().commit()
296 set_handle_flash_repos()
296 set_handle_flash_repos()
297 set_handle_flash_repo_groups()
297 set_handle_flash_repo_groups()
298 set_handle_flash_user_groups()
298 set_handle_flash_user_groups()
299 h.flash(_('Successfully deleted user'), category='success')
299 h.flash(_('Successfully deleted user'), category='success')
300 except (UserOwnsReposException, UserOwnsRepoGroupsException,
300 except (UserOwnsReposException, UserOwnsRepoGroupsException,
301 UserOwnsUserGroupsException, DefaultUserException) as e:
301 UserOwnsUserGroupsException, DefaultUserException) as e:
302 h.flash(e, category='warning')
302 h.flash(e, category='warning')
303 except Exception:
303 except Exception:
304 log.exception("Exception during deletion of user")
304 log.exception("Exception during deletion of user")
305 h.flash(_('An error occurred during deletion of user'),
305 h.flash(_('An error occurred during deletion of user'),
306 category='error')
306 category='error')
307 return redirect(url('users'))
307 return redirect(url('users'))
308
308
309 @HasPermissionAllDecorator('hg.admin')
309 @HasPermissionAllDecorator('hg.admin')
310 @auth.CSRFRequired()
310 @auth.CSRFRequired()
311 def reset_password(self, user_id):
311 def reset_password(self, user_id):
312 """
312 """
313 toggle reset password flag for this user
313 toggle reset password flag for this user
314
314
315 :param user_id:
315 :param user_id:
316 """
316 """
317 user_id = safe_int(user_id)
317 user_id = safe_int(user_id)
318 c.user = User.get_or_404(user_id)
318 c.user = User.get_or_404(user_id)
319 try:
319 try:
320 old_value = c.user.user_data.get('force_password_change')
320 old_value = c.user.user_data.get('force_password_change')
321 c.user.update_userdata(force_password_change=not old_value)
321 c.user.update_userdata(force_password_change=not old_value)
322 Session().commit()
322 Session().commit()
323 if old_value:
323 if old_value:
324 msg = _('Force password change disabled for user')
324 msg = _('Force password change disabled for user')
325 else:
325 else:
326 msg = _('Force password change enabled for user')
326 msg = _('Force password change enabled for user')
327 h.flash(msg, category='success')
327 h.flash(msg, category='success')
328 except Exception:
328 except Exception:
329 log.exception("Exception during password reset for user")
329 log.exception("Exception during password reset for user")
330 h.flash(_('An error occurred during password reset for user'),
330 h.flash(_('An error occurred during password reset for user'),
331 category='error')
331 category='error')
332
332
333 return redirect(url('edit_user_advanced', user_id=user_id))
333 return redirect(url('edit_user_advanced', user_id=user_id))
334
334
335 @HasPermissionAllDecorator('hg.admin')
335 @HasPermissionAllDecorator('hg.admin')
336 @auth.CSRFRequired()
336 @auth.CSRFRequired()
337 def create_personal_repo_group(self, user_id):
337 def create_personal_repo_group(self, user_id):
338 """
338 """
339 Create personal repository group for this user
339 Create personal repository group for this user
340
340
341 :param user_id:
341 :param user_id:
342 """
342 """
343 from rhodecode.model.repo_group import RepoGroupModel
343 from rhodecode.model.repo_group import RepoGroupModel
344
344
345 user_id = safe_int(user_id)
345 user_id = safe_int(user_id)
346 c.user = User.get_or_404(user_id)
346 c.user = User.get_or_404(user_id)
347
347
348 try:
348 try:
349 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {
349 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {
350 'username': c.user.username}
350 'username': c.user.username}
351 if not RepoGroup.get_by_group_name(c.user.username):
351 if not RepoGroup.get_by_group_name(c.user.username):
352 RepoGroupModel().create(group_name=c.user.username,
352 RepoGroupModel().create(group_name=c.user.username,
353 group_description=desc,
353 group_description=desc,
354 owner=c.user.username)
354 owner=c.user.username)
355
355
356 msg = _('Created repository group `%s`' % (c.user.username,))
356 msg = _('Created repository group `%s`' % (c.user.username,))
357 h.flash(msg, category='success')
357 h.flash(msg, category='success')
358 except Exception:
358 except Exception:
359 log.exception("Exception during repository group creation")
359 log.exception("Exception during repository group creation")
360 msg = _(
360 msg = _(
361 'An error occurred during repository group creation for user')
361 'An error occurred during repository group creation for user')
362 h.flash(msg, category='error')
362 h.flash(msg, category='error')
363
363
364 return redirect(url('edit_user_advanced', user_id=user_id))
364 return redirect(url('edit_user_advanced', user_id=user_id))
365
365
366 @HasPermissionAllDecorator('hg.admin')
366 @HasPermissionAllDecorator('hg.admin')
367 def show(self, user_id):
367 def show(self, user_id):
368 """GET /users/user_id: Show a specific item"""
368 """GET /users/user_id: Show a specific item"""
369 # url('user', user_id=ID)
369 # url('user', user_id=ID)
370 User.get_or_404(-1)
370 User.get_or_404(-1)
371
371
372 @HasPermissionAllDecorator('hg.admin')
372 @HasPermissionAllDecorator('hg.admin')
373 def edit(self, user_id):
373 def edit(self, user_id):
374 """GET /users/user_id/edit: Form to edit an existing item"""
374 """GET /users/user_id/edit: Form to edit an existing item"""
375 # url('edit_user', user_id=ID)
375 # url('edit_user', user_id=ID)
376 user_id = safe_int(user_id)
376 user_id = safe_int(user_id)
377 c.user = User.get_or_404(user_id)
377 c.user = User.get_or_404(user_id)
378 if c.user.username == User.DEFAULT_USER:
378 if c.user.username == User.DEFAULT_USER:
379 h.flash(_("You can't edit this user"), category='warning')
379 h.flash(_("You can't edit this user"), category='warning')
380 return redirect(url('users'))
380 return redirect(url('users'))
381
381
382 c.active = 'profile'
382 c.active = 'profile'
383 c.extern_type = c.user.extern_type
383 c.extern_type = c.user.extern_type
384 c.extern_name = c.user.extern_name
384 c.extern_name = c.user.extern_name
385 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
385 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
386
386
387 defaults = c.user.get_dict()
387 defaults = c.user.get_dict()
388 defaults.update({'language': c.user.user_data.get('language')})
388 defaults.update({'language': c.user.user_data.get('language')})
389 return htmlfill.render(
389 return htmlfill.render(
390 render('admin/users/user_edit.html'),
390 render('admin/users/user_edit.html'),
391 defaults=defaults,
391 defaults=defaults,
392 encoding="UTF-8",
392 encoding="UTF-8",
393 force_defaults=False)
393 force_defaults=False)
394
394
395 @HasPermissionAllDecorator('hg.admin')
395 @HasPermissionAllDecorator('hg.admin')
396 def edit_advanced(self, user_id):
396 def edit_advanced(self, user_id):
397 user_id = safe_int(user_id)
397 user_id = safe_int(user_id)
398 user = c.user = User.get_or_404(user_id)
398 user = c.user = User.get_or_404(user_id)
399 if user.username == User.DEFAULT_USER:
399 if user.username == User.DEFAULT_USER:
400 h.flash(_("You can't edit this user"), category='warning')
400 h.flash(_("You can't edit this user"), category='warning')
401 return redirect(url('users'))
401 return redirect(url('users'))
402
402
403 c.active = 'advanced'
403 c.active = 'advanced'
404 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
404 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
405 c.personal_repo_group = RepoGroup.get_by_group_name(user.username)
405 c.personal_repo_group = RepoGroup.get_by_group_name(user.username)
406 c.first_admin = User.get_first_admin()
406 c.first_admin = User.get_first_super_admin()
407 defaults = user.get_dict()
407 defaults = user.get_dict()
408
408
409 # Interim workaround if the user participated on any pull requests as a
409 # Interim workaround if the user participated on any pull requests as a
410 # reviewer.
410 # reviewer.
411 has_review = bool(PullRequestReviewers.query().filter(
411 has_review = bool(PullRequestReviewers.query().filter(
412 PullRequestReviewers.user_id == user_id).first())
412 PullRequestReviewers.user_id == user_id).first())
413 c.can_delete_user = not has_review
413 c.can_delete_user = not has_review
414 c.can_delete_user_message = _(
414 c.can_delete_user_message = _(
415 'The user participates as reviewer in pull requests and '
415 'The user participates as reviewer in pull requests and '
416 'cannot be deleted. You can set the user to '
416 'cannot be deleted. You can set the user to '
417 '"inactive" instead of deleting it.') if has_review else ''
417 '"inactive" instead of deleting it.') if has_review else ''
418
418
419 return htmlfill.render(
419 return htmlfill.render(
420 render('admin/users/user_edit.html'),
420 render('admin/users/user_edit.html'),
421 defaults=defaults,
421 defaults=defaults,
422 encoding="UTF-8",
422 encoding="UTF-8",
423 force_defaults=False)
423 force_defaults=False)
424
424
425 @HasPermissionAllDecorator('hg.admin')
425 @HasPermissionAllDecorator('hg.admin')
426 def edit_auth_tokens(self, user_id):
426 def edit_auth_tokens(self, user_id):
427 user_id = safe_int(user_id)
427 user_id = safe_int(user_id)
428 c.user = User.get_or_404(user_id)
428 c.user = User.get_or_404(user_id)
429 if c.user.username == User.DEFAULT_USER:
429 if c.user.username == User.DEFAULT_USER:
430 h.flash(_("You can't edit this user"), category='warning')
430 h.flash(_("You can't edit this user"), category='warning')
431 return redirect(url('users'))
431 return redirect(url('users'))
432
432
433 c.active = 'auth_tokens'
433 c.active = 'auth_tokens'
434 show_expired = True
434 show_expired = True
435 c.lifetime_values = [
435 c.lifetime_values = [
436 (str(-1), _('forever')),
436 (str(-1), _('forever')),
437 (str(5), _('5 minutes')),
437 (str(5), _('5 minutes')),
438 (str(60), _('1 hour')),
438 (str(60), _('1 hour')),
439 (str(60 * 24), _('1 day')),
439 (str(60 * 24), _('1 day')),
440 (str(60 * 24 * 30), _('1 month')),
440 (str(60 * 24 * 30), _('1 month')),
441 ]
441 ]
442 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
442 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
443 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
443 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
444 for x in AuthTokenModel.cls.ROLES]
444 for x in AuthTokenModel.cls.ROLES]
445 c.role_options = [(c.role_values, _("Role"))]
445 c.role_options = [(c.role_values, _("Role"))]
446 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
446 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
447 c.user.user_id, show_expired=show_expired)
447 c.user.user_id, show_expired=show_expired)
448 defaults = c.user.get_dict()
448 defaults = c.user.get_dict()
449 return htmlfill.render(
449 return htmlfill.render(
450 render('admin/users/user_edit.html'),
450 render('admin/users/user_edit.html'),
451 defaults=defaults,
451 defaults=defaults,
452 encoding="UTF-8",
452 encoding="UTF-8",
453 force_defaults=False)
453 force_defaults=False)
454
454
455 @HasPermissionAllDecorator('hg.admin')
455 @HasPermissionAllDecorator('hg.admin')
456 @auth.CSRFRequired()
456 @auth.CSRFRequired()
457 def add_auth_token(self, user_id):
457 def add_auth_token(self, user_id):
458 user_id = safe_int(user_id)
458 user_id = safe_int(user_id)
459 c.user = User.get_or_404(user_id)
459 c.user = User.get_or_404(user_id)
460 if c.user.username == User.DEFAULT_USER:
460 if c.user.username == User.DEFAULT_USER:
461 h.flash(_("You can't edit this user"), category='warning')
461 h.flash(_("You can't edit this user"), category='warning')
462 return redirect(url('users'))
462 return redirect(url('users'))
463
463
464 lifetime = safe_int(request.POST.get('lifetime'), -1)
464 lifetime = safe_int(request.POST.get('lifetime'), -1)
465 description = request.POST.get('description')
465 description = request.POST.get('description')
466 role = request.POST.get('role')
466 role = request.POST.get('role')
467 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
467 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
468 Session().commit()
468 Session().commit()
469 h.flash(_("Auth token successfully created"), category='success')
469 h.flash(_("Auth token successfully created"), category='success')
470 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
470 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
471
471
472 @HasPermissionAllDecorator('hg.admin')
472 @HasPermissionAllDecorator('hg.admin')
473 @auth.CSRFRequired()
473 @auth.CSRFRequired()
474 def delete_auth_token(self, user_id):
474 def delete_auth_token(self, user_id):
475 user_id = safe_int(user_id)
475 user_id = safe_int(user_id)
476 c.user = User.get_or_404(user_id)
476 c.user = User.get_or_404(user_id)
477 if c.user.username == User.DEFAULT_USER:
477 if c.user.username == User.DEFAULT_USER:
478 h.flash(_("You can't edit this user"), category='warning')
478 h.flash(_("You can't edit this user"), category='warning')
479 return redirect(url('users'))
479 return redirect(url('users'))
480
480
481 auth_token = request.POST.get('del_auth_token')
481 auth_token = request.POST.get('del_auth_token')
482 if request.POST.get('del_auth_token_builtin'):
482 if request.POST.get('del_auth_token_builtin'):
483 user = User.get(c.user.user_id)
483 user = User.get(c.user.user_id)
484 if user:
484 if user:
485 user.api_key = generate_auth_token(user.username)
485 user.api_key = generate_auth_token(user.username)
486 Session().add(user)
486 Session().add(user)
487 Session().commit()
487 Session().commit()
488 h.flash(_("Auth token successfully reset"), category='success')
488 h.flash(_("Auth token successfully reset"), category='success')
489 elif auth_token:
489 elif auth_token:
490 AuthTokenModel().delete(auth_token, c.user.user_id)
490 AuthTokenModel().delete(auth_token, c.user.user_id)
491 Session().commit()
491 Session().commit()
492 h.flash(_("Auth token successfully deleted"), category='success')
492 h.flash(_("Auth token successfully deleted"), category='success')
493
493
494 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
494 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
495
495
496 @HasPermissionAllDecorator('hg.admin')
496 @HasPermissionAllDecorator('hg.admin')
497 def edit_global_perms(self, user_id):
497 def edit_global_perms(self, user_id):
498 user_id = safe_int(user_id)
498 user_id = safe_int(user_id)
499 c.user = User.get_or_404(user_id)
499 c.user = User.get_or_404(user_id)
500 if c.user.username == User.DEFAULT_USER:
500 if c.user.username == User.DEFAULT_USER:
501 h.flash(_("You can't edit this user"), category='warning')
501 h.flash(_("You can't edit this user"), category='warning')
502 return redirect(url('users'))
502 return redirect(url('users'))
503
503
504 c.active = 'global_perms'
504 c.active = 'global_perms'
505
505
506 c.default_user = User.get_default_user()
506 c.default_user = User.get_default_user()
507 defaults = c.user.get_dict()
507 defaults = c.user.get_dict()
508 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
508 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
509 defaults.update(c.default_user.get_default_perms())
509 defaults.update(c.default_user.get_default_perms())
510 defaults.update(c.user.get_default_perms())
510 defaults.update(c.user.get_default_perms())
511
511
512 return htmlfill.render(
512 return htmlfill.render(
513 render('admin/users/user_edit.html'),
513 render('admin/users/user_edit.html'),
514 defaults=defaults,
514 defaults=defaults,
515 encoding="UTF-8",
515 encoding="UTF-8",
516 force_defaults=False)
516 force_defaults=False)
517
517
518 @HasPermissionAllDecorator('hg.admin')
518 @HasPermissionAllDecorator('hg.admin')
519 @auth.CSRFRequired()
519 @auth.CSRFRequired()
520 def update_global_perms(self, user_id):
520 def update_global_perms(self, user_id):
521 """PUT /users_perm/user_id: Update an existing item"""
521 """PUT /users_perm/user_id: Update an existing item"""
522 # url('user_perm', user_id=ID, method='put')
522 # url('user_perm', user_id=ID, method='put')
523 user_id = safe_int(user_id)
523 user_id = safe_int(user_id)
524 user = User.get_or_404(user_id)
524 user = User.get_or_404(user_id)
525 c.active = 'global_perms'
525 c.active = 'global_perms'
526 try:
526 try:
527 # first stage that verifies the checkbox
527 # first stage that verifies the checkbox
528 _form = UserIndividualPermissionsForm()
528 _form = UserIndividualPermissionsForm()
529 form_result = _form.to_python(dict(request.POST))
529 form_result = _form.to_python(dict(request.POST))
530 inherit_perms = form_result['inherit_default_permissions']
530 inherit_perms = form_result['inherit_default_permissions']
531 user.inherit_default_permissions = inherit_perms
531 user.inherit_default_permissions = inherit_perms
532 Session().add(user)
532 Session().add(user)
533
533
534 if not inherit_perms:
534 if not inherit_perms:
535 # only update the individual ones if we un check the flag
535 # only update the individual ones if we un check the flag
536 _form = UserPermissionsForm(
536 _form = UserPermissionsForm(
537 [x[0] for x in c.repo_create_choices],
537 [x[0] for x in c.repo_create_choices],
538 [x[0] for x in c.repo_create_on_write_choices],
538 [x[0] for x in c.repo_create_on_write_choices],
539 [x[0] for x in c.repo_group_create_choices],
539 [x[0] for x in c.repo_group_create_choices],
540 [x[0] for x in c.user_group_create_choices],
540 [x[0] for x in c.user_group_create_choices],
541 [x[0] for x in c.fork_choices],
541 [x[0] for x in c.fork_choices],
542 [x[0] for x in c.inherit_default_permission_choices])()
542 [x[0] for x in c.inherit_default_permission_choices])()
543
543
544 form_result = _form.to_python(dict(request.POST))
544 form_result = _form.to_python(dict(request.POST))
545 form_result.update({'perm_user_id': user.user_id})
545 form_result.update({'perm_user_id': user.user_id})
546
546
547 PermissionModel().update_user_permissions(form_result)
547 PermissionModel().update_user_permissions(form_result)
548
548
549 Session().commit()
549 Session().commit()
550 h.flash(_('User global permissions updated successfully'),
550 h.flash(_('User global permissions updated successfully'),
551 category='success')
551 category='success')
552
552
553 Session().commit()
553 Session().commit()
554 except formencode.Invalid as errors:
554 except formencode.Invalid as errors:
555 defaults = errors.value
555 defaults = errors.value
556 c.user = user
556 c.user = user
557 return htmlfill.render(
557 return htmlfill.render(
558 render('admin/users/user_edit.html'),
558 render('admin/users/user_edit.html'),
559 defaults=defaults,
559 defaults=defaults,
560 errors=errors.error_dict or {},
560 errors=errors.error_dict or {},
561 prefix_error=False,
561 prefix_error=False,
562 encoding="UTF-8",
562 encoding="UTF-8",
563 force_defaults=False)
563 force_defaults=False)
564 except Exception:
564 except Exception:
565 log.exception("Exception during permissions saving")
565 log.exception("Exception during permissions saving")
566 h.flash(_('An error occurred during permissions saving'),
566 h.flash(_('An error occurred during permissions saving'),
567 category='error')
567 category='error')
568 return redirect(url('edit_user_global_perms', user_id=user_id))
568 return redirect(url('edit_user_global_perms', user_id=user_id))
569
569
570 @HasPermissionAllDecorator('hg.admin')
570 @HasPermissionAllDecorator('hg.admin')
571 def edit_perms_summary(self, user_id):
571 def edit_perms_summary(self, user_id):
572 user_id = safe_int(user_id)
572 user_id = safe_int(user_id)
573 c.user = User.get_or_404(user_id)
573 c.user = User.get_or_404(user_id)
574 if c.user.username == User.DEFAULT_USER:
574 if c.user.username == User.DEFAULT_USER:
575 h.flash(_("You can't edit this user"), category='warning')
575 h.flash(_("You can't edit this user"), category='warning')
576 return redirect(url('users'))
576 return redirect(url('users'))
577
577
578 c.active = 'perms_summary'
578 c.active = 'perms_summary'
579 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
579 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
580
580
581 return render('admin/users/user_edit.html')
581 return render('admin/users/user_edit.html')
582
582
583 @HasPermissionAllDecorator('hg.admin')
583 @HasPermissionAllDecorator('hg.admin')
584 def edit_emails(self, user_id):
584 def edit_emails(self, user_id):
585 user_id = safe_int(user_id)
585 user_id = safe_int(user_id)
586 c.user = User.get_or_404(user_id)
586 c.user = User.get_or_404(user_id)
587 if c.user.username == User.DEFAULT_USER:
587 if c.user.username == User.DEFAULT_USER:
588 h.flash(_("You can't edit this user"), category='warning')
588 h.flash(_("You can't edit this user"), category='warning')
589 return redirect(url('users'))
589 return redirect(url('users'))
590
590
591 c.active = 'emails'
591 c.active = 'emails'
592 c.user_email_map = UserEmailMap.query() \
592 c.user_email_map = UserEmailMap.query() \
593 .filter(UserEmailMap.user == c.user).all()
593 .filter(UserEmailMap.user == c.user).all()
594
594
595 defaults = c.user.get_dict()
595 defaults = c.user.get_dict()
596 return htmlfill.render(
596 return htmlfill.render(
597 render('admin/users/user_edit.html'),
597 render('admin/users/user_edit.html'),
598 defaults=defaults,
598 defaults=defaults,
599 encoding="UTF-8",
599 encoding="UTF-8",
600 force_defaults=False)
600 force_defaults=False)
601
601
602 @HasPermissionAllDecorator('hg.admin')
602 @HasPermissionAllDecorator('hg.admin')
603 @auth.CSRFRequired()
603 @auth.CSRFRequired()
604 def add_email(self, user_id):
604 def add_email(self, user_id):
605 """POST /user_emails:Add an existing item"""
605 """POST /user_emails:Add an existing item"""
606 # url('user_emails', user_id=ID, method='put')
606 # url('user_emails', user_id=ID, method='put')
607 user_id = safe_int(user_id)
607 user_id = safe_int(user_id)
608 c.user = User.get_or_404(user_id)
608 c.user = User.get_or_404(user_id)
609
609
610 email = request.POST.get('new_email')
610 email = request.POST.get('new_email')
611 user_model = UserModel()
611 user_model = UserModel()
612
612
613 try:
613 try:
614 user_model.add_extra_email(user_id, email)
614 user_model.add_extra_email(user_id, email)
615 Session().commit()
615 Session().commit()
616 h.flash(_("Added new email address `%s` for user account") % email,
616 h.flash(_("Added new email address `%s` for user account") % email,
617 category='success')
617 category='success')
618 except formencode.Invalid as error:
618 except formencode.Invalid as error:
619 msg = error.error_dict['email']
619 msg = error.error_dict['email']
620 h.flash(msg, category='error')
620 h.flash(msg, category='error')
621 except Exception:
621 except Exception:
622 log.exception("Exception during email saving")
622 log.exception("Exception during email saving")
623 h.flash(_('An error occurred during email saving'),
623 h.flash(_('An error occurred during email saving'),
624 category='error')
624 category='error')
625 return redirect(url('edit_user_emails', user_id=user_id))
625 return redirect(url('edit_user_emails', user_id=user_id))
626
626
627 @HasPermissionAllDecorator('hg.admin')
627 @HasPermissionAllDecorator('hg.admin')
628 @auth.CSRFRequired()
628 @auth.CSRFRequired()
629 def delete_email(self, user_id):
629 def delete_email(self, user_id):
630 """DELETE /user_emails_delete/user_id: Delete an existing item"""
630 """DELETE /user_emails_delete/user_id: Delete an existing item"""
631 # url('user_emails_delete', user_id=ID, method='delete')
631 # url('user_emails_delete', user_id=ID, method='delete')
632 user_id = safe_int(user_id)
632 user_id = safe_int(user_id)
633 c.user = User.get_or_404(user_id)
633 c.user = User.get_or_404(user_id)
634 email_id = request.POST.get('del_email_id')
634 email_id = request.POST.get('del_email_id')
635 user_model = UserModel()
635 user_model = UserModel()
636 user_model.delete_extra_email(user_id, email_id)
636 user_model.delete_extra_email(user_id, email_id)
637 Session().commit()
637 Session().commit()
638 h.flash(_("Removed email address from user account"), category='success')
638 h.flash(_("Removed email address from user account"), category='success')
639 return redirect(url('edit_user_emails', user_id=user_id))
639 return redirect(url('edit_user_emails', user_id=user_id))
640
640
641 @HasPermissionAllDecorator('hg.admin')
641 @HasPermissionAllDecorator('hg.admin')
642 def edit_ips(self, user_id):
642 def edit_ips(self, user_id):
643 user_id = safe_int(user_id)
643 user_id = safe_int(user_id)
644 c.user = User.get_or_404(user_id)
644 c.user = User.get_or_404(user_id)
645 if c.user.username == User.DEFAULT_USER:
645 if c.user.username == User.DEFAULT_USER:
646 h.flash(_("You can't edit this user"), category='warning')
646 h.flash(_("You can't edit this user"), category='warning')
647 return redirect(url('users'))
647 return redirect(url('users'))
648
648
649 c.active = 'ips'
649 c.active = 'ips'
650 c.user_ip_map = UserIpMap.query() \
650 c.user_ip_map = UserIpMap.query() \
651 .filter(UserIpMap.user == c.user).all()
651 .filter(UserIpMap.user == c.user).all()
652
652
653 c.inherit_default_ips = c.user.inherit_default_permissions
653 c.inherit_default_ips = c.user.inherit_default_permissions
654 c.default_user_ip_map = UserIpMap.query() \
654 c.default_user_ip_map = UserIpMap.query() \
655 .filter(UserIpMap.user == User.get_default_user()).all()
655 .filter(UserIpMap.user == User.get_default_user()).all()
656
656
657 defaults = c.user.get_dict()
657 defaults = c.user.get_dict()
658 return htmlfill.render(
658 return htmlfill.render(
659 render('admin/users/user_edit.html'),
659 render('admin/users/user_edit.html'),
660 defaults=defaults,
660 defaults=defaults,
661 encoding="UTF-8",
661 encoding="UTF-8",
662 force_defaults=False)
662 force_defaults=False)
663
663
664 @HasPermissionAllDecorator('hg.admin')
664 @HasPermissionAllDecorator('hg.admin')
665 @auth.CSRFRequired()
665 @auth.CSRFRequired()
666 def add_ip(self, user_id):
666 def add_ip(self, user_id):
667 """POST /user_ips:Add an existing item"""
667 """POST /user_ips:Add an existing item"""
668 # url('user_ips', user_id=ID, method='put')
668 # url('user_ips', user_id=ID, method='put')
669
669
670 user_id = safe_int(user_id)
670 user_id = safe_int(user_id)
671 c.user = User.get_or_404(user_id)
671 c.user = User.get_or_404(user_id)
672 user_model = UserModel()
672 user_model = UserModel()
673 try:
673 try:
674 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
674 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
675 except Exception as e:
675 except Exception as e:
676 ip_list = []
676 ip_list = []
677 log.exception("Exception during ip saving")
677 log.exception("Exception during ip saving")
678 h.flash(_('An error occurred during ip saving:%s' % (e,)),
678 h.flash(_('An error occurred during ip saving:%s' % (e,)),
679 category='error')
679 category='error')
680
680
681 desc = request.POST.get('description')
681 desc = request.POST.get('description')
682 added = []
682 added = []
683 for ip in ip_list:
683 for ip in ip_list:
684 try:
684 try:
685 user_model.add_extra_ip(user_id, ip, desc)
685 user_model.add_extra_ip(user_id, ip, desc)
686 Session().commit()
686 Session().commit()
687 added.append(ip)
687 added.append(ip)
688 except formencode.Invalid as error:
688 except formencode.Invalid as error:
689 msg = error.error_dict['ip']
689 msg = error.error_dict['ip']
690 h.flash(msg, category='error')
690 h.flash(msg, category='error')
691 except Exception:
691 except Exception:
692 log.exception("Exception during ip saving")
692 log.exception("Exception during ip saving")
693 h.flash(_('An error occurred during ip saving'),
693 h.flash(_('An error occurred during ip saving'),
694 category='error')
694 category='error')
695 if added:
695 if added:
696 h.flash(
696 h.flash(
697 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
697 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
698 category='success')
698 category='success')
699 if 'default_user' in request.POST:
699 if 'default_user' in request.POST:
700 return redirect(url('admin_permissions_ips'))
700 return redirect(url('admin_permissions_ips'))
701 return redirect(url('edit_user_ips', user_id=user_id))
701 return redirect(url('edit_user_ips', user_id=user_id))
702
702
703 @HasPermissionAllDecorator('hg.admin')
703 @HasPermissionAllDecorator('hg.admin')
704 @auth.CSRFRequired()
704 @auth.CSRFRequired()
705 def delete_ip(self, user_id):
705 def delete_ip(self, user_id):
706 """DELETE /user_ips_delete/user_id: Delete an existing item"""
706 """DELETE /user_ips_delete/user_id: Delete an existing item"""
707 # url('user_ips_delete', user_id=ID, method='delete')
707 # url('user_ips_delete', user_id=ID, method='delete')
708 user_id = safe_int(user_id)
708 user_id = safe_int(user_id)
709 c.user = User.get_or_404(user_id)
709 c.user = User.get_or_404(user_id)
710
710
711 ip_id = request.POST.get('del_ip_id')
711 ip_id = request.POST.get('del_ip_id')
712 user_model = UserModel()
712 user_model = UserModel()
713 user_model.delete_extra_ip(user_id, ip_id)
713 user_model.delete_extra_ip(user_id, ip_id)
714 Session().commit()
714 Session().commit()
715 h.flash(_("Removed ip address from user whitelist"), category='success')
715 h.flash(_("Removed ip address from user whitelist"), category='success')
716
716
717 if 'default_user' in request.POST:
717 if 'default_user' in request.POST:
718 return redirect(url('admin_permissions_ips'))
718 return redirect(url('admin_permissions_ips'))
719 return redirect(url('edit_user_ips', user_id=user_id))
719 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,977 +1,977 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 from os.path import abspath
36 from os.path import abspath
37 from os.path import dirname as dn, join as jn
37 from os.path import dirname as dn, join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44
44
45 from rhodecode.lib.fakemod import create_module
45 from rhodecode.lib.fakemod import create_module
46 from rhodecode.lib.vcs.backends.base import Config
46 from rhodecode.lib.vcs.backends.base import Config
47 from rhodecode.lib.vcs.exceptions import VCSError
47 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
48 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 from rhodecode.lib.utils2 import (
49 from rhodecode.lib.utils2 import (
50 safe_str, safe_unicode, get_current_rhodecode_user, md5)
50 safe_str, safe_unicode, get_current_rhodecode_user, md5)
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import (
52 from rhodecode.model.db import (
53 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
53 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55
55
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
59 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
60
60
61 _license_cache = None
61 _license_cache = None
62
62
63
63
64 def recursive_replace(str_, replace=' '):
64 def recursive_replace(str_, replace=' '):
65 """
65 """
66 Recursive replace of given sign to just one instance
66 Recursive replace of given sign to just one instance
67
67
68 :param str_: given string
68 :param str_: given string
69 :param replace: char to find and replace multiple instances
69 :param replace: char to find and replace multiple instances
70
70
71 Examples::
71 Examples::
72 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
72 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
73 'Mighty-Mighty-Bo-sstones'
73 'Mighty-Mighty-Bo-sstones'
74 """
74 """
75
75
76 if str_.find(replace * 2) == -1:
76 if str_.find(replace * 2) == -1:
77 return str_
77 return str_
78 else:
78 else:
79 str_ = str_.replace(replace * 2, replace)
79 str_ = str_.replace(replace * 2, replace)
80 return recursive_replace(str_, replace)
80 return recursive_replace(str_, replace)
81
81
82
82
83 def repo_name_slug(value):
83 def repo_name_slug(value):
84 """
84 """
85 Return slug of name of repository
85 Return slug of name of repository
86 This function is called on each creation/modification
86 This function is called on each creation/modification
87 of repository to prevent bad names in repo
87 of repository to prevent bad names in repo
88 """
88 """
89
89
90 slug = remove_formatting(value)
90 slug = remove_formatting(value)
91 slug = strip_tags(slug)
91 slug = strip_tags(slug)
92
92
93 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
93 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
94 slug = slug.replace(c, '-')
94 slug = slug.replace(c, '-')
95 slug = recursive_replace(slug, '-')
95 slug = recursive_replace(slug, '-')
96 slug = collapse(slug, '-')
96 slug = collapse(slug, '-')
97 return slug
97 return slug
98
98
99
99
100 #==============================================================================
100 #==============================================================================
101 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
101 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
102 #==============================================================================
102 #==============================================================================
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repo_group_slug(request):
110 def get_repo_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def get_user_group_slug(request):
117 def get_user_group_slug(request):
118 _group = request.environ['pylons.routes_dict'].get('user_group_id')
118 _group = request.environ['pylons.routes_dict'].get('user_group_id')
119 try:
119 try:
120 _group = UserGroup.get(_group)
120 _group = UserGroup.get(_group)
121 if _group:
121 if _group:
122 _group = _group.users_group_name
122 _group = _group.users_group_name
123 except Exception:
123 except Exception:
124 log.debug(traceback.format_exc())
124 log.debug(traceback.format_exc())
125 #catch all failures here
125 #catch all failures here
126 pass
126 pass
127
127
128 return _group
128 return _group
129
129
130
130
131 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
131 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
132 """
132 """
133 Action logger for various actions made by users
133 Action logger for various actions made by users
134
134
135 :param user: user that made this action, can be a unique username string or
135 :param user: user that made this action, can be a unique username string or
136 object containing user_id attribute
136 object containing user_id attribute
137 :param action: action to log, should be on of predefined unique actions for
137 :param action: action to log, should be on of predefined unique actions for
138 easy translations
138 easy translations
139 :param repo: string name of repository or object containing repo_id,
139 :param repo: string name of repository or object containing repo_id,
140 that action was made on
140 that action was made on
141 :param ipaddr: optional ip address from what the action was made
141 :param ipaddr: optional ip address from what the action was made
142 :param sa: optional sqlalchemy session
142 :param sa: optional sqlalchemy session
143
143
144 """
144 """
145
145
146 if not sa:
146 if not sa:
147 sa = meta.Session()
147 sa = meta.Session()
148 # if we don't get explicit IP address try to get one from registered user
148 # if we don't get explicit IP address try to get one from registered user
149 # in tmpl context var
149 # in tmpl context var
150 if not ipaddr:
150 if not ipaddr:
151 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
151 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
152
152
153 try:
153 try:
154 if getattr(user, 'user_id', None):
154 if getattr(user, 'user_id', None):
155 user_obj = User.get(user.user_id)
155 user_obj = User.get(user.user_id)
156 elif isinstance(user, basestring):
156 elif isinstance(user, basestring):
157 user_obj = User.get_by_username(user)
157 user_obj = User.get_by_username(user)
158 else:
158 else:
159 raise Exception('You have to provide a user object or a username')
159 raise Exception('You have to provide a user object or a username')
160
160
161 if getattr(repo, 'repo_id', None):
161 if getattr(repo, 'repo_id', None):
162 repo_obj = Repository.get(repo.repo_id)
162 repo_obj = Repository.get(repo.repo_id)
163 repo_name = repo_obj.repo_name
163 repo_name = repo_obj.repo_name
164 elif isinstance(repo, basestring):
164 elif isinstance(repo, basestring):
165 repo_name = repo.lstrip('/')
165 repo_name = repo.lstrip('/')
166 repo_obj = Repository.get_by_repo_name(repo_name)
166 repo_obj = Repository.get_by_repo_name(repo_name)
167 else:
167 else:
168 repo_obj = None
168 repo_obj = None
169 repo_name = ''
169 repo_name = ''
170
170
171 user_log = UserLog()
171 user_log = UserLog()
172 user_log.user_id = user_obj.user_id
172 user_log.user_id = user_obj.user_id
173 user_log.username = user_obj.username
173 user_log.username = user_obj.username
174 action = safe_unicode(action)
174 action = safe_unicode(action)
175 user_log.action = action[:1200000]
175 user_log.action = action[:1200000]
176
176
177 user_log.repository = repo_obj
177 user_log.repository = repo_obj
178 user_log.repository_name = repo_name
178 user_log.repository_name = repo_name
179
179
180 user_log.action_date = datetime.datetime.now()
180 user_log.action_date = datetime.datetime.now()
181 user_log.user_ip = ipaddr
181 user_log.user_ip = ipaddr
182 sa.add(user_log)
182 sa.add(user_log)
183
183
184 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
184 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
185 action, safe_unicode(repo), user_obj, ipaddr)
185 action, safe_unicode(repo), user_obj, ipaddr)
186 if commit:
186 if commit:
187 sa.commit()
187 sa.commit()
188 except Exception:
188 except Exception:
189 log.error(traceback.format_exc())
189 log.error(traceback.format_exc())
190 raise
190 raise
191
191
192
192
193 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
193 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
194 """
194 """
195 Scans given path for repos and return (name,(type,path)) tuple
195 Scans given path for repos and return (name,(type,path)) tuple
196
196
197 :param path: path to scan for repositories
197 :param path: path to scan for repositories
198 :param recursive: recursive search and return names with subdirs in front
198 :param recursive: recursive search and return names with subdirs in front
199 """
199 """
200
200
201 # remove ending slash for better results
201 # remove ending slash for better results
202 path = path.rstrip(os.sep)
202 path = path.rstrip(os.sep)
203 log.debug('now scanning in %s location recursive:%s...', path, recursive)
203 log.debug('now scanning in %s location recursive:%s...', path, recursive)
204
204
205 def _get_repos(p):
205 def _get_repos(p):
206 dirpaths = _get_dirpaths(p)
206 dirpaths = _get_dirpaths(p)
207 if not _is_dir_writable(p):
207 if not _is_dir_writable(p):
208 log.warning('repo path without write access: %s', p)
208 log.warning('repo path without write access: %s', p)
209
209
210 for dirpath in dirpaths:
210 for dirpath in dirpaths:
211 if os.path.isfile(os.path.join(p, dirpath)):
211 if os.path.isfile(os.path.join(p, dirpath)):
212 continue
212 continue
213 cur_path = os.path.join(p, dirpath)
213 cur_path = os.path.join(p, dirpath)
214
214
215 # skip removed repos
215 # skip removed repos
216 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
216 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
217 continue
217 continue
218
218
219 #skip .<somethin> dirs
219 #skip .<somethin> dirs
220 if dirpath.startswith('.'):
220 if dirpath.startswith('.'):
221 continue
221 continue
222
222
223 try:
223 try:
224 scm_info = get_scm(cur_path)
224 scm_info = get_scm(cur_path)
225 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
225 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
226 except VCSError:
226 except VCSError:
227 if not recursive:
227 if not recursive:
228 continue
228 continue
229 #check if this dir containts other repos for recursive scan
229 #check if this dir containts other repos for recursive scan
230 rec_path = os.path.join(p, dirpath)
230 rec_path = os.path.join(p, dirpath)
231 if os.path.isdir(rec_path):
231 if os.path.isdir(rec_path):
232 for inner_scm in _get_repos(rec_path):
232 for inner_scm in _get_repos(rec_path):
233 yield inner_scm
233 yield inner_scm
234
234
235 return _get_repos(path)
235 return _get_repos(path)
236
236
237
237
238 def _get_dirpaths(p):
238 def _get_dirpaths(p):
239 try:
239 try:
240 # OS-independable way of checking if we have at least read-only
240 # OS-independable way of checking if we have at least read-only
241 # access or not.
241 # access or not.
242 dirpaths = os.listdir(p)
242 dirpaths = os.listdir(p)
243 except OSError:
243 except OSError:
244 log.warning('ignoring repo path without read access: %s', p)
244 log.warning('ignoring repo path without read access: %s', p)
245 return []
245 return []
246
246
247 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
247 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
248 # decode paths and suddenly returns unicode objects itself. The items it
248 # decode paths and suddenly returns unicode objects itself. The items it
249 # cannot decode are returned as strings and cause issues.
249 # cannot decode are returned as strings and cause issues.
250 #
250 #
251 # Those paths are ignored here until a solid solution for path handling has
251 # Those paths are ignored here until a solid solution for path handling has
252 # been built.
252 # been built.
253 expected_type = type(p)
253 expected_type = type(p)
254
254
255 def _has_correct_type(item):
255 def _has_correct_type(item):
256 if type(item) is not expected_type:
256 if type(item) is not expected_type:
257 log.error(
257 log.error(
258 u"Ignoring path %s since it cannot be decoded into unicode.",
258 u"Ignoring path %s since it cannot be decoded into unicode.",
259 # Using "repr" to make sure that we see the byte value in case
259 # Using "repr" to make sure that we see the byte value in case
260 # of support.
260 # of support.
261 repr(item))
261 repr(item))
262 return False
262 return False
263 return True
263 return True
264
264
265 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
265 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
266
266
267 return dirpaths
267 return dirpaths
268
268
269
269
270 def _is_dir_writable(path):
270 def _is_dir_writable(path):
271 """
271 """
272 Probe if `path` is writable.
272 Probe if `path` is writable.
273
273
274 Due to trouble on Cygwin / Windows, this is actually probing if it is
274 Due to trouble on Cygwin / Windows, this is actually probing if it is
275 possible to create a file inside of `path`, stat does not produce reliable
275 possible to create a file inside of `path`, stat does not produce reliable
276 results in this case.
276 results in this case.
277 """
277 """
278 try:
278 try:
279 with tempfile.TemporaryFile(dir=path):
279 with tempfile.TemporaryFile(dir=path):
280 pass
280 pass
281 except OSError:
281 except OSError:
282 return False
282 return False
283 return True
283 return True
284
284
285
285
286 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
286 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
287 """
287 """
288 Returns True if given path is a valid repository False otherwise.
288 Returns True if given path is a valid repository False otherwise.
289 If expect_scm param is given also, compare if given scm is the same
289 If expect_scm param is given also, compare if given scm is the same
290 as expected from scm parameter. If explicit_scm is given don't try to
290 as expected from scm parameter. If explicit_scm is given don't try to
291 detect the scm, just use the given one to check if repo is valid
291 detect the scm, just use the given one to check if repo is valid
292
292
293 :param repo_name:
293 :param repo_name:
294 :param base_path:
294 :param base_path:
295 :param expect_scm:
295 :param expect_scm:
296 :param explicit_scm:
296 :param explicit_scm:
297
297
298 :return True: if given path is a valid repository
298 :return True: if given path is a valid repository
299 """
299 """
300 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
300 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
301 log.debug('Checking if `%s` is a valid path for repository', repo_name)
301 log.debug('Checking if `%s` is a valid path for repository', repo_name)
302
302
303 try:
303 try:
304 if explicit_scm:
304 if explicit_scm:
305 detected_scms = [get_scm_backend(explicit_scm)]
305 detected_scms = [get_scm_backend(explicit_scm)]
306 else:
306 else:
307 detected_scms = get_scm(full_path)
307 detected_scms = get_scm(full_path)
308
308
309 if expect_scm:
309 if expect_scm:
310 return detected_scms[0] == expect_scm
310 return detected_scms[0] == expect_scm
311 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
311 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
312 return True
312 return True
313 except VCSError:
313 except VCSError:
314 log.debug('path: %s is not a valid repo !', full_path)
314 log.debug('path: %s is not a valid repo !', full_path)
315 return False
315 return False
316
316
317
317
318 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
318 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
319 """
319 """
320 Returns True if given path is a repository group, False otherwise
320 Returns True if given path is a repository group, False otherwise
321
321
322 :param repo_name:
322 :param repo_name:
323 :param base_path:
323 :param base_path:
324 """
324 """
325 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
325 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
326 log.debug('Checking if `%s` is a valid path for repository group',
326 log.debug('Checking if `%s` is a valid path for repository group',
327 repo_group_name)
327 repo_group_name)
328
328
329 # check if it's not a repo
329 # check if it's not a repo
330 if is_valid_repo(repo_group_name, base_path):
330 if is_valid_repo(repo_group_name, base_path):
331 log.debug('Repo called %s exist, it is not a valid '
331 log.debug('Repo called %s exist, it is not a valid '
332 'repo group' % repo_group_name)
332 'repo group' % repo_group_name)
333 return False
333 return False
334
334
335 try:
335 try:
336 # we need to check bare git repos at higher level
336 # we need to check bare git repos at higher level
337 # since we might match branches/hooks/info/objects or possible
337 # since we might match branches/hooks/info/objects or possible
338 # other things inside bare git repo
338 # other things inside bare git repo
339 scm_ = get_scm(os.path.dirname(full_path))
339 scm_ = get_scm(os.path.dirname(full_path))
340 log.debug('path: %s is a vcs object:%s, not valid '
340 log.debug('path: %s is a vcs object:%s, not valid '
341 'repo group' % (full_path, scm_))
341 'repo group' % (full_path, scm_))
342 return False
342 return False
343 except VCSError:
343 except VCSError:
344 pass
344 pass
345
345
346 # check if it's a valid path
346 # check if it's a valid path
347 if skip_path_check or os.path.isdir(full_path):
347 if skip_path_check or os.path.isdir(full_path):
348 log.debug('path: %s is a valid repo group !', full_path)
348 log.debug('path: %s is a valid repo group !', full_path)
349 return True
349 return True
350
350
351 log.debug('path: %s is not a valid repo group !', full_path)
351 log.debug('path: %s is not a valid repo group !', full_path)
352 return False
352 return False
353
353
354
354
355 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
355 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
356 while True:
356 while True:
357 ok = raw_input(prompt)
357 ok = raw_input(prompt)
358 if ok in ('y', 'ye', 'yes'):
358 if ok in ('y', 'ye', 'yes'):
359 return True
359 return True
360 if ok in ('n', 'no', 'nop', 'nope'):
360 if ok in ('n', 'no', 'nop', 'nope'):
361 return False
361 return False
362 retries = retries - 1
362 retries = retries - 1
363 if retries < 0:
363 if retries < 0:
364 raise IOError
364 raise IOError
365 print complaint
365 print complaint
366
366
367 # propagated from mercurial documentation
367 # propagated from mercurial documentation
368 ui_sections = [
368 ui_sections = [
369 'alias', 'auth',
369 'alias', 'auth',
370 'decode/encode', 'defaults',
370 'decode/encode', 'defaults',
371 'diff', 'email',
371 'diff', 'email',
372 'extensions', 'format',
372 'extensions', 'format',
373 'merge-patterns', 'merge-tools',
373 'merge-patterns', 'merge-tools',
374 'hooks', 'http_proxy',
374 'hooks', 'http_proxy',
375 'smtp', 'patch',
375 'smtp', 'patch',
376 'paths', 'profiling',
376 'paths', 'profiling',
377 'server', 'trusted',
377 'server', 'trusted',
378 'ui', 'web', ]
378 'ui', 'web', ]
379
379
380
380
381 def config_data_from_db(clear_session=True, repo=None):
381 def config_data_from_db(clear_session=True, repo=None):
382 """
382 """
383 Read the configuration data from the database and return configuration
383 Read the configuration data from the database and return configuration
384 tuples.
384 tuples.
385 """
385 """
386 from rhodecode.model.settings import VcsSettingsModel
386 from rhodecode.model.settings import VcsSettingsModel
387
387
388 config = []
388 config = []
389
389
390 sa = meta.Session()
390 sa = meta.Session()
391 settings_model = VcsSettingsModel(repo=repo, sa=sa)
391 settings_model = VcsSettingsModel(repo=repo, sa=sa)
392
392
393 ui_settings = settings_model.get_ui_settings()
393 ui_settings = settings_model.get_ui_settings()
394
394
395 for setting in ui_settings:
395 for setting in ui_settings:
396 if setting.active:
396 if setting.active:
397 log.debug(
397 log.debug(
398 'settings ui from db: [%s] %s=%s',
398 'settings ui from db: [%s] %s=%s',
399 setting.section, setting.key, setting.value)
399 setting.section, setting.key, setting.value)
400 config.append((
400 config.append((
401 safe_str(setting.section), safe_str(setting.key),
401 safe_str(setting.section), safe_str(setting.key),
402 safe_str(setting.value)))
402 safe_str(setting.value)))
403 if setting.key == 'push_ssl':
403 if setting.key == 'push_ssl':
404 # force set push_ssl requirement to False, rhodecode
404 # force set push_ssl requirement to False, rhodecode
405 # handles that
405 # handles that
406 config.append((
406 config.append((
407 safe_str(setting.section), safe_str(setting.key), False))
407 safe_str(setting.section), safe_str(setting.key), False))
408 if clear_session:
408 if clear_session:
409 meta.Session.remove()
409 meta.Session.remove()
410
410
411 # TODO: mikhail: probably it makes no sense to re-read hooks information.
411 # TODO: mikhail: probably it makes no sense to re-read hooks information.
412 # It's already there and activated/deactivated
412 # It's already there and activated/deactivated
413 skip_entries = []
413 skip_entries = []
414 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
414 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
415 if 'pull' not in enabled_hook_classes:
415 if 'pull' not in enabled_hook_classes:
416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
417 if 'push' not in enabled_hook_classes:
417 if 'push' not in enabled_hook_classes:
418 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
418 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
419
419
420 config = [entry for entry in config if entry[:2] not in skip_entries]
420 config = [entry for entry in config if entry[:2] not in skip_entries]
421
421
422 return config
422 return config
423
423
424
424
425 def make_db_config(clear_session=True, repo=None):
425 def make_db_config(clear_session=True, repo=None):
426 """
426 """
427 Create a :class:`Config` instance based on the values in the database.
427 Create a :class:`Config` instance based on the values in the database.
428 """
428 """
429 config = Config()
429 config = Config()
430 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
430 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
431 for section, option, value in config_data:
431 for section, option, value in config_data:
432 config.set(section, option, value)
432 config.set(section, option, value)
433 return config
433 return config
434
434
435
435
436 def get_enabled_hook_classes(ui_settings):
436 def get_enabled_hook_classes(ui_settings):
437 """
437 """
438 Return the enabled hook classes.
438 Return the enabled hook classes.
439
439
440 :param ui_settings: List of ui_settings as returned
440 :param ui_settings: List of ui_settings as returned
441 by :meth:`VcsSettingsModel.get_ui_settings`
441 by :meth:`VcsSettingsModel.get_ui_settings`
442
442
443 :return: a list with the enabled hook classes. The order is not guaranteed.
443 :return: a list with the enabled hook classes. The order is not guaranteed.
444 :rtype: list
444 :rtype: list
445 """
445 """
446 enabled_hooks = []
446 enabled_hooks = []
447 active_hook_keys = [
447 active_hook_keys = [
448 key for section, key, value, active in ui_settings
448 key for section, key, value, active in ui_settings
449 if section == 'hooks' and active]
449 if section == 'hooks' and active]
450
450
451 hook_names = {
451 hook_names = {
452 RhodeCodeUi.HOOK_PUSH: 'push',
452 RhodeCodeUi.HOOK_PUSH: 'push',
453 RhodeCodeUi.HOOK_PULL: 'pull',
453 RhodeCodeUi.HOOK_PULL: 'pull',
454 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
454 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
455 }
455 }
456
456
457 for key in active_hook_keys:
457 for key in active_hook_keys:
458 hook = hook_names.get(key)
458 hook = hook_names.get(key)
459 if hook:
459 if hook:
460 enabled_hooks.append(hook)
460 enabled_hooks.append(hook)
461
461
462 return enabled_hooks
462 return enabled_hooks
463
463
464
464
465 def set_rhodecode_config(config):
465 def set_rhodecode_config(config):
466 """
466 """
467 Updates pylons config with new settings from database
467 Updates pylons config with new settings from database
468
468
469 :param config:
469 :param config:
470 """
470 """
471 from rhodecode.model.settings import SettingsModel
471 from rhodecode.model.settings import SettingsModel
472 app_settings = SettingsModel().get_all_settings()
472 app_settings = SettingsModel().get_all_settings()
473
473
474 for k, v in app_settings.items():
474 for k, v in app_settings.items():
475 config[k] = v
475 config[k] = v
476
476
477
477
478 def map_groups(path):
478 def map_groups(path):
479 """
479 """
480 Given a full path to a repository, create all nested groups that this
480 Given a full path to a repository, create all nested groups that this
481 repo is inside. This function creates parent-child relationships between
481 repo is inside. This function creates parent-child relationships between
482 groups and creates default perms for all new groups.
482 groups and creates default perms for all new groups.
483
483
484 :param paths: full path to repository
484 :param paths: full path to repository
485 """
485 """
486 from rhodecode.model.repo_group import RepoGroupModel
486 from rhodecode.model.repo_group import RepoGroupModel
487 sa = meta.Session()
487 sa = meta.Session()
488 groups = path.split(Repository.NAME_SEP)
488 groups = path.split(Repository.NAME_SEP)
489 parent = None
489 parent = None
490 group = None
490 group = None
491
491
492 # last element is repo in nested groups structure
492 # last element is repo in nested groups structure
493 groups = groups[:-1]
493 groups = groups[:-1]
494 rgm = RepoGroupModel(sa)
494 rgm = RepoGroupModel(sa)
495 owner = User.get_first_admin()
495 owner = User.get_first_super_admin()
496 for lvl, group_name in enumerate(groups):
496 for lvl, group_name in enumerate(groups):
497 group_name = '/'.join(groups[:lvl] + [group_name])
497 group_name = '/'.join(groups[:lvl] + [group_name])
498 group = RepoGroup.get_by_group_name(group_name)
498 group = RepoGroup.get_by_group_name(group_name)
499 desc = '%s group' % group_name
499 desc = '%s group' % group_name
500
500
501 # skip folders that are now removed repos
501 # skip folders that are now removed repos
502 if REMOVED_REPO_PAT.match(group_name):
502 if REMOVED_REPO_PAT.match(group_name):
503 break
503 break
504
504
505 if group is None:
505 if group is None:
506 log.debug('creating group level: %s group_name: %s',
506 log.debug('creating group level: %s group_name: %s',
507 lvl, group_name)
507 lvl, group_name)
508 group = RepoGroup(group_name, parent)
508 group = RepoGroup(group_name, parent)
509 group.group_description = desc
509 group.group_description = desc
510 group.user = owner
510 group.user = owner
511 sa.add(group)
511 sa.add(group)
512 perm_obj = rgm._create_default_perms(group)
512 perm_obj = rgm._create_default_perms(group)
513 sa.add(perm_obj)
513 sa.add(perm_obj)
514 sa.flush()
514 sa.flush()
515
515
516 parent = group
516 parent = group
517 return group
517 return group
518
518
519
519
520 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
520 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
521 """
521 """
522 maps all repos given in initial_repo_list, non existing repositories
522 maps all repos given in initial_repo_list, non existing repositories
523 are created, if remove_obsolete is True it also checks for db entries
523 are created, if remove_obsolete is True it also checks for db entries
524 that are not in initial_repo_list and removes them.
524 that are not in initial_repo_list and removes them.
525
525
526 :param initial_repo_list: list of repositories found by scanning methods
526 :param initial_repo_list: list of repositories found by scanning methods
527 :param remove_obsolete: check for obsolete entries in database
527 :param remove_obsolete: check for obsolete entries in database
528 """
528 """
529 from rhodecode.model.repo import RepoModel
529 from rhodecode.model.repo import RepoModel
530 from rhodecode.model.scm import ScmModel
530 from rhodecode.model.scm import ScmModel
531 from rhodecode.model.repo_group import RepoGroupModel
531 from rhodecode.model.repo_group import RepoGroupModel
532 from rhodecode.model.settings import SettingsModel
532 from rhodecode.model.settings import SettingsModel
533
533
534 sa = meta.Session()
534 sa = meta.Session()
535 repo_model = RepoModel()
535 repo_model = RepoModel()
536 user = User.get_first_admin()
536 user = User.get_first_super_admin()
537 added = []
537 added = []
538
538
539 # creation defaults
539 # creation defaults
540 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
540 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
541 enable_statistics = defs.get('repo_enable_statistics')
541 enable_statistics = defs.get('repo_enable_statistics')
542 enable_locking = defs.get('repo_enable_locking')
542 enable_locking = defs.get('repo_enable_locking')
543 enable_downloads = defs.get('repo_enable_downloads')
543 enable_downloads = defs.get('repo_enable_downloads')
544 private = defs.get('repo_private')
544 private = defs.get('repo_private')
545
545
546 for name, repo in initial_repo_list.items():
546 for name, repo in initial_repo_list.items():
547 group = map_groups(name)
547 group = map_groups(name)
548 unicode_name = safe_unicode(name)
548 unicode_name = safe_unicode(name)
549 db_repo = repo_model.get_by_repo_name(unicode_name)
549 db_repo = repo_model.get_by_repo_name(unicode_name)
550 # found repo that is on filesystem not in RhodeCode database
550 # found repo that is on filesystem not in RhodeCode database
551 if not db_repo:
551 if not db_repo:
552 log.info('repository %s not found, creating now', name)
552 log.info('repository %s not found, creating now', name)
553 added.append(name)
553 added.append(name)
554 desc = (repo.description
554 desc = (repo.description
555 if repo.description != 'unknown'
555 if repo.description != 'unknown'
556 else '%s repository' % name)
556 else '%s repository' % name)
557
557
558 db_repo = repo_model._create_repo(
558 db_repo = repo_model._create_repo(
559 repo_name=name,
559 repo_name=name,
560 repo_type=repo.alias,
560 repo_type=repo.alias,
561 description=desc,
561 description=desc,
562 repo_group=getattr(group, 'group_id', None),
562 repo_group=getattr(group, 'group_id', None),
563 owner=user,
563 owner=user,
564 enable_locking=enable_locking,
564 enable_locking=enable_locking,
565 enable_downloads=enable_downloads,
565 enable_downloads=enable_downloads,
566 enable_statistics=enable_statistics,
566 enable_statistics=enable_statistics,
567 private=private,
567 private=private,
568 state=Repository.STATE_CREATED
568 state=Repository.STATE_CREATED
569 )
569 )
570 sa.commit()
570 sa.commit()
571 # we added that repo just now, and make sure we updated server info
571 # we added that repo just now, and make sure we updated server info
572 if db_repo.repo_type == 'git':
572 if db_repo.repo_type == 'git':
573 git_repo = db_repo.scm_instance()
573 git_repo = db_repo.scm_instance()
574 # update repository server-info
574 # update repository server-info
575 log.debug('Running update server info')
575 log.debug('Running update server info')
576 git_repo._update_server_info()
576 git_repo._update_server_info()
577
577
578 db_repo.update_commit_cache()
578 db_repo.update_commit_cache()
579
579
580 config = db_repo._config
580 config = db_repo._config
581 config.set('extensions', 'largefiles', '')
581 config.set('extensions', 'largefiles', '')
582 ScmModel().install_hooks(
582 ScmModel().install_hooks(
583 db_repo.scm_instance(config=config),
583 db_repo.scm_instance(config=config),
584 repo_type=db_repo.repo_type)
584 repo_type=db_repo.repo_type)
585
585
586 removed = []
586 removed = []
587 if remove_obsolete:
587 if remove_obsolete:
588 # remove from database those repositories that are not in the filesystem
588 # remove from database those repositories that are not in the filesystem
589 for repo in sa.query(Repository).all():
589 for repo in sa.query(Repository).all():
590 if repo.repo_name not in initial_repo_list.keys():
590 if repo.repo_name not in initial_repo_list.keys():
591 log.debug("Removing non-existing repository found in db `%s`",
591 log.debug("Removing non-existing repository found in db `%s`",
592 repo.repo_name)
592 repo.repo_name)
593 try:
593 try:
594 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
594 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
595 sa.commit()
595 sa.commit()
596 removed.append(repo.repo_name)
596 removed.append(repo.repo_name)
597 except Exception:
597 except Exception:
598 # don't hold further removals on error
598 # don't hold further removals on error
599 log.error(traceback.format_exc())
599 log.error(traceback.format_exc())
600 sa.rollback()
600 sa.rollback()
601
601
602 def splitter(full_repo_name):
602 def splitter(full_repo_name):
603 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
603 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
604 gr_name = None
604 gr_name = None
605 if len(_parts) == 2:
605 if len(_parts) == 2:
606 gr_name = _parts[0]
606 gr_name = _parts[0]
607 return gr_name
607 return gr_name
608
608
609 initial_repo_group_list = [splitter(x) for x in
609 initial_repo_group_list = [splitter(x) for x in
610 initial_repo_list.keys() if splitter(x)]
610 initial_repo_list.keys() if splitter(x)]
611
611
612 # remove from database those repository groups that are not in the
612 # remove from database those repository groups that are not in the
613 # filesystem due to parent child relationships we need to delete them
613 # filesystem due to parent child relationships we need to delete them
614 # in a specific order of most nested first
614 # in a specific order of most nested first
615 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
615 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
616 nested_sort = lambda gr: len(gr.split('/'))
616 nested_sort = lambda gr: len(gr.split('/'))
617 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
617 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
618 if group_name not in initial_repo_group_list:
618 if group_name not in initial_repo_group_list:
619 repo_group = RepoGroup.get_by_group_name(group_name)
619 repo_group = RepoGroup.get_by_group_name(group_name)
620 if (repo_group.children.all() or
620 if (repo_group.children.all() or
621 not RepoGroupModel().check_exist_filesystem(
621 not RepoGroupModel().check_exist_filesystem(
622 group_name=group_name, exc_on_failure=False)):
622 group_name=group_name, exc_on_failure=False)):
623 continue
623 continue
624
624
625 log.info(
625 log.info(
626 'Removing non-existing repository group found in db `%s`',
626 'Removing non-existing repository group found in db `%s`',
627 group_name)
627 group_name)
628 try:
628 try:
629 RepoGroupModel(sa).delete(group_name, fs_remove=False)
629 RepoGroupModel(sa).delete(group_name, fs_remove=False)
630 sa.commit()
630 sa.commit()
631 removed.append(group_name)
631 removed.append(group_name)
632 except Exception:
632 except Exception:
633 # don't hold further removals on error
633 # don't hold further removals on error
634 log.exception(
634 log.exception(
635 'Unable to remove repository group `%s`',
635 'Unable to remove repository group `%s`',
636 group_name)
636 group_name)
637 sa.rollback()
637 sa.rollback()
638 raise
638 raise
639
639
640 return added, removed
640 return added, removed
641
641
642
642
643 def get_default_cache_settings(settings):
643 def get_default_cache_settings(settings):
644 cache_settings = {}
644 cache_settings = {}
645 for key in settings.keys():
645 for key in settings.keys():
646 for prefix in ['beaker.cache.', 'cache.']:
646 for prefix in ['beaker.cache.', 'cache.']:
647 if key.startswith(prefix):
647 if key.startswith(prefix):
648 name = key.split(prefix)[1].strip()
648 name = key.split(prefix)[1].strip()
649 cache_settings[name] = settings[key].strip()
649 cache_settings[name] = settings[key].strip()
650 return cache_settings
650 return cache_settings
651
651
652
652
653 # set cache regions for beaker so celery can utilise it
653 # set cache regions for beaker so celery can utilise it
654 def add_cache(settings):
654 def add_cache(settings):
655 from rhodecode.lib import caches
655 from rhodecode.lib import caches
656 cache_settings = {'regions': None}
656 cache_settings = {'regions': None}
657 # main cache settings used as default ...
657 # main cache settings used as default ...
658 cache_settings.update(get_default_cache_settings(settings))
658 cache_settings.update(get_default_cache_settings(settings))
659
659
660 if cache_settings['regions']:
660 if cache_settings['regions']:
661 for region in cache_settings['regions'].split(','):
661 for region in cache_settings['regions'].split(','):
662 region = region.strip()
662 region = region.strip()
663 region_settings = {}
663 region_settings = {}
664 for key, value in cache_settings.items():
664 for key, value in cache_settings.items():
665 if key.startswith(region):
665 if key.startswith(region):
666 region_settings[key.split('.')[1]] = value
666 region_settings[key.split('.')[1]] = value
667
667
668 caches.configure_cache_region(
668 caches.configure_cache_region(
669 region, region_settings, cache_settings)
669 region, region_settings, cache_settings)
670
670
671
671
672 def load_rcextensions(root_path):
672 def load_rcextensions(root_path):
673 import rhodecode
673 import rhodecode
674 from rhodecode.config import conf
674 from rhodecode.config import conf
675
675
676 path = os.path.join(root_path, 'rcextensions', '__init__.py')
676 path = os.path.join(root_path, 'rcextensions', '__init__.py')
677 if os.path.isfile(path):
677 if os.path.isfile(path):
678 rcext = create_module('rc', path)
678 rcext = create_module('rc', path)
679 EXT = rhodecode.EXTENSIONS = rcext
679 EXT = rhodecode.EXTENSIONS = rcext
680 log.debug('Found rcextensions now loading %s...', rcext)
680 log.debug('Found rcextensions now loading %s...', rcext)
681
681
682 # Additional mappings that are not present in the pygments lexers
682 # Additional mappings that are not present in the pygments lexers
683 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
683 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
684
684
685 # auto check if the module is not missing any data, set to default if is
685 # auto check if the module is not missing any data, set to default if is
686 # this will help autoupdate new feature of rcext module
686 # this will help autoupdate new feature of rcext module
687 #from rhodecode.config import rcextensions
687 #from rhodecode.config import rcextensions
688 #for k in dir(rcextensions):
688 #for k in dir(rcextensions):
689 # if not k.startswith('_') and not hasattr(EXT, k):
689 # if not k.startswith('_') and not hasattr(EXT, k):
690 # setattr(EXT, k, getattr(rcextensions, k))
690 # setattr(EXT, k, getattr(rcextensions, k))
691
691
692
692
693 def get_custom_lexer(extension):
693 def get_custom_lexer(extension):
694 """
694 """
695 returns a custom lexer if it is defined in rcextensions module, or None
695 returns a custom lexer if it is defined in rcextensions module, or None
696 if there's no custom lexer defined
696 if there's no custom lexer defined
697 """
697 """
698 import rhodecode
698 import rhodecode
699 from pygments import lexers
699 from pygments import lexers
700 # check if we didn't define this extension as other lexer
700 # check if we didn't define this extension as other lexer
701 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
701 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
702 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
702 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
703 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
703 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
704 return lexers.get_lexer_by_name(_lexer_name)
704 return lexers.get_lexer_by_name(_lexer_name)
705
705
706
706
707 #==============================================================================
707 #==============================================================================
708 # TEST FUNCTIONS AND CREATORS
708 # TEST FUNCTIONS AND CREATORS
709 #==============================================================================
709 #==============================================================================
710 def create_test_index(repo_location, config):
710 def create_test_index(repo_location, config):
711 """
711 """
712 Makes default test index.
712 Makes default test index.
713 """
713 """
714 import rc_testdata
714 import rc_testdata
715
715
716 rc_testdata.extract_search_index(
716 rc_testdata.extract_search_index(
717 'vcs_search_index', os.path.dirname(config['search.location']))
717 'vcs_search_index', os.path.dirname(config['search.location']))
718
718
719
719
720 def create_test_directory(test_path):
720 def create_test_directory(test_path):
721 """
721 """
722 Create test directory if it doesn't exist.
722 Create test directory if it doesn't exist.
723 """
723 """
724 if not os.path.isdir(test_path):
724 if not os.path.isdir(test_path):
725 log.debug('Creating testdir %s', test_path)
725 log.debug('Creating testdir %s', test_path)
726 os.makedirs(test_path)
726 os.makedirs(test_path)
727
727
728
728
729 def create_test_database(test_path, config):
729 def create_test_database(test_path, config):
730 """
730 """
731 Makes a fresh database.
731 Makes a fresh database.
732 """
732 """
733 from rhodecode.lib.db_manage import DbManage
733 from rhodecode.lib.db_manage import DbManage
734
734
735 # PART ONE create db
735 # PART ONE create db
736 dbconf = config['sqlalchemy.db1.url']
736 dbconf = config['sqlalchemy.db1.url']
737 log.debug('making test db %s', dbconf)
737 log.debug('making test db %s', dbconf)
738
738
739 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
739 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
740 tests=True, cli_args={'force_ask': True})
740 tests=True, cli_args={'force_ask': True})
741 dbmanage.create_tables(override=True)
741 dbmanage.create_tables(override=True)
742 dbmanage.set_db_version()
742 dbmanage.set_db_version()
743 # for tests dynamically set new root paths based on generated content
743 # for tests dynamically set new root paths based on generated content
744 dbmanage.create_settings(dbmanage.config_prompt(test_path))
744 dbmanage.create_settings(dbmanage.config_prompt(test_path))
745 dbmanage.create_default_user()
745 dbmanage.create_default_user()
746 dbmanage.create_test_admin_and_users()
746 dbmanage.create_test_admin_and_users()
747 dbmanage.create_permissions()
747 dbmanage.create_permissions()
748 dbmanage.populate_default_permissions()
748 dbmanage.populate_default_permissions()
749 Session().commit()
749 Session().commit()
750
750
751
751
752 def create_test_repositories(test_path, config):
752 def create_test_repositories(test_path, config):
753 """
753 """
754 Creates test repositories in the temporary directory. Repositories are
754 Creates test repositories in the temporary directory. Repositories are
755 extracted from archives within the rc_testdata package.
755 extracted from archives within the rc_testdata package.
756 """
756 """
757 import rc_testdata
757 import rc_testdata
758 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
758 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
759
759
760 log.debug('making test vcs repositories')
760 log.debug('making test vcs repositories')
761
761
762 idx_path = config['search.location']
762 idx_path = config['search.location']
763 data_path = config['cache_dir']
763 data_path = config['cache_dir']
764
764
765 # clean index and data
765 # clean index and data
766 if idx_path and os.path.exists(idx_path):
766 if idx_path and os.path.exists(idx_path):
767 log.debug('remove %s', idx_path)
767 log.debug('remove %s', idx_path)
768 shutil.rmtree(idx_path)
768 shutil.rmtree(idx_path)
769
769
770 if data_path and os.path.exists(data_path):
770 if data_path and os.path.exists(data_path):
771 log.debug('remove %s', data_path)
771 log.debug('remove %s', data_path)
772 shutil.rmtree(data_path)
772 shutil.rmtree(data_path)
773
773
774 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
774 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
775 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
775 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
776
776
777 # Note: Subversion is in the process of being integrated with the system,
777 # Note: Subversion is in the process of being integrated with the system,
778 # until we have a properly packed version of the test svn repository, this
778 # until we have a properly packed version of the test svn repository, this
779 # tries to copy over the repo from a package "rc_testdata"
779 # tries to copy over the repo from a package "rc_testdata"
780 svn_repo_path = rc_testdata.get_svn_repo_archive()
780 svn_repo_path = rc_testdata.get_svn_repo_archive()
781 with tarfile.open(svn_repo_path) as tar:
781 with tarfile.open(svn_repo_path) as tar:
782 tar.extractall(jn(test_path, SVN_REPO))
782 tar.extractall(jn(test_path, SVN_REPO))
783
783
784
784
785 #==============================================================================
785 #==============================================================================
786 # PASTER COMMANDS
786 # PASTER COMMANDS
787 #==============================================================================
787 #==============================================================================
788 class BasePasterCommand(Command):
788 class BasePasterCommand(Command):
789 """
789 """
790 Abstract Base Class for paster commands.
790 Abstract Base Class for paster commands.
791
791
792 The celery commands are somewhat aggressive about loading
792 The celery commands are somewhat aggressive about loading
793 celery.conf, and since our module sets the `CELERY_LOADER`
793 celery.conf, and since our module sets the `CELERY_LOADER`
794 environment variable to our loader, we have to bootstrap a bit and
794 environment variable to our loader, we have to bootstrap a bit and
795 make sure we've had a chance to load the pylons config off of the
795 make sure we've had a chance to load the pylons config off of the
796 command line, otherwise everything fails.
796 command line, otherwise everything fails.
797 """
797 """
798 min_args = 1
798 min_args = 1
799 min_args_error = "Please provide a paster config file as an argument."
799 min_args_error = "Please provide a paster config file as an argument."
800 takes_config_file = 1
800 takes_config_file = 1
801 requires_config_file = True
801 requires_config_file = True
802
802
803 def notify_msg(self, msg, log=False):
803 def notify_msg(self, msg, log=False):
804 """Make a notification to user, additionally if logger is passed
804 """Make a notification to user, additionally if logger is passed
805 it logs this action using given logger
805 it logs this action using given logger
806
806
807 :param msg: message that will be printed to user
807 :param msg: message that will be printed to user
808 :param log: logging instance, to use to additionally log this message
808 :param log: logging instance, to use to additionally log this message
809
809
810 """
810 """
811 if log and isinstance(log, logging):
811 if log and isinstance(log, logging):
812 log(msg)
812 log(msg)
813
813
814 def run(self, args):
814 def run(self, args):
815 """
815 """
816 Overrides Command.run
816 Overrides Command.run
817
817
818 Checks for a config file argument and loads it.
818 Checks for a config file argument and loads it.
819 """
819 """
820 if len(args) < self.min_args:
820 if len(args) < self.min_args:
821 raise BadCommand(
821 raise BadCommand(
822 self.min_args_error % {'min_args': self.min_args,
822 self.min_args_error % {'min_args': self.min_args,
823 'actual_args': len(args)})
823 'actual_args': len(args)})
824
824
825 # Decrement because we're going to lob off the first argument.
825 # Decrement because we're going to lob off the first argument.
826 # @@ This is hacky
826 # @@ This is hacky
827 self.min_args -= 1
827 self.min_args -= 1
828 self.bootstrap_config(args[0])
828 self.bootstrap_config(args[0])
829 self.update_parser()
829 self.update_parser()
830 return super(BasePasterCommand, self).run(args[1:])
830 return super(BasePasterCommand, self).run(args[1:])
831
831
832 def update_parser(self):
832 def update_parser(self):
833 """
833 """
834 Abstract method. Allows for the class' parser to be updated
834 Abstract method. Allows for the class' parser to be updated
835 before the superclass' `run` method is called. Necessary to
835 before the superclass' `run` method is called. Necessary to
836 allow options/arguments to be passed through to the underlying
836 allow options/arguments to be passed through to the underlying
837 celery command.
837 celery command.
838 """
838 """
839 raise NotImplementedError("Abstract Method.")
839 raise NotImplementedError("Abstract Method.")
840
840
841 def bootstrap_config(self, conf):
841 def bootstrap_config(self, conf):
842 """
842 """
843 Loads the pylons configuration.
843 Loads the pylons configuration.
844 """
844 """
845 from pylons import config as pylonsconfig
845 from pylons import config as pylonsconfig
846
846
847 self.path_to_ini_file = os.path.realpath(conf)
847 self.path_to_ini_file = os.path.realpath(conf)
848 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
848 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
849 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
849 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
850
850
851 def _init_session(self):
851 def _init_session(self):
852 """
852 """
853 Inits SqlAlchemy Session
853 Inits SqlAlchemy Session
854 """
854 """
855 logging.config.fileConfig(self.path_to_ini_file)
855 logging.config.fileConfig(self.path_to_ini_file)
856 from pylons import config
856 from pylons import config
857 from rhodecode.config.utils import initialize_database
857 from rhodecode.config.utils import initialize_database
858
858
859 # get to remove repos !!
859 # get to remove repos !!
860 add_cache(config)
860 add_cache(config)
861 initialize_database(config)
861 initialize_database(config)
862
862
863
863
864 @decorator.decorator
864 @decorator.decorator
865 def jsonify(func, *args, **kwargs):
865 def jsonify(func, *args, **kwargs):
866 """Action decorator that formats output for JSON
866 """Action decorator that formats output for JSON
867
867
868 Given a function that will return content, this decorator will turn
868 Given a function that will return content, this decorator will turn
869 the result into JSON, with a content-type of 'application/json' and
869 the result into JSON, with a content-type of 'application/json' and
870 output it.
870 output it.
871
871
872 """
872 """
873 from pylons.decorators.util import get_pylons
873 from pylons.decorators.util import get_pylons
874 from rhodecode.lib.ext_json import json
874 from rhodecode.lib.ext_json import json
875 pylons = get_pylons(args)
875 pylons = get_pylons(args)
876 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
876 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
877 data = func(*args, **kwargs)
877 data = func(*args, **kwargs)
878 if isinstance(data, (list, tuple)):
878 if isinstance(data, (list, tuple)):
879 msg = "JSON responses with Array envelopes are susceptible to " \
879 msg = "JSON responses with Array envelopes are susceptible to " \
880 "cross-site data leak attacks, see " \
880 "cross-site data leak attacks, see " \
881 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
881 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
882 warnings.warn(msg, Warning, 2)
882 warnings.warn(msg, Warning, 2)
883 log.warning(msg)
883 log.warning(msg)
884 log.debug("Returning JSON wrapped action output")
884 log.debug("Returning JSON wrapped action output")
885 return json.dumps(data, encoding='utf-8')
885 return json.dumps(data, encoding='utf-8')
886
886
887
887
888 class PartialRenderer(object):
888 class PartialRenderer(object):
889 """
889 """
890 Partial renderer used to render chunks of html used in datagrids
890 Partial renderer used to render chunks of html used in datagrids
891 use like::
891 use like::
892
892
893 _render = PartialRenderer('data_table/_dt_elements.html')
893 _render = PartialRenderer('data_table/_dt_elements.html')
894 _render('quick_menu', args, kwargs)
894 _render('quick_menu', args, kwargs)
895 PartialRenderer.h,
895 PartialRenderer.h,
896 c,
896 c,
897 _,
897 _,
898 ungettext
898 ungettext
899 are the template stuff initialized inside and can be re-used later
899 are the template stuff initialized inside and can be re-used later
900
900
901 :param tmpl_name: template path relate to /templates/ dir
901 :param tmpl_name: template path relate to /templates/ dir
902 """
902 """
903
903
904 def __init__(self, tmpl_name):
904 def __init__(self, tmpl_name):
905 import rhodecode
905 import rhodecode
906 from pylons import request, tmpl_context as c
906 from pylons import request, tmpl_context as c
907 from pylons.i18n.translation import _, ungettext
907 from pylons.i18n.translation import _, ungettext
908 from rhodecode.lib import helpers as h
908 from rhodecode.lib import helpers as h
909
909
910 self.tmpl_name = tmpl_name
910 self.tmpl_name = tmpl_name
911 self.rhodecode = rhodecode
911 self.rhodecode = rhodecode
912 self.c = c
912 self.c = c
913 self._ = _
913 self._ = _
914 self.ungettext = ungettext
914 self.ungettext = ungettext
915 self.h = h
915 self.h = h
916 self.request = request
916 self.request = request
917
917
918 def _mako_lookup(self):
918 def _mako_lookup(self):
919 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
919 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
920 return _tmpl_lookup.get_template(self.tmpl_name)
920 return _tmpl_lookup.get_template(self.tmpl_name)
921
921
922 def _update_kwargs_for_render(self, kwargs):
922 def _update_kwargs_for_render(self, kwargs):
923 """
923 """
924 Inject params required for Mako rendering
924 Inject params required for Mako rendering
925 """
925 """
926 _kwargs = {
926 _kwargs = {
927 '_': self._,
927 '_': self._,
928 'h': self.h,
928 'h': self.h,
929 'c': self.c,
929 'c': self.c,
930 'request': self.request,
930 'request': self.request,
931 'ungettext': self.ungettext,
931 'ungettext': self.ungettext,
932 }
932 }
933 _kwargs.update(kwargs)
933 _kwargs.update(kwargs)
934 return _kwargs
934 return _kwargs
935
935
936 def _render_with_exc(self, render_func, args, kwargs):
936 def _render_with_exc(self, render_func, args, kwargs):
937 try:
937 try:
938 return render_func.render(*args, **kwargs)
938 return render_func.render(*args, **kwargs)
939 except:
939 except:
940 log.error(exceptions.text_error_template().render())
940 log.error(exceptions.text_error_template().render())
941 raise
941 raise
942
942
943 def _get_template(self, template_obj, def_name):
943 def _get_template(self, template_obj, def_name):
944 if def_name:
944 if def_name:
945 tmpl = template_obj.get_def(def_name)
945 tmpl = template_obj.get_def(def_name)
946 else:
946 else:
947 tmpl = template_obj
947 tmpl = template_obj
948 return tmpl
948 return tmpl
949
949
950 def render(self, def_name, *args, **kwargs):
950 def render(self, def_name, *args, **kwargs):
951 lookup_obj = self._mako_lookup()
951 lookup_obj = self._mako_lookup()
952 tmpl = self._get_template(lookup_obj, def_name=def_name)
952 tmpl = self._get_template(lookup_obj, def_name=def_name)
953 kwargs = self._update_kwargs_for_render(kwargs)
953 kwargs = self._update_kwargs_for_render(kwargs)
954 return self._render_with_exc(tmpl, args, kwargs)
954 return self._render_with_exc(tmpl, args, kwargs)
955
955
956 def __call__(self, tmpl, *args, **kwargs):
956 def __call__(self, tmpl, *args, **kwargs):
957 return self.render(tmpl, *args, **kwargs)
957 return self.render(tmpl, *args, **kwargs)
958
958
959
959
960 def password_changed(auth_user, session):
960 def password_changed(auth_user, session):
961 if auth_user.username == User.DEFAULT_USER:
961 if auth_user.username == User.DEFAULT_USER:
962 return False
962 return False
963 password_hash = md5(auth_user.password) if auth_user.password else None
963 password_hash = md5(auth_user.password) if auth_user.password else None
964 rhodecode_user = session.get('rhodecode_user', {})
964 rhodecode_user = session.get('rhodecode_user', {})
965 session_password_hash = rhodecode_user.get('password', '')
965 session_password_hash = rhodecode_user.get('password', '')
966 return password_hash != session_password_hash
966 return password_hash != session_password_hash
967
967
968
968
969 def read_opensource_licenses():
969 def read_opensource_licenses():
970 global _license_cache
970 global _license_cache
971
971
972 if not _license_cache:
972 if not _license_cache:
973 licenses = pkg_resources.resource_string(
973 licenses = pkg_resources.resource_string(
974 'rhodecode', 'config/licenses.json')
974 'rhodecode', 'config/licenses.json')
975 _license_cache = json.loads(licenses)
975 _license_cache = json.loads(licenses)
976
976
977 return _license_cache
977 return _license_cache
@@ -1,3462 +1,3462 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 os
25 import os
26 import sys
26 import sys
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.exc import IntegrityError
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
42 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
43 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
45 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
46 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48
48
49 from pylons import url
49 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
50 from pylons.i18n.translation import lazy_ugettext as _
51
51
52 from rhodecode.lib.vcs import get_backend
52 from rhodecode.lib.vcs import get_backend
53 from rhodecode.lib.vcs.utils.helpers import get_scm
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
55 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
56 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file beaker.session.secret
73 # this is propagated from .ini file beaker.session.secret
74 # and initialized at environment.py
74 # and initialized at environment.py
75 ENCRYPTION_KEY = None
75 ENCRYPTION_KEY = None
76
76
77 # used to sort permissions by types, '#' used here is not allowed to be in
77 # used to sort permissions by types, '#' used here is not allowed to be in
78 # usernames, and it's very early in sorted string.printable table.
78 # usernames, and it's very early in sorted string.printable table.
79 PERMISSION_TYPE_SORT = {
79 PERMISSION_TYPE_SORT = {
80 'admin': '####',
80 'admin': '####',
81 'write': '###',
81 'write': '###',
82 'read': '##',
82 'read': '##',
83 'none': '#',
83 'none': '#',
84 }
84 }
85
85
86
86
87 def display_sort(obj):
87 def display_sort(obj):
88 """
88 """
89 Sort function used to sort permissions in .permissions() function of
89 Sort function used to sort permissions in .permissions() function of
90 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 of all other resources
91 of all other resources
92 """
92 """
93
93
94 if obj.username == User.DEFAULT_USER:
94 if obj.username == User.DEFAULT_USER:
95 return '#####'
95 return '#####'
96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 return prefix + obj.username
97 return prefix + obj.username
98
98
99
99
100 def _hash_key(k):
100 def _hash_key(k):
101 return md5_safe(k)
101 return md5_safe(k)
102
102
103
103
104 class EncryptedTextValue(TypeDecorator):
104 class EncryptedTextValue(TypeDecorator):
105 """
105 """
106 Special column for encrypted long text data, use like::
106 Special column for encrypted long text data, use like::
107
107
108 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109
109
110 This column is intelligent so if value is in unencrypted form it return
110 This column is intelligent so if value is in unencrypted form it return
111 unencrypted form, but on save it always encrypts
111 unencrypted form, but on save it always encrypts
112 """
112 """
113 impl = Text
113 impl = Text
114
114
115 def process_bind_param(self, value, dialect):
115 def process_bind_param(self, value, dialect):
116 if not value:
116 if not value:
117 return value
117 return value
118 if value.startswith('enc$aes$'):
118 if value.startswith('enc$aes$'):
119 # protect against double encrypting if someone manually starts
119 # protect against double encrypting if someone manually starts
120 # doing
120 # doing
121 raise ValueError('value needs to be in unencrypted format, ie. '
121 raise ValueError('value needs to be in unencrypted format, ie. '
122 'not starting with enc$aes$')
122 'not starting with enc$aes$')
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 if not value:
126 if not value:
127 return value
127 return value
128
128
129 parts = value.split('$', 3)
129 parts = value.split('$', 3)
130 if not len(parts) == 3:
130 if not len(parts) == 3:
131 # probably not encrypted values
131 # probably not encrypted values
132 return value
132 return value
133 else:
133 else:
134 if parts[0] != 'enc':
134 if parts[0] != 'enc':
135 # parts ok but without our header ?
135 # parts ok but without our header ?
136 return value
136 return value
137
137
138 # at that stage we know it's our encryption
138 # at that stage we know it's our encryption
139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
140 return decrypted_data
140 return decrypted_data
141
141
142
142
143 class BaseModel(object):
143 class BaseModel(object):
144 """
144 """
145 Base Model for all classes
145 Base Model for all classes
146 """
146 """
147
147
148 @classmethod
148 @classmethod
149 def _get_keys(cls):
149 def _get_keys(cls):
150 """return column names for this model """
150 """return column names for this model """
151 return class_mapper(cls).c.keys()
151 return class_mapper(cls).c.keys()
152
152
153 def get_dict(self):
153 def get_dict(self):
154 """
154 """
155 return dict with keys and values corresponding
155 return dict with keys and values corresponding
156 to this model data """
156 to this model data """
157
157
158 d = {}
158 d = {}
159 for k in self._get_keys():
159 for k in self._get_keys():
160 d[k] = getattr(self, k)
160 d[k] = getattr(self, k)
161
161
162 # also use __json__() if present to get additional fields
162 # also use __json__() if present to get additional fields
163 _json_attr = getattr(self, '__json__', None)
163 _json_attr = getattr(self, '__json__', None)
164 if _json_attr:
164 if _json_attr:
165 # update with attributes from __json__
165 # update with attributes from __json__
166 if callable(_json_attr):
166 if callable(_json_attr):
167 _json_attr = _json_attr()
167 _json_attr = _json_attr()
168 for k, val in _json_attr.iteritems():
168 for k, val in _json_attr.iteritems():
169 d[k] = val
169 d[k] = val
170 return d
170 return d
171
171
172 def get_appstruct(self):
172 def get_appstruct(self):
173 """return list with keys and values tuples corresponding
173 """return list with keys and values tuples corresponding
174 to this model data """
174 to this model data """
175
175
176 l = []
176 l = []
177 for k in self._get_keys():
177 for k in self._get_keys():
178 l.append((k, getattr(self, k),))
178 l.append((k, getattr(self, k),))
179 return l
179 return l
180
180
181 def populate_obj(self, populate_dict):
181 def populate_obj(self, populate_dict):
182 """populate model with data from given populate_dict"""
182 """populate model with data from given populate_dict"""
183
183
184 for k in self._get_keys():
184 for k in self._get_keys():
185 if k in populate_dict:
185 if k in populate_dict:
186 setattr(self, k, populate_dict[k])
186 setattr(self, k, populate_dict[k])
187
187
188 @classmethod
188 @classmethod
189 def query(cls):
189 def query(cls):
190 return Session().query(cls)
190 return Session().query(cls)
191
191
192 @classmethod
192 @classmethod
193 def get(cls, id_):
193 def get(cls, id_):
194 if id_:
194 if id_:
195 return cls.query().get(id_)
195 return cls.query().get(id_)
196
196
197 @classmethod
197 @classmethod
198 def get_or_404(cls, id_):
198 def get_or_404(cls, id_):
199 try:
199 try:
200 id_ = int(id_)
200 id_ = int(id_)
201 except (TypeError, ValueError):
201 except (TypeError, ValueError):
202 raise HTTPNotFound
202 raise HTTPNotFound
203
203
204 res = cls.query().get(id_)
204 res = cls.query().get(id_)
205 if not res:
205 if not res:
206 raise HTTPNotFound
206 raise HTTPNotFound
207 return res
207 return res
208
208
209 @classmethod
209 @classmethod
210 def getAll(cls):
210 def getAll(cls):
211 # deprecated and left for backward compatibility
211 # deprecated and left for backward compatibility
212 return cls.get_all()
212 return cls.get_all()
213
213
214 @classmethod
214 @classmethod
215 def get_all(cls):
215 def get_all(cls):
216 return cls.query().all()
216 return cls.query().all()
217
217
218 @classmethod
218 @classmethod
219 def delete(cls, id_):
219 def delete(cls, id_):
220 obj = cls.query().get(id_)
220 obj = cls.query().get(id_)
221 Session().delete(obj)
221 Session().delete(obj)
222
222
223 @classmethod
223 @classmethod
224 def identity_cache(cls, session, attr_name, value):
224 def identity_cache(cls, session, attr_name, value):
225 exist_in_session = []
225 exist_in_session = []
226 for (item_cls, pkey), instance in session.identity_map.items():
226 for (item_cls, pkey), instance in session.identity_map.items():
227 if cls == item_cls and getattr(instance, attr_name) == value:
227 if cls == item_cls and getattr(instance, attr_name) == value:
228 exist_in_session.append(instance)
228 exist_in_session.append(instance)
229 if exist_in_session:
229 if exist_in_session:
230 if len(exist_in_session) == 1:
230 if len(exist_in_session) == 1:
231 return exist_in_session[0]
231 return exist_in_session[0]
232 log.exception(
232 log.exception(
233 'multiple objects with attr %s and '
233 'multiple objects with attr %s and '
234 'value %s found with same name: %r',
234 'value %s found with same name: %r',
235 attr_name, value, exist_in_session)
235 attr_name, value, exist_in_session)
236
236
237 def __repr__(self):
237 def __repr__(self):
238 if hasattr(self, '__unicode__'):
238 if hasattr(self, '__unicode__'):
239 # python repr needs to return str
239 # python repr needs to return str
240 try:
240 try:
241 return safe_str(self.__unicode__())
241 return safe_str(self.__unicode__())
242 except UnicodeDecodeError:
242 except UnicodeDecodeError:
243 pass
243 pass
244 return '<DB:%s>' % (self.__class__.__name__)
244 return '<DB:%s>' % (self.__class__.__name__)
245
245
246
246
247 class RhodeCodeSetting(Base, BaseModel):
247 class RhodeCodeSetting(Base, BaseModel):
248 __tablename__ = 'rhodecode_settings'
248 __tablename__ = 'rhodecode_settings'
249 __table_args__ = (
249 __table_args__ = (
250 UniqueConstraint('app_settings_name'),
250 UniqueConstraint('app_settings_name'),
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
252 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
253 )
253 )
254
254
255 SETTINGS_TYPES = {
255 SETTINGS_TYPES = {
256 'str': safe_str,
256 'str': safe_str,
257 'int': safe_int,
257 'int': safe_int,
258 'unicode': safe_unicode,
258 'unicode': safe_unicode,
259 'bool': str2bool,
259 'bool': str2bool,
260 'list': functools.partial(aslist, sep=',')
260 'list': functools.partial(aslist, sep=',')
261 }
261 }
262 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
262 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
263 GLOBAL_CONF_KEY = 'app_settings'
263 GLOBAL_CONF_KEY = 'app_settings'
264
264
265 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
265 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
266 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
267 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
267 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
268 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
268 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
269
269
270 def __init__(self, key='', val='', type='unicode'):
270 def __init__(self, key='', val='', type='unicode'):
271 self.app_settings_name = key
271 self.app_settings_name = key
272 self.app_settings_type = type
272 self.app_settings_type = type
273 self.app_settings_value = val
273 self.app_settings_value = val
274
274
275 @validates('_app_settings_value')
275 @validates('_app_settings_value')
276 def validate_settings_value(self, key, val):
276 def validate_settings_value(self, key, val):
277 assert type(val) == unicode
277 assert type(val) == unicode
278 return val
278 return val
279
279
280 @hybrid_property
280 @hybrid_property
281 def app_settings_value(self):
281 def app_settings_value(self):
282 v = self._app_settings_value
282 v = self._app_settings_value
283 _type = self.app_settings_type
283 _type = self.app_settings_type
284 if _type:
284 if _type:
285 _type = self.app_settings_type.split('.')[0]
285 _type = self.app_settings_type.split('.')[0]
286 # decode the encrypted value
286 # decode the encrypted value
287 if 'encrypted' in self.app_settings_type:
287 if 'encrypted' in self.app_settings_type:
288 cipher = EncryptedTextValue()
288 cipher = EncryptedTextValue()
289 v = safe_unicode(cipher.process_result_value(v, None))
289 v = safe_unicode(cipher.process_result_value(v, None))
290
290
291 converter = self.SETTINGS_TYPES.get(_type) or \
291 converter = self.SETTINGS_TYPES.get(_type) or \
292 self.SETTINGS_TYPES['unicode']
292 self.SETTINGS_TYPES['unicode']
293 return converter(v)
293 return converter(v)
294
294
295 @app_settings_value.setter
295 @app_settings_value.setter
296 def app_settings_value(self, val):
296 def app_settings_value(self, val):
297 """
297 """
298 Setter that will always make sure we use unicode in app_settings_value
298 Setter that will always make sure we use unicode in app_settings_value
299
299
300 :param val:
300 :param val:
301 """
301 """
302 val = safe_unicode(val)
302 val = safe_unicode(val)
303 # encode the encrypted value
303 # encode the encrypted value
304 if 'encrypted' in self.app_settings_type:
304 if 'encrypted' in self.app_settings_type:
305 cipher = EncryptedTextValue()
305 cipher = EncryptedTextValue()
306 val = safe_unicode(cipher.process_bind_param(val, None))
306 val = safe_unicode(cipher.process_bind_param(val, None))
307 self._app_settings_value = val
307 self._app_settings_value = val
308
308
309 @hybrid_property
309 @hybrid_property
310 def app_settings_type(self):
310 def app_settings_type(self):
311 return self._app_settings_type
311 return self._app_settings_type
312
312
313 @app_settings_type.setter
313 @app_settings_type.setter
314 def app_settings_type(self, val):
314 def app_settings_type(self, val):
315 if val.split('.')[0] not in self.SETTINGS_TYPES:
315 if val.split('.')[0] not in self.SETTINGS_TYPES:
316 raise Exception('type must be one of %s got %s'
316 raise Exception('type must be one of %s got %s'
317 % (self.SETTINGS_TYPES.keys(), val))
317 % (self.SETTINGS_TYPES.keys(), val))
318 self._app_settings_type = val
318 self._app_settings_type = val
319
319
320 def __unicode__(self):
320 def __unicode__(self):
321 return u"<%s('%s:%s[%s]')>" % (
321 return u"<%s('%s:%s[%s]')>" % (
322 self.__class__.__name__,
322 self.__class__.__name__,
323 self.app_settings_name, self.app_settings_value,
323 self.app_settings_name, self.app_settings_value,
324 self.app_settings_type
324 self.app_settings_type
325 )
325 )
326
326
327
327
328 class RhodeCodeUi(Base, BaseModel):
328 class RhodeCodeUi(Base, BaseModel):
329 __tablename__ = 'rhodecode_ui'
329 __tablename__ = 'rhodecode_ui'
330 __table_args__ = (
330 __table_args__ = (
331 UniqueConstraint('ui_key'),
331 UniqueConstraint('ui_key'),
332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
334 )
334 )
335
335
336 HOOK_REPO_SIZE = 'changegroup.repo_size'
336 HOOK_REPO_SIZE = 'changegroup.repo_size'
337 # HG
337 # HG
338 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
338 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
339 HOOK_PULL = 'outgoing.pull_logger'
339 HOOK_PULL = 'outgoing.pull_logger'
340 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
340 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
341 HOOK_PUSH = 'changegroup.push_logger'
341 HOOK_PUSH = 'changegroup.push_logger'
342
342
343 # TODO: johbo: Unify way how hooks are configured for git and hg,
343 # TODO: johbo: Unify way how hooks are configured for git and hg,
344 # git part is currently hardcoded.
344 # git part is currently hardcoded.
345
345
346 # SVN PATTERNS
346 # SVN PATTERNS
347 SVN_BRANCH_ID = 'vcs_svn_branch'
347 SVN_BRANCH_ID = 'vcs_svn_branch'
348 SVN_TAG_ID = 'vcs_svn_tag'
348 SVN_TAG_ID = 'vcs_svn_tag'
349
349
350 ui_id = Column(
350 ui_id = Column(
351 "ui_id", Integer(), nullable=False, unique=True, default=None,
351 "ui_id", Integer(), nullable=False, unique=True, default=None,
352 primary_key=True)
352 primary_key=True)
353 ui_section = Column(
353 ui_section = Column(
354 "ui_section", String(255), nullable=True, unique=None, default=None)
354 "ui_section", String(255), nullable=True, unique=None, default=None)
355 ui_key = Column(
355 ui_key = Column(
356 "ui_key", String(255), nullable=True, unique=None, default=None)
356 "ui_key", String(255), nullable=True, unique=None, default=None)
357 ui_value = Column(
357 ui_value = Column(
358 "ui_value", String(255), nullable=True, unique=None, default=None)
358 "ui_value", String(255), nullable=True, unique=None, default=None)
359 ui_active = Column(
359 ui_active = Column(
360 "ui_active", Boolean(), nullable=True, unique=None, default=True)
360 "ui_active", Boolean(), nullable=True, unique=None, default=True)
361
361
362 def __repr__(self):
362 def __repr__(self):
363 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
363 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
364 self.ui_key, self.ui_value)
364 self.ui_key, self.ui_value)
365
365
366
366
367 class RepoRhodeCodeSetting(Base, BaseModel):
367 class RepoRhodeCodeSetting(Base, BaseModel):
368 __tablename__ = 'repo_rhodecode_settings'
368 __tablename__ = 'repo_rhodecode_settings'
369 __table_args__ = (
369 __table_args__ = (
370 UniqueConstraint(
370 UniqueConstraint(
371 'app_settings_name', 'repository_id',
371 'app_settings_name', 'repository_id',
372 name='uq_repo_rhodecode_setting_name_repo_id'),
372 name='uq_repo_rhodecode_setting_name_repo_id'),
373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
374 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
374 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
375 )
375 )
376
376
377 repository_id = Column(
377 repository_id = Column(
378 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
378 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
379 nullable=False)
379 nullable=False)
380 app_settings_id = Column(
380 app_settings_id = Column(
381 "app_settings_id", Integer(), nullable=False, unique=True,
381 "app_settings_id", Integer(), nullable=False, unique=True,
382 default=None, primary_key=True)
382 default=None, primary_key=True)
383 app_settings_name = Column(
383 app_settings_name = Column(
384 "app_settings_name", String(255), nullable=True, unique=None,
384 "app_settings_name", String(255), nullable=True, unique=None,
385 default=None)
385 default=None)
386 _app_settings_value = Column(
386 _app_settings_value = Column(
387 "app_settings_value", String(4096), nullable=True, unique=None,
387 "app_settings_value", String(4096), nullable=True, unique=None,
388 default=None)
388 default=None)
389 _app_settings_type = Column(
389 _app_settings_type = Column(
390 "app_settings_type", String(255), nullable=True, unique=None,
390 "app_settings_type", String(255), nullable=True, unique=None,
391 default=None)
391 default=None)
392
392
393 repository = relationship('Repository')
393 repository = relationship('Repository')
394
394
395 def __init__(self, repository_id, key='', val='', type='unicode'):
395 def __init__(self, repository_id, key='', val='', type='unicode'):
396 self.repository_id = repository_id
396 self.repository_id = repository_id
397 self.app_settings_name = key
397 self.app_settings_name = key
398 self.app_settings_type = type
398 self.app_settings_type = type
399 self.app_settings_value = val
399 self.app_settings_value = val
400
400
401 @validates('_app_settings_value')
401 @validates('_app_settings_value')
402 def validate_settings_value(self, key, val):
402 def validate_settings_value(self, key, val):
403 assert type(val) == unicode
403 assert type(val) == unicode
404 return val
404 return val
405
405
406 @hybrid_property
406 @hybrid_property
407 def app_settings_value(self):
407 def app_settings_value(self):
408 v = self._app_settings_value
408 v = self._app_settings_value
409 type_ = self.app_settings_type
409 type_ = self.app_settings_type
410 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
410 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
411 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
411 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
412 return converter(v)
412 return converter(v)
413
413
414 @app_settings_value.setter
414 @app_settings_value.setter
415 def app_settings_value(self, val):
415 def app_settings_value(self, val):
416 """
416 """
417 Setter that will always make sure we use unicode in app_settings_value
417 Setter that will always make sure we use unicode in app_settings_value
418
418
419 :param val:
419 :param val:
420 """
420 """
421 self._app_settings_value = safe_unicode(val)
421 self._app_settings_value = safe_unicode(val)
422
422
423 @hybrid_property
423 @hybrid_property
424 def app_settings_type(self):
424 def app_settings_type(self):
425 return self._app_settings_type
425 return self._app_settings_type
426
426
427 @app_settings_type.setter
427 @app_settings_type.setter
428 def app_settings_type(self, val):
428 def app_settings_type(self, val):
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 if val not in SETTINGS_TYPES:
430 if val not in SETTINGS_TYPES:
431 raise Exception('type must be one of %s got %s'
431 raise Exception('type must be one of %s got %s'
432 % (SETTINGS_TYPES.keys(), val))
432 % (SETTINGS_TYPES.keys(), val))
433 self._app_settings_type = val
433 self._app_settings_type = val
434
434
435 def __unicode__(self):
435 def __unicode__(self):
436 return u"<%s('%s:%s:%s[%s]')>" % (
436 return u"<%s('%s:%s:%s[%s]')>" % (
437 self.__class__.__name__, self.repository.repo_name,
437 self.__class__.__name__, self.repository.repo_name,
438 self.app_settings_name, self.app_settings_value,
438 self.app_settings_name, self.app_settings_value,
439 self.app_settings_type
439 self.app_settings_type
440 )
440 )
441
441
442
442
443 class RepoRhodeCodeUi(Base, BaseModel):
443 class RepoRhodeCodeUi(Base, BaseModel):
444 __tablename__ = 'repo_rhodecode_ui'
444 __tablename__ = 'repo_rhodecode_ui'
445 __table_args__ = (
445 __table_args__ = (
446 UniqueConstraint(
446 UniqueConstraint(
447 'repository_id', 'ui_section', 'ui_key',
447 'repository_id', 'ui_section', 'ui_key',
448 name='uq_repo_rhodecode_ui_repository_id_section_key'),
448 name='uq_repo_rhodecode_ui_repository_id_section_key'),
449 {'extend_existing': True, 'mysql_engine': 'InnoDB',
449 {'extend_existing': True, 'mysql_engine': 'InnoDB',
450 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
450 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
451 )
451 )
452
452
453 repository_id = Column(
453 repository_id = Column(
454 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
454 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
455 nullable=False)
455 nullable=False)
456 ui_id = Column(
456 ui_id = Column(
457 "ui_id", Integer(), nullable=False, unique=True, default=None,
457 "ui_id", Integer(), nullable=False, unique=True, default=None,
458 primary_key=True)
458 primary_key=True)
459 ui_section = Column(
459 ui_section = Column(
460 "ui_section", String(255), nullable=True, unique=None, default=None)
460 "ui_section", String(255), nullable=True, unique=None, default=None)
461 ui_key = Column(
461 ui_key = Column(
462 "ui_key", String(255), nullable=True, unique=None, default=None)
462 "ui_key", String(255), nullable=True, unique=None, default=None)
463 ui_value = Column(
463 ui_value = Column(
464 "ui_value", String(255), nullable=True, unique=None, default=None)
464 "ui_value", String(255), nullable=True, unique=None, default=None)
465 ui_active = Column(
465 ui_active = Column(
466 "ui_active", Boolean(), nullable=True, unique=None, default=True)
466 "ui_active", Boolean(), nullable=True, unique=None, default=True)
467
467
468 repository = relationship('Repository')
468 repository = relationship('Repository')
469
469
470 def __repr__(self):
470 def __repr__(self):
471 return '<%s[%s:%s]%s=>%s]>' % (
471 return '<%s[%s:%s]%s=>%s]>' % (
472 self.__class__.__name__, self.repository.repo_name,
472 self.__class__.__name__, self.repository.repo_name,
473 self.ui_section, self.ui_key, self.ui_value)
473 self.ui_section, self.ui_key, self.ui_value)
474
474
475
475
476 class User(Base, BaseModel):
476 class User(Base, BaseModel):
477 __tablename__ = 'users'
477 __tablename__ = 'users'
478 __table_args__ = (
478 __table_args__ = (
479 UniqueConstraint('username'), UniqueConstraint('email'),
479 UniqueConstraint('username'), UniqueConstraint('email'),
480 Index('u_username_idx', 'username'),
480 Index('u_username_idx', 'username'),
481 Index('u_email_idx', 'email'),
481 Index('u_email_idx', 'email'),
482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
484 )
484 )
485 DEFAULT_USER = 'default'
485 DEFAULT_USER = 'default'
486 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
486 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
487 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
487 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
488
488
489 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 username = Column("username", String(255), nullable=True, unique=None, default=None)
490 username = Column("username", String(255), nullable=True, unique=None, default=None)
491 password = Column("password", String(255), nullable=True, unique=None, default=None)
491 password = Column("password", String(255), nullable=True, unique=None, default=None)
492 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
492 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
493 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
493 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
494 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
494 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
495 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
495 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
496 _email = Column("email", String(255), nullable=True, unique=None, default=None)
496 _email = Column("email", String(255), nullable=True, unique=None, default=None)
497 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
497 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
498 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
498 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
499 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
499 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
500 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
500 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
501 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
501 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
502 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
502 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
503 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
503 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
504
504
505 user_log = relationship('UserLog')
505 user_log = relationship('UserLog')
506 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
506 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
507
507
508 repositories = relationship('Repository')
508 repositories = relationship('Repository')
509 repository_groups = relationship('RepoGroup')
509 repository_groups = relationship('RepoGroup')
510 user_groups = relationship('UserGroup')
510 user_groups = relationship('UserGroup')
511
511
512 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
512 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
513 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
513 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
514
514
515 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
515 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
516 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
516 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
517 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
517 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
518
518
519 group_member = relationship('UserGroupMember', cascade='all')
519 group_member = relationship('UserGroupMember', cascade='all')
520
520
521 notifications = relationship('UserNotification', cascade='all')
521 notifications = relationship('UserNotification', cascade='all')
522 # notifications assigned to this user
522 # notifications assigned to this user
523 user_created_notifications = relationship('Notification', cascade='all')
523 user_created_notifications = relationship('Notification', cascade='all')
524 # comments created by this user
524 # comments created by this user
525 user_comments = relationship('ChangesetComment', cascade='all')
525 user_comments = relationship('ChangesetComment', cascade='all')
526 # user profile extra info
526 # user profile extra info
527 user_emails = relationship('UserEmailMap', cascade='all')
527 user_emails = relationship('UserEmailMap', cascade='all')
528 user_ip_map = relationship('UserIpMap', cascade='all')
528 user_ip_map = relationship('UserIpMap', cascade='all')
529 user_auth_tokens = relationship('UserApiKeys', cascade='all')
529 user_auth_tokens = relationship('UserApiKeys', cascade='all')
530 # gists
530 # gists
531 user_gists = relationship('Gist', cascade='all')
531 user_gists = relationship('Gist', cascade='all')
532 # user pull requests
532 # user pull requests
533 user_pull_requests = relationship('PullRequest', cascade='all')
533 user_pull_requests = relationship('PullRequest', cascade='all')
534 # external identities
534 # external identities
535 extenal_identities = relationship(
535 extenal_identities = relationship(
536 'ExternalIdentity',
536 'ExternalIdentity',
537 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
537 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
538 cascade='all')
538 cascade='all')
539
539
540 def __unicode__(self):
540 def __unicode__(self):
541 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
541 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
542 self.user_id, self.username)
542 self.user_id, self.username)
543
543
544 @hybrid_property
544 @hybrid_property
545 def email(self):
545 def email(self):
546 return self._email
546 return self._email
547
547
548 @email.setter
548 @email.setter
549 def email(self, val):
549 def email(self, val):
550 self._email = val.lower() if val else None
550 self._email = val.lower() if val else None
551
551
552 @property
552 @property
553 def firstname(self):
553 def firstname(self):
554 # alias for future
554 # alias for future
555 return self.name
555 return self.name
556
556
557 @property
557 @property
558 def emails(self):
558 def emails(self):
559 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
559 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
560 return [self.email] + [x.email for x in other]
560 return [self.email] + [x.email for x in other]
561
561
562 @property
562 @property
563 def auth_tokens(self):
563 def auth_tokens(self):
564 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
564 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
565
565
566 @property
566 @property
567 def extra_auth_tokens(self):
567 def extra_auth_tokens(self):
568 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
568 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
569
569
570 @property
570 @property
571 def feed_token(self):
571 def feed_token(self):
572 feed_tokens = UserApiKeys.query()\
572 feed_tokens = UserApiKeys.query()\
573 .filter(UserApiKeys.user == self)\
573 .filter(UserApiKeys.user == self)\
574 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
574 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
575 .all()
575 .all()
576 if feed_tokens:
576 if feed_tokens:
577 return feed_tokens[0].api_key
577 return feed_tokens[0].api_key
578 else:
578 else:
579 # use the main token so we don't end up with nothing...
579 # use the main token so we don't end up with nothing...
580 return self.api_key
580 return self.api_key
581
581
582 @classmethod
582 @classmethod
583 def extra_valid_auth_tokens(cls, user, role=None):
583 def extra_valid_auth_tokens(cls, user, role=None):
584 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
584 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
585 .filter(or_(UserApiKeys.expires == -1,
585 .filter(or_(UserApiKeys.expires == -1,
586 UserApiKeys.expires >= time.time()))
586 UserApiKeys.expires >= time.time()))
587 if role:
587 if role:
588 tokens = tokens.filter(or_(UserApiKeys.role == role,
588 tokens = tokens.filter(or_(UserApiKeys.role == role,
589 UserApiKeys.role == UserApiKeys.ROLE_ALL))
589 UserApiKeys.role == UserApiKeys.ROLE_ALL))
590 return tokens.all()
590 return tokens.all()
591
591
592 @property
592 @property
593 def ip_addresses(self):
593 def ip_addresses(self):
594 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
594 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
595 return [x.ip_addr for x in ret]
595 return [x.ip_addr for x in ret]
596
596
597 @property
597 @property
598 def username_and_name(self):
598 def username_and_name(self):
599 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
599 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
600
600
601 @property
601 @property
602 def username_or_name_or_email(self):
602 def username_or_name_or_email(self):
603 full_name = self.full_name if self.full_name is not ' ' else None
603 full_name = self.full_name if self.full_name is not ' ' else None
604 return self.username or full_name or self.email
604 return self.username or full_name or self.email
605
605
606 @property
606 @property
607 def full_name(self):
607 def full_name(self):
608 return '%s %s' % (self.firstname, self.lastname)
608 return '%s %s' % (self.firstname, self.lastname)
609
609
610 @property
610 @property
611 def full_name_or_username(self):
611 def full_name_or_username(self):
612 return ('%s %s' % (self.firstname, self.lastname)
612 return ('%s %s' % (self.firstname, self.lastname)
613 if (self.firstname and self.lastname) else self.username)
613 if (self.firstname and self.lastname) else self.username)
614
614
615 @property
615 @property
616 def full_contact(self):
616 def full_contact(self):
617 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
617 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
618
618
619 @property
619 @property
620 def short_contact(self):
620 def short_contact(self):
621 return '%s %s' % (self.firstname, self.lastname)
621 return '%s %s' % (self.firstname, self.lastname)
622
622
623 @property
623 @property
624 def is_admin(self):
624 def is_admin(self):
625 return self.admin
625 return self.admin
626
626
627 @property
627 @property
628 def AuthUser(self):
628 def AuthUser(self):
629 """
629 """
630 Returns instance of AuthUser for this user
630 Returns instance of AuthUser for this user
631 """
631 """
632 from rhodecode.lib.auth import AuthUser
632 from rhodecode.lib.auth import AuthUser
633 return AuthUser(user_id=self.user_id, api_key=self.api_key,
633 return AuthUser(user_id=self.user_id, api_key=self.api_key,
634 username=self.username)
634 username=self.username)
635
635
636 @hybrid_property
636 @hybrid_property
637 def user_data(self):
637 def user_data(self):
638 if not self._user_data:
638 if not self._user_data:
639 return {}
639 return {}
640
640
641 try:
641 try:
642 return json.loads(self._user_data)
642 return json.loads(self._user_data)
643 except TypeError:
643 except TypeError:
644 return {}
644 return {}
645
645
646 @user_data.setter
646 @user_data.setter
647 def user_data(self, val):
647 def user_data(self, val):
648 if not isinstance(val, dict):
648 if not isinstance(val, dict):
649 raise Exception('user_data must be dict, got %s' % type(val))
649 raise Exception('user_data must be dict, got %s' % type(val))
650 try:
650 try:
651 self._user_data = json.dumps(val)
651 self._user_data = json.dumps(val)
652 except Exception:
652 except Exception:
653 log.error(traceback.format_exc())
653 log.error(traceback.format_exc())
654
654
655 @classmethod
655 @classmethod
656 def get_by_username(cls, username, case_insensitive=False,
656 def get_by_username(cls, username, case_insensitive=False,
657 cache=False, identity_cache=False):
657 cache=False, identity_cache=False):
658 session = Session()
658 session = Session()
659
659
660 if case_insensitive:
660 if case_insensitive:
661 q = cls.query().filter(
661 q = cls.query().filter(
662 func.lower(cls.username) == func.lower(username))
662 func.lower(cls.username) == func.lower(username))
663 else:
663 else:
664 q = cls.query().filter(cls.username == username)
664 q = cls.query().filter(cls.username == username)
665
665
666 if cache:
666 if cache:
667 if identity_cache:
667 if identity_cache:
668 val = cls.identity_cache(session, 'username', username)
668 val = cls.identity_cache(session, 'username', username)
669 if val:
669 if val:
670 return val
670 return val
671 else:
671 else:
672 q = q.options(
672 q = q.options(
673 FromCache("sql_cache_short",
673 FromCache("sql_cache_short",
674 "get_user_by_name_%s" % _hash_key(username)))
674 "get_user_by_name_%s" % _hash_key(username)))
675
675
676 return q.scalar()
676 return q.scalar()
677
677
678 @classmethod
678 @classmethod
679 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
679 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
680 q = cls.query().filter(cls.api_key == auth_token)
680 q = cls.query().filter(cls.api_key == auth_token)
681
681
682 if cache:
682 if cache:
683 q = q.options(FromCache("sql_cache_short",
683 q = q.options(FromCache("sql_cache_short",
684 "get_auth_token_%s" % auth_token))
684 "get_auth_token_%s" % auth_token))
685 res = q.scalar()
685 res = q.scalar()
686
686
687 if fallback and not res:
687 if fallback and not res:
688 #fallback to additional keys
688 #fallback to additional keys
689 _res = UserApiKeys.query()\
689 _res = UserApiKeys.query()\
690 .filter(UserApiKeys.api_key == auth_token)\
690 .filter(UserApiKeys.api_key == auth_token)\
691 .filter(or_(UserApiKeys.expires == -1,
691 .filter(or_(UserApiKeys.expires == -1,
692 UserApiKeys.expires >= time.time()))\
692 UserApiKeys.expires >= time.time()))\
693 .first()
693 .first()
694 if _res:
694 if _res:
695 res = _res.user
695 res = _res.user
696 return res
696 return res
697
697
698 @classmethod
698 @classmethod
699 def get_by_email(cls, email, case_insensitive=False, cache=False):
699 def get_by_email(cls, email, case_insensitive=False, cache=False):
700
700
701 if case_insensitive:
701 if case_insensitive:
702 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
702 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
703
703
704 else:
704 else:
705 q = cls.query().filter(cls.email == email)
705 q = cls.query().filter(cls.email == email)
706
706
707 if cache:
707 if cache:
708 q = q.options(FromCache("sql_cache_short",
708 q = q.options(FromCache("sql_cache_short",
709 "get_email_key_%s" % email))
709 "get_email_key_%s" % email))
710
710
711 ret = q.scalar()
711 ret = q.scalar()
712 if ret is None:
712 if ret is None:
713 q = UserEmailMap.query()
713 q = UserEmailMap.query()
714 # try fetching in alternate email map
714 # try fetching in alternate email map
715 if case_insensitive:
715 if case_insensitive:
716 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
716 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
717 else:
717 else:
718 q = q.filter(UserEmailMap.email == email)
718 q = q.filter(UserEmailMap.email == email)
719 q = q.options(joinedload(UserEmailMap.user))
719 q = q.options(joinedload(UserEmailMap.user))
720 if cache:
720 if cache:
721 q = q.options(FromCache("sql_cache_short",
721 q = q.options(FromCache("sql_cache_short",
722 "get_email_map_key_%s" % email))
722 "get_email_map_key_%s" % email))
723 ret = getattr(q.scalar(), 'user', None)
723 ret = getattr(q.scalar(), 'user', None)
724
724
725 return ret
725 return ret
726
726
727 @classmethod
727 @classmethod
728 def get_from_cs_author(cls, author):
728 def get_from_cs_author(cls, author):
729 """
729 """
730 Tries to get User objects out of commit author string
730 Tries to get User objects out of commit author string
731
731
732 :param author:
732 :param author:
733 """
733 """
734 from rhodecode.lib.helpers import email, author_name
734 from rhodecode.lib.helpers import email, author_name
735 # Valid email in the attribute passed, see if they're in the system
735 # Valid email in the attribute passed, see if they're in the system
736 _email = email(author)
736 _email = email(author)
737 if _email:
737 if _email:
738 user = cls.get_by_email(_email, case_insensitive=True)
738 user = cls.get_by_email(_email, case_insensitive=True)
739 if user:
739 if user:
740 return user
740 return user
741 # Maybe we can match by username?
741 # Maybe we can match by username?
742 _author = author_name(author)
742 _author = author_name(author)
743 user = cls.get_by_username(_author, case_insensitive=True)
743 user = cls.get_by_username(_author, case_insensitive=True)
744 if user:
744 if user:
745 return user
745 return user
746
746
747 def update_userdata(self, **kwargs):
747 def update_userdata(self, **kwargs):
748 usr = self
748 usr = self
749 old = usr.user_data
749 old = usr.user_data
750 old.update(**kwargs)
750 old.update(**kwargs)
751 usr.user_data = old
751 usr.user_data = old
752 Session().add(usr)
752 Session().add(usr)
753 log.debug('updated userdata with ', kwargs)
753 log.debug('updated userdata with ', kwargs)
754
754
755 def update_lastlogin(self):
755 def update_lastlogin(self):
756 """Update user lastlogin"""
756 """Update user lastlogin"""
757 self.last_login = datetime.datetime.now()
757 self.last_login = datetime.datetime.now()
758 Session().add(self)
758 Session().add(self)
759 log.debug('updated user %s lastlogin', self.username)
759 log.debug('updated user %s lastlogin', self.username)
760
760
761 def update_lastactivity(self):
761 def update_lastactivity(self):
762 """Update user lastactivity"""
762 """Update user lastactivity"""
763 usr = self
763 usr = self
764 old = usr.user_data
764 old = usr.user_data
765 old.update({'last_activity': time.time()})
765 old.update({'last_activity': time.time()})
766 usr.user_data = old
766 usr.user_data = old
767 Session().add(usr)
767 Session().add(usr)
768 log.debug('updated user %s lastactivity', usr.username)
768 log.debug('updated user %s lastactivity', usr.username)
769
769
770 def update_password(self, new_password, change_api_key=False):
770 def update_password(self, new_password, change_api_key=False):
771 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
771 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
772
772
773 self.password = get_crypt_password(new_password)
773 self.password = get_crypt_password(new_password)
774 if change_api_key:
774 if change_api_key:
775 self.api_key = generate_auth_token(self.username)
775 self.api_key = generate_auth_token(self.username)
776 Session().add(self)
776 Session().add(self)
777
777
778 @classmethod
778 @classmethod
779 def get_first_admin(cls):
779 def get_first_super_admin(cls):
780 user = User.query().filter(User.admin == True).first()
780 user = User.query().filter(User.admin == true()).first()
781 if user is None:
781 if user is None:
782 raise Exception('Missing administrative account!')
782 raise Exception('Missing administrative account!')
783 return user
783 return user
784
784
785 @classmethod
785 @classmethod
786 def get_all_super_admins(cls):
786 def get_all_super_admins(cls):
787 """
787 """
788 Returns all admin accounts sorted by username
788 Returns all admin accounts sorted by username
789 """
789 """
790 return User.query().filter(User.admin == true())\
790 return User.query().filter(User.admin == true())\
791 .order_by(User.username.asc()).all()
791 .order_by(User.username.asc()).all()
792
792
793 @classmethod
793 @classmethod
794 def get_default_user(cls, cache=False):
794 def get_default_user(cls, cache=False):
795 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
795 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
796 if user is None:
796 if user is None:
797 raise Exception('Missing default account!')
797 raise Exception('Missing default account!')
798 return user
798 return user
799
799
800 def _get_default_perms(self, user, suffix=''):
800 def _get_default_perms(self, user, suffix=''):
801 from rhodecode.model.permission import PermissionModel
801 from rhodecode.model.permission import PermissionModel
802 return PermissionModel().get_default_perms(user.user_perms, suffix)
802 return PermissionModel().get_default_perms(user.user_perms, suffix)
803
803
804 def get_default_perms(self, suffix=''):
804 def get_default_perms(self, suffix=''):
805 return self._get_default_perms(self, suffix)
805 return self._get_default_perms(self, suffix)
806
806
807 def get_api_data(self, include_secrets=False, details='full'):
807 def get_api_data(self, include_secrets=False, details='full'):
808 """
808 """
809 Common function for generating user related data for API
809 Common function for generating user related data for API
810
810
811 :param include_secrets: By default secrets in the API data will be replaced
811 :param include_secrets: By default secrets in the API data will be replaced
812 by a placeholder value to prevent exposing this data by accident. In case
812 by a placeholder value to prevent exposing this data by accident. In case
813 this data shall be exposed, set this flag to ``True``.
813 this data shall be exposed, set this flag to ``True``.
814
814
815 :param details: details can be 'basic|full' basic gives only a subset of
815 :param details: details can be 'basic|full' basic gives only a subset of
816 the available user information that includes user_id, name and emails.
816 the available user information that includes user_id, name and emails.
817 """
817 """
818 user = self
818 user = self
819 user_data = self.user_data
819 user_data = self.user_data
820 data = {
820 data = {
821 'user_id': user.user_id,
821 'user_id': user.user_id,
822 'username': user.username,
822 'username': user.username,
823 'firstname': user.name,
823 'firstname': user.name,
824 'lastname': user.lastname,
824 'lastname': user.lastname,
825 'email': user.email,
825 'email': user.email,
826 'emails': user.emails,
826 'emails': user.emails,
827 }
827 }
828 if details == 'basic':
828 if details == 'basic':
829 return data
829 return data
830
830
831 api_key_length = 40
831 api_key_length = 40
832 api_key_replacement = '*' * api_key_length
832 api_key_replacement = '*' * api_key_length
833
833
834 extras = {
834 extras = {
835 'api_key': api_key_replacement,
835 'api_key': api_key_replacement,
836 'api_keys': [api_key_replacement],
836 'api_keys': [api_key_replacement],
837 'active': user.active,
837 'active': user.active,
838 'admin': user.admin,
838 'admin': user.admin,
839 'extern_type': user.extern_type,
839 'extern_type': user.extern_type,
840 'extern_name': user.extern_name,
840 'extern_name': user.extern_name,
841 'last_login': user.last_login,
841 'last_login': user.last_login,
842 'ip_addresses': user.ip_addresses,
842 'ip_addresses': user.ip_addresses,
843 'language': user_data.get('language')
843 'language': user_data.get('language')
844 }
844 }
845 data.update(extras)
845 data.update(extras)
846
846
847 if include_secrets:
847 if include_secrets:
848 data['api_key'] = user.api_key
848 data['api_key'] = user.api_key
849 data['api_keys'] = user.auth_tokens
849 data['api_keys'] = user.auth_tokens
850 return data
850 return data
851
851
852 def __json__(self):
852 def __json__(self):
853 data = {
853 data = {
854 'full_name': self.full_name,
854 'full_name': self.full_name,
855 'full_name_or_username': self.full_name_or_username,
855 'full_name_or_username': self.full_name_or_username,
856 'short_contact': self.short_contact,
856 'short_contact': self.short_contact,
857 'full_contact': self.full_contact,
857 'full_contact': self.full_contact,
858 }
858 }
859 data.update(self.get_api_data())
859 data.update(self.get_api_data())
860 return data
860 return data
861
861
862
862
863 class UserApiKeys(Base, BaseModel):
863 class UserApiKeys(Base, BaseModel):
864 __tablename__ = 'user_api_keys'
864 __tablename__ = 'user_api_keys'
865 __table_args__ = (
865 __table_args__ = (
866 Index('uak_api_key_idx', 'api_key'),
866 Index('uak_api_key_idx', 'api_key'),
867 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
867 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
868 UniqueConstraint('api_key'),
868 UniqueConstraint('api_key'),
869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
870 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
870 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
871 )
871 )
872 __mapper_args__ = {}
872 __mapper_args__ = {}
873
873
874 # ApiKey role
874 # ApiKey role
875 ROLE_ALL = 'token_role_all'
875 ROLE_ALL = 'token_role_all'
876 ROLE_HTTP = 'token_role_http'
876 ROLE_HTTP = 'token_role_http'
877 ROLE_VCS = 'token_role_vcs'
877 ROLE_VCS = 'token_role_vcs'
878 ROLE_API = 'token_role_api'
878 ROLE_API = 'token_role_api'
879 ROLE_FEED = 'token_role_feed'
879 ROLE_FEED = 'token_role_feed'
880 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
880 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
881
881
882 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
882 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
884 api_key = Column("api_key", String(255), nullable=False, unique=True)
884 api_key = Column("api_key", String(255), nullable=False, unique=True)
885 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
885 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
886 expires = Column('expires', Float(53), nullable=False)
886 expires = Column('expires', Float(53), nullable=False)
887 role = Column('role', String(255), nullable=True)
887 role = Column('role', String(255), nullable=True)
888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
889
889
890 user = relationship('User', lazy='joined')
890 user = relationship('User', lazy='joined')
891
891
892 @classmethod
892 @classmethod
893 def _get_role_name(cls, role):
893 def _get_role_name(cls, role):
894 return {
894 return {
895 cls.ROLE_ALL: _('all'),
895 cls.ROLE_ALL: _('all'),
896 cls.ROLE_HTTP: _('http/web interface'),
896 cls.ROLE_HTTP: _('http/web interface'),
897 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
897 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
898 cls.ROLE_API: _('api calls'),
898 cls.ROLE_API: _('api calls'),
899 cls.ROLE_FEED: _('feed access'),
899 cls.ROLE_FEED: _('feed access'),
900 }.get(role, role)
900 }.get(role, role)
901
901
902 @property
902 @property
903 def expired(self):
903 def expired(self):
904 if self.expires == -1:
904 if self.expires == -1:
905 return False
905 return False
906 return time.time() > self.expires
906 return time.time() > self.expires
907
907
908 @property
908 @property
909 def role_humanized(self):
909 def role_humanized(self):
910 return self._get_role_name(self.role)
910 return self._get_role_name(self.role)
911
911
912
912
913 class UserEmailMap(Base, BaseModel):
913 class UserEmailMap(Base, BaseModel):
914 __tablename__ = 'user_email_map'
914 __tablename__ = 'user_email_map'
915 __table_args__ = (
915 __table_args__ = (
916 Index('uem_email_idx', 'email'),
916 Index('uem_email_idx', 'email'),
917 UniqueConstraint('email'),
917 UniqueConstraint('email'),
918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
920 )
920 )
921 __mapper_args__ = {}
921 __mapper_args__ = {}
922
922
923 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
923 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
924 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
924 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
925 _email = Column("email", String(255), nullable=True, unique=False, default=None)
925 _email = Column("email", String(255), nullable=True, unique=False, default=None)
926 user = relationship('User', lazy='joined')
926 user = relationship('User', lazy='joined')
927
927
928 @validates('_email')
928 @validates('_email')
929 def validate_email(self, key, email):
929 def validate_email(self, key, email):
930 # check if this email is not main one
930 # check if this email is not main one
931 main_email = Session().query(User).filter(User.email == email).scalar()
931 main_email = Session().query(User).filter(User.email == email).scalar()
932 if main_email is not None:
932 if main_email is not None:
933 raise AttributeError('email %s is present is user table' % email)
933 raise AttributeError('email %s is present is user table' % email)
934 return email
934 return email
935
935
936 @hybrid_property
936 @hybrid_property
937 def email(self):
937 def email(self):
938 return self._email
938 return self._email
939
939
940 @email.setter
940 @email.setter
941 def email(self, val):
941 def email(self, val):
942 self._email = val.lower() if val else None
942 self._email = val.lower() if val else None
943
943
944
944
945 class UserIpMap(Base, BaseModel):
945 class UserIpMap(Base, BaseModel):
946 __tablename__ = 'user_ip_map'
946 __tablename__ = 'user_ip_map'
947 __table_args__ = (
947 __table_args__ = (
948 UniqueConstraint('user_id', 'ip_addr'),
948 UniqueConstraint('user_id', 'ip_addr'),
949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
951 )
951 )
952 __mapper_args__ = {}
952 __mapper_args__ = {}
953
953
954 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
954 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
955 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
955 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
956 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
956 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
957 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
957 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
958 description = Column("description", String(10000), nullable=True, unique=None, default=None)
958 description = Column("description", String(10000), nullable=True, unique=None, default=None)
959 user = relationship('User', lazy='joined')
959 user = relationship('User', lazy='joined')
960
960
961 @classmethod
961 @classmethod
962 def _get_ip_range(cls, ip_addr):
962 def _get_ip_range(cls, ip_addr):
963 net = ipaddress.ip_network(ip_addr, strict=False)
963 net = ipaddress.ip_network(ip_addr, strict=False)
964 return [str(net.network_address), str(net.broadcast_address)]
964 return [str(net.network_address), str(net.broadcast_address)]
965
965
966 def __json__(self):
966 def __json__(self):
967 return {
967 return {
968 'ip_addr': self.ip_addr,
968 'ip_addr': self.ip_addr,
969 'ip_range': self._get_ip_range(self.ip_addr),
969 'ip_range': self._get_ip_range(self.ip_addr),
970 }
970 }
971
971
972 def __unicode__(self):
972 def __unicode__(self):
973 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
973 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
974 self.user_id, self.ip_addr)
974 self.user_id, self.ip_addr)
975
975
976 class UserLog(Base, BaseModel):
976 class UserLog(Base, BaseModel):
977 __tablename__ = 'user_logs'
977 __tablename__ = 'user_logs'
978 __table_args__ = (
978 __table_args__ = (
979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
981 )
981 )
982 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
982 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
983 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)
984 username = Column("username", String(255), nullable=True, unique=None, default=None)
984 username = Column("username", String(255), nullable=True, unique=None, default=None)
985 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
985 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
986 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
986 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
987 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
987 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
988 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
988 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
989 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
989 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
990
990
991 def __unicode__(self):
991 def __unicode__(self):
992 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
992 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
993 self.repository_name,
993 self.repository_name,
994 self.action)
994 self.action)
995
995
996 @property
996 @property
997 def action_as_day(self):
997 def action_as_day(self):
998 return datetime.date(*self.action_date.timetuple()[:3])
998 return datetime.date(*self.action_date.timetuple()[:3])
999
999
1000 user = relationship('User')
1000 user = relationship('User')
1001 repository = relationship('Repository', cascade='')
1001 repository = relationship('Repository', cascade='')
1002
1002
1003
1003
1004 class UserGroup(Base, BaseModel):
1004 class UserGroup(Base, BaseModel):
1005 __tablename__ = 'users_groups'
1005 __tablename__ = 'users_groups'
1006 __table_args__ = (
1006 __table_args__ = (
1007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1009 )
1009 )
1010
1010
1011 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1011 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1012 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1012 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1013 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1013 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1014 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1014 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1015 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1015 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1017 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1017 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1018 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1018 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1019
1019
1020 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1020 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1021 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1021 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1022 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1022 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1023 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1023 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1024 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1024 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1025 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1025 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1026
1026
1027 user = relationship('User')
1027 user = relationship('User')
1028
1028
1029 @hybrid_property
1029 @hybrid_property
1030 def group_data(self):
1030 def group_data(self):
1031 if not self._group_data:
1031 if not self._group_data:
1032 return {}
1032 return {}
1033
1033
1034 try:
1034 try:
1035 return json.loads(self._group_data)
1035 return json.loads(self._group_data)
1036 except TypeError:
1036 except TypeError:
1037 return {}
1037 return {}
1038
1038
1039 @group_data.setter
1039 @group_data.setter
1040 def group_data(self, val):
1040 def group_data(self, val):
1041 try:
1041 try:
1042 self._group_data = json.dumps(val)
1042 self._group_data = json.dumps(val)
1043 except Exception:
1043 except Exception:
1044 log.error(traceback.format_exc())
1044 log.error(traceback.format_exc())
1045
1045
1046 def __unicode__(self):
1046 def __unicode__(self):
1047 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1047 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1048 self.users_group_id,
1048 self.users_group_id,
1049 self.users_group_name)
1049 self.users_group_name)
1050
1050
1051 @classmethod
1051 @classmethod
1052 def get_by_group_name(cls, group_name, cache=False,
1052 def get_by_group_name(cls, group_name, cache=False,
1053 case_insensitive=False):
1053 case_insensitive=False):
1054 if case_insensitive:
1054 if case_insensitive:
1055 q = cls.query().filter(func.lower(cls.users_group_name) ==
1055 q = cls.query().filter(func.lower(cls.users_group_name) ==
1056 func.lower(group_name))
1056 func.lower(group_name))
1057
1057
1058 else:
1058 else:
1059 q = cls.query().filter(cls.users_group_name == group_name)
1059 q = cls.query().filter(cls.users_group_name == group_name)
1060 if cache:
1060 if cache:
1061 q = q.options(FromCache(
1061 q = q.options(FromCache(
1062 "sql_cache_short",
1062 "sql_cache_short",
1063 "get_group_%s" % _hash_key(group_name)))
1063 "get_group_%s" % _hash_key(group_name)))
1064 return q.scalar()
1064 return q.scalar()
1065
1065
1066 @classmethod
1066 @classmethod
1067 def get(cls, user_group_id, cache=False):
1067 def get(cls, user_group_id, cache=False):
1068 user_group = cls.query()
1068 user_group = cls.query()
1069 if cache:
1069 if cache:
1070 user_group = user_group.options(FromCache("sql_cache_short",
1070 user_group = user_group.options(FromCache("sql_cache_short",
1071 "get_users_group_%s" % user_group_id))
1071 "get_users_group_%s" % user_group_id))
1072 return user_group.get(user_group_id)
1072 return user_group.get(user_group_id)
1073
1073
1074 def permissions(self, with_admins=True, with_owner=True):
1074 def permissions(self, with_admins=True, with_owner=True):
1075 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1075 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1076 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1076 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1077 joinedload(UserUserGroupToPerm.user),
1077 joinedload(UserUserGroupToPerm.user),
1078 joinedload(UserUserGroupToPerm.permission),)
1078 joinedload(UserUserGroupToPerm.permission),)
1079
1079
1080 # get owners and admins and permissions. We do a trick of re-writing
1080 # get owners and admins and permissions. We do a trick of re-writing
1081 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1081 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1082 # has a global reference and changing one object propagates to all
1082 # has a global reference and changing one object propagates to all
1083 # others. This means if admin is also an owner admin_row that change
1083 # others. This means if admin is also an owner admin_row that change
1084 # would propagate to both objects
1084 # would propagate to both objects
1085 perm_rows = []
1085 perm_rows = []
1086 for _usr in q.all():
1086 for _usr in q.all():
1087 usr = AttributeDict(_usr.user.get_dict())
1087 usr = AttributeDict(_usr.user.get_dict())
1088 usr.permission = _usr.permission.permission_name
1088 usr.permission = _usr.permission.permission_name
1089 perm_rows.append(usr)
1089 perm_rows.append(usr)
1090
1090
1091 # filter the perm rows by 'default' first and then sort them by
1091 # filter the perm rows by 'default' first and then sort them by
1092 # admin,write,read,none permissions sorted again alphabetically in
1092 # admin,write,read,none permissions sorted again alphabetically in
1093 # each group
1093 # each group
1094 perm_rows = sorted(perm_rows, key=display_sort)
1094 perm_rows = sorted(perm_rows, key=display_sort)
1095
1095
1096 _admin_perm = 'usergroup.admin'
1096 _admin_perm = 'usergroup.admin'
1097 owner_row = []
1097 owner_row = []
1098 if with_owner:
1098 if with_owner:
1099 usr = AttributeDict(self.user.get_dict())
1099 usr = AttributeDict(self.user.get_dict())
1100 usr.owner_row = True
1100 usr.owner_row = True
1101 usr.permission = _admin_perm
1101 usr.permission = _admin_perm
1102 owner_row.append(usr)
1102 owner_row.append(usr)
1103
1103
1104 super_admin_rows = []
1104 super_admin_rows = []
1105 if with_admins:
1105 if with_admins:
1106 for usr in User.get_all_super_admins():
1106 for usr in User.get_all_super_admins():
1107 # if this admin is also owner, don't double the record
1107 # if this admin is also owner, don't double the record
1108 if usr.user_id == owner_row[0].user_id:
1108 if usr.user_id == owner_row[0].user_id:
1109 owner_row[0].admin_row = True
1109 owner_row[0].admin_row = True
1110 else:
1110 else:
1111 usr = AttributeDict(usr.get_dict())
1111 usr = AttributeDict(usr.get_dict())
1112 usr.admin_row = True
1112 usr.admin_row = True
1113 usr.permission = _admin_perm
1113 usr.permission = _admin_perm
1114 super_admin_rows.append(usr)
1114 super_admin_rows.append(usr)
1115
1115
1116 return super_admin_rows + owner_row + perm_rows
1116 return super_admin_rows + owner_row + perm_rows
1117
1117
1118 def permission_user_groups(self):
1118 def permission_user_groups(self):
1119 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1119 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1120 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1120 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1121 joinedload(UserGroupUserGroupToPerm.target_user_group),
1121 joinedload(UserGroupUserGroupToPerm.target_user_group),
1122 joinedload(UserGroupUserGroupToPerm.permission),)
1122 joinedload(UserGroupUserGroupToPerm.permission),)
1123
1123
1124 perm_rows = []
1124 perm_rows = []
1125 for _user_group in q.all():
1125 for _user_group in q.all():
1126 usr = AttributeDict(_user_group.user_group.get_dict())
1126 usr = AttributeDict(_user_group.user_group.get_dict())
1127 usr.permission = _user_group.permission.permission_name
1127 usr.permission = _user_group.permission.permission_name
1128 perm_rows.append(usr)
1128 perm_rows.append(usr)
1129
1129
1130 return perm_rows
1130 return perm_rows
1131
1131
1132 def _get_default_perms(self, user_group, suffix=''):
1132 def _get_default_perms(self, user_group, suffix=''):
1133 from rhodecode.model.permission import PermissionModel
1133 from rhodecode.model.permission import PermissionModel
1134 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1134 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1135
1135
1136 def get_default_perms(self, suffix=''):
1136 def get_default_perms(self, suffix=''):
1137 return self._get_default_perms(self, suffix)
1137 return self._get_default_perms(self, suffix)
1138
1138
1139 def get_api_data(self, with_group_members=True, include_secrets=False):
1139 def get_api_data(self, with_group_members=True, include_secrets=False):
1140 """
1140 """
1141 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1141 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1142 basically forwarded.
1142 basically forwarded.
1143
1143
1144 """
1144 """
1145 user_group = self
1145 user_group = self
1146
1146
1147 data = {
1147 data = {
1148 'users_group_id': user_group.users_group_id,
1148 'users_group_id': user_group.users_group_id,
1149 'group_name': user_group.users_group_name,
1149 'group_name': user_group.users_group_name,
1150 'group_description': user_group.user_group_description,
1150 'group_description': user_group.user_group_description,
1151 'active': user_group.users_group_active,
1151 'active': user_group.users_group_active,
1152 'owner': user_group.user.username,
1152 'owner': user_group.user.username,
1153 }
1153 }
1154 if with_group_members:
1154 if with_group_members:
1155 users = []
1155 users = []
1156 for user in user_group.members:
1156 for user in user_group.members:
1157 user = user.user
1157 user = user.user
1158 users.append(user.get_api_data(include_secrets=include_secrets))
1158 users.append(user.get_api_data(include_secrets=include_secrets))
1159 data['users'] = users
1159 data['users'] = users
1160
1160
1161 return data
1161 return data
1162
1162
1163
1163
1164 class UserGroupMember(Base, BaseModel):
1164 class UserGroupMember(Base, BaseModel):
1165 __tablename__ = 'users_groups_members'
1165 __tablename__ = 'users_groups_members'
1166 __table_args__ = (
1166 __table_args__ = (
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1169 )
1169 )
1170
1170
1171 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1171 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1172 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1174
1174
1175 user = relationship('User', lazy='joined')
1175 user = relationship('User', lazy='joined')
1176 users_group = relationship('UserGroup')
1176 users_group = relationship('UserGroup')
1177
1177
1178 def __init__(self, gr_id='', u_id=''):
1178 def __init__(self, gr_id='', u_id=''):
1179 self.users_group_id = gr_id
1179 self.users_group_id = gr_id
1180 self.user_id = u_id
1180 self.user_id = u_id
1181
1181
1182
1182
1183 class RepositoryField(Base, BaseModel):
1183 class RepositoryField(Base, BaseModel):
1184 __tablename__ = 'repositories_fields'
1184 __tablename__ = 'repositories_fields'
1185 __table_args__ = (
1185 __table_args__ = (
1186 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1186 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1189 )
1189 )
1190 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1190 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1191
1191
1192 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1192 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1193 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1194 field_key = Column("field_key", String(250))
1194 field_key = Column("field_key", String(250))
1195 field_label = Column("field_label", String(1024), nullable=False)
1195 field_label = Column("field_label", String(1024), nullable=False)
1196 field_value = Column("field_value", String(10000), nullable=False)
1196 field_value = Column("field_value", String(10000), nullable=False)
1197 field_desc = Column("field_desc", String(1024), nullable=False)
1197 field_desc = Column("field_desc", String(1024), nullable=False)
1198 field_type = Column("field_type", String(255), nullable=False, unique=None)
1198 field_type = Column("field_type", String(255), nullable=False, unique=None)
1199 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1199 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1200
1200
1201 repository = relationship('Repository')
1201 repository = relationship('Repository')
1202
1202
1203 @property
1203 @property
1204 def field_key_prefixed(self):
1204 def field_key_prefixed(self):
1205 return 'ex_%s' % self.field_key
1205 return 'ex_%s' % self.field_key
1206
1206
1207 @classmethod
1207 @classmethod
1208 def un_prefix_key(cls, key):
1208 def un_prefix_key(cls, key):
1209 if key.startswith(cls.PREFIX):
1209 if key.startswith(cls.PREFIX):
1210 return key[len(cls.PREFIX):]
1210 return key[len(cls.PREFIX):]
1211 return key
1211 return key
1212
1212
1213 @classmethod
1213 @classmethod
1214 def get_by_key_name(cls, key, repo):
1214 def get_by_key_name(cls, key, repo):
1215 row = cls.query()\
1215 row = cls.query()\
1216 .filter(cls.repository == repo)\
1216 .filter(cls.repository == repo)\
1217 .filter(cls.field_key == key).scalar()
1217 .filter(cls.field_key == key).scalar()
1218 return row
1218 return row
1219
1219
1220
1220
1221 class Repository(Base, BaseModel):
1221 class Repository(Base, BaseModel):
1222 __tablename__ = 'repositories'
1222 __tablename__ = 'repositories'
1223 __table_args__ = (
1223 __table_args__ = (
1224 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1224 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1226 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1226 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1227 )
1227 )
1228 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1228 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1229 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1229 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1230
1230
1231 STATE_CREATED = 'repo_state_created'
1231 STATE_CREATED = 'repo_state_created'
1232 STATE_PENDING = 'repo_state_pending'
1232 STATE_PENDING = 'repo_state_pending'
1233 STATE_ERROR = 'repo_state_error'
1233 STATE_ERROR = 'repo_state_error'
1234
1234
1235 LOCK_AUTOMATIC = 'lock_auto'
1235 LOCK_AUTOMATIC = 'lock_auto'
1236 LOCK_API = 'lock_api'
1236 LOCK_API = 'lock_api'
1237 LOCK_WEB = 'lock_web'
1237 LOCK_WEB = 'lock_web'
1238 LOCK_PULL = 'lock_pull'
1238 LOCK_PULL = 'lock_pull'
1239
1239
1240 NAME_SEP = URL_SEP
1240 NAME_SEP = URL_SEP
1241
1241
1242 repo_id = Column(
1242 repo_id = Column(
1243 "repo_id", Integer(), nullable=False, unique=True, default=None,
1243 "repo_id", Integer(), nullable=False, unique=True, default=None,
1244 primary_key=True)
1244 primary_key=True)
1245 _repo_name = Column(
1245 _repo_name = Column(
1246 "repo_name", Text(), nullable=False, default=None)
1246 "repo_name", Text(), nullable=False, default=None)
1247 _repo_name_hash = Column(
1247 _repo_name_hash = Column(
1248 "repo_name_hash", String(255), nullable=False, unique=True)
1248 "repo_name_hash", String(255), nullable=False, unique=True)
1249 repo_state = Column("repo_state", String(255), nullable=True)
1249 repo_state = Column("repo_state", String(255), nullable=True)
1250
1250
1251 clone_uri = Column(
1251 clone_uri = Column(
1252 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1252 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1253 default=None)
1253 default=None)
1254 repo_type = Column(
1254 repo_type = Column(
1255 "repo_type", String(255), nullable=False, unique=False, default=None)
1255 "repo_type", String(255), nullable=False, unique=False, default=None)
1256 user_id = Column(
1256 user_id = Column(
1257 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1257 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1258 unique=False, default=None)
1258 unique=False, default=None)
1259 private = Column(
1259 private = Column(
1260 "private", Boolean(), nullable=True, unique=None, default=None)
1260 "private", Boolean(), nullable=True, unique=None, default=None)
1261 enable_statistics = Column(
1261 enable_statistics = Column(
1262 "statistics", Boolean(), nullable=True, unique=None, default=True)
1262 "statistics", Boolean(), nullable=True, unique=None, default=True)
1263 enable_downloads = Column(
1263 enable_downloads = Column(
1264 "downloads", Boolean(), nullable=True, unique=None, default=True)
1264 "downloads", Boolean(), nullable=True, unique=None, default=True)
1265 description = Column(
1265 description = Column(
1266 "description", String(10000), nullable=True, unique=None, default=None)
1266 "description", String(10000), nullable=True, unique=None, default=None)
1267 created_on = Column(
1267 created_on = Column(
1268 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1268 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1269 default=datetime.datetime.now)
1269 default=datetime.datetime.now)
1270 updated_on = Column(
1270 updated_on = Column(
1271 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1271 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1272 default=datetime.datetime.now)
1272 default=datetime.datetime.now)
1273 _landing_revision = Column(
1273 _landing_revision = Column(
1274 "landing_revision", String(255), nullable=False, unique=False,
1274 "landing_revision", String(255), nullable=False, unique=False,
1275 default=None)
1275 default=None)
1276 enable_locking = Column(
1276 enable_locking = Column(
1277 "enable_locking", Boolean(), nullable=False, unique=None,
1277 "enable_locking", Boolean(), nullable=False, unique=None,
1278 default=False)
1278 default=False)
1279 _locked = Column(
1279 _locked = Column(
1280 "locked", String(255), nullable=True, unique=False, default=None)
1280 "locked", String(255), nullable=True, unique=False, default=None)
1281 _changeset_cache = Column(
1281 _changeset_cache = Column(
1282 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1282 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1283
1283
1284 fork_id = Column(
1284 fork_id = Column(
1285 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1285 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1286 nullable=True, unique=False, default=None)
1286 nullable=True, unique=False, default=None)
1287 group_id = Column(
1287 group_id = Column(
1288 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1288 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1289 unique=False, default=None)
1289 unique=False, default=None)
1290
1290
1291 user = relationship('User', lazy='joined')
1291 user = relationship('User', lazy='joined')
1292 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1292 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1293 group = relationship('RepoGroup', lazy='joined')
1293 group = relationship('RepoGroup', lazy='joined')
1294 repo_to_perm = relationship(
1294 repo_to_perm = relationship(
1295 'UserRepoToPerm', cascade='all',
1295 'UserRepoToPerm', cascade='all',
1296 order_by='UserRepoToPerm.repo_to_perm_id')
1296 order_by='UserRepoToPerm.repo_to_perm_id')
1297 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1297 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1298 stats = relationship('Statistics', cascade='all', uselist=False)
1298 stats = relationship('Statistics', cascade='all', uselist=False)
1299
1299
1300 followers = relationship(
1300 followers = relationship(
1301 'UserFollowing',
1301 'UserFollowing',
1302 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1302 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1303 cascade='all')
1303 cascade='all')
1304 extra_fields = relationship(
1304 extra_fields = relationship(
1305 'RepositoryField', cascade="all, delete, delete-orphan")
1305 'RepositoryField', cascade="all, delete, delete-orphan")
1306 logs = relationship('UserLog')
1306 logs = relationship('UserLog')
1307 comments = relationship(
1307 comments = relationship(
1308 'ChangesetComment', cascade="all, delete, delete-orphan")
1308 'ChangesetComment', cascade="all, delete, delete-orphan")
1309 pull_requests_source = relationship(
1309 pull_requests_source = relationship(
1310 'PullRequest',
1310 'PullRequest',
1311 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1311 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1312 cascade="all, delete, delete-orphan")
1312 cascade="all, delete, delete-orphan")
1313 pull_requests_target = relationship(
1313 pull_requests_target = relationship(
1314 'PullRequest',
1314 'PullRequest',
1315 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1315 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1316 cascade="all, delete, delete-orphan")
1316 cascade="all, delete, delete-orphan")
1317 ui = relationship('RepoRhodeCodeUi', cascade="all")
1317 ui = relationship('RepoRhodeCodeUi', cascade="all")
1318 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1318 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1319
1319
1320 def __unicode__(self):
1320 def __unicode__(self):
1321 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1321 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1322 safe_unicode(self.repo_name))
1322 safe_unicode(self.repo_name))
1323
1323
1324 @hybrid_property
1324 @hybrid_property
1325 def landing_rev(self):
1325 def landing_rev(self):
1326 # always should return [rev_type, rev]
1326 # always should return [rev_type, rev]
1327 if self._landing_revision:
1327 if self._landing_revision:
1328 _rev_info = self._landing_revision.split(':')
1328 _rev_info = self._landing_revision.split(':')
1329 if len(_rev_info) < 2:
1329 if len(_rev_info) < 2:
1330 _rev_info.insert(0, 'rev')
1330 _rev_info.insert(0, 'rev')
1331 return [_rev_info[0], _rev_info[1]]
1331 return [_rev_info[0], _rev_info[1]]
1332 return [None, None]
1332 return [None, None]
1333
1333
1334 @landing_rev.setter
1334 @landing_rev.setter
1335 def landing_rev(self, val):
1335 def landing_rev(self, val):
1336 if ':' not in val:
1336 if ':' not in val:
1337 raise ValueError('value must be delimited with `:` and consist '
1337 raise ValueError('value must be delimited with `:` and consist '
1338 'of <rev_type>:<rev>, got %s instead' % val)
1338 'of <rev_type>:<rev>, got %s instead' % val)
1339 self._landing_revision = val
1339 self._landing_revision = val
1340
1340
1341 @hybrid_property
1341 @hybrid_property
1342 def locked(self):
1342 def locked(self):
1343 if self._locked:
1343 if self._locked:
1344 user_id, timelocked, reason = self._locked.split(':')
1344 user_id, timelocked, reason = self._locked.split(':')
1345 lock_values = int(user_id), timelocked, reason
1345 lock_values = int(user_id), timelocked, reason
1346 else:
1346 else:
1347 lock_values = [None, None, None]
1347 lock_values = [None, None, None]
1348 return lock_values
1348 return lock_values
1349
1349
1350 @locked.setter
1350 @locked.setter
1351 def locked(self, val):
1351 def locked(self, val):
1352 if val and isinstance(val, (list, tuple)):
1352 if val and isinstance(val, (list, tuple)):
1353 self._locked = ':'.join(map(str, val))
1353 self._locked = ':'.join(map(str, val))
1354 else:
1354 else:
1355 self._locked = None
1355 self._locked = None
1356
1356
1357 @hybrid_property
1357 @hybrid_property
1358 def changeset_cache(self):
1358 def changeset_cache(self):
1359 from rhodecode.lib.vcs.backends.base import EmptyCommit
1359 from rhodecode.lib.vcs.backends.base import EmptyCommit
1360 dummy = EmptyCommit().__json__()
1360 dummy = EmptyCommit().__json__()
1361 if not self._changeset_cache:
1361 if not self._changeset_cache:
1362 return dummy
1362 return dummy
1363 try:
1363 try:
1364 return json.loads(self._changeset_cache)
1364 return json.loads(self._changeset_cache)
1365 except TypeError:
1365 except TypeError:
1366 return dummy
1366 return dummy
1367 except Exception:
1367 except Exception:
1368 log.error(traceback.format_exc())
1368 log.error(traceback.format_exc())
1369 return dummy
1369 return dummy
1370
1370
1371 @changeset_cache.setter
1371 @changeset_cache.setter
1372 def changeset_cache(self, val):
1372 def changeset_cache(self, val):
1373 try:
1373 try:
1374 self._changeset_cache = json.dumps(val)
1374 self._changeset_cache = json.dumps(val)
1375 except Exception:
1375 except Exception:
1376 log.error(traceback.format_exc())
1376 log.error(traceback.format_exc())
1377
1377
1378 @hybrid_property
1378 @hybrid_property
1379 def repo_name(self):
1379 def repo_name(self):
1380 return self._repo_name
1380 return self._repo_name
1381
1381
1382 @repo_name.setter
1382 @repo_name.setter
1383 def repo_name(self, value):
1383 def repo_name(self, value):
1384 self._repo_name = value
1384 self._repo_name = value
1385 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1385 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1386
1386
1387 @classmethod
1387 @classmethod
1388 def normalize_repo_name(cls, repo_name):
1388 def normalize_repo_name(cls, repo_name):
1389 """
1389 """
1390 Normalizes os specific repo_name to the format internally stored inside
1390 Normalizes os specific repo_name to the format internally stored inside
1391 database using URL_SEP
1391 database using URL_SEP
1392
1392
1393 :param cls:
1393 :param cls:
1394 :param repo_name:
1394 :param repo_name:
1395 """
1395 """
1396 return cls.NAME_SEP.join(repo_name.split(os.sep))
1396 return cls.NAME_SEP.join(repo_name.split(os.sep))
1397
1397
1398 @classmethod
1398 @classmethod
1399 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1399 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1400 session = Session()
1400 session = Session()
1401 q = session.query(cls).filter(cls.repo_name == repo_name)
1401 q = session.query(cls).filter(cls.repo_name == repo_name)
1402
1402
1403 if cache:
1403 if cache:
1404 if identity_cache:
1404 if identity_cache:
1405 val = cls.identity_cache(session, 'repo_name', repo_name)
1405 val = cls.identity_cache(session, 'repo_name', repo_name)
1406 if val:
1406 if val:
1407 return val
1407 return val
1408 else:
1408 else:
1409 q = q.options(
1409 q = q.options(
1410 FromCache("sql_cache_short",
1410 FromCache("sql_cache_short",
1411 "get_repo_by_name_%s" % _hash_key(repo_name)))
1411 "get_repo_by_name_%s" % _hash_key(repo_name)))
1412
1412
1413 return q.scalar()
1413 return q.scalar()
1414
1414
1415 @classmethod
1415 @classmethod
1416 def get_by_full_path(cls, repo_full_path):
1416 def get_by_full_path(cls, repo_full_path):
1417 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1417 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1418 repo_name = cls.normalize_repo_name(repo_name)
1418 repo_name = cls.normalize_repo_name(repo_name)
1419 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1419 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1420
1420
1421 @classmethod
1421 @classmethod
1422 def get_repo_forks(cls, repo_id):
1422 def get_repo_forks(cls, repo_id):
1423 return cls.query().filter(Repository.fork_id == repo_id)
1423 return cls.query().filter(Repository.fork_id == repo_id)
1424
1424
1425 @classmethod
1425 @classmethod
1426 def base_path(cls):
1426 def base_path(cls):
1427 """
1427 """
1428 Returns base path when all repos are stored
1428 Returns base path when all repos are stored
1429
1429
1430 :param cls:
1430 :param cls:
1431 """
1431 """
1432 q = Session().query(RhodeCodeUi)\
1432 q = Session().query(RhodeCodeUi)\
1433 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1433 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1434 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1434 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1435 return q.one().ui_value
1435 return q.one().ui_value
1436
1436
1437 @classmethod
1437 @classmethod
1438 def is_valid(cls, repo_name):
1438 def is_valid(cls, repo_name):
1439 """
1439 """
1440 returns True if given repo name is a valid filesystem repository
1440 returns True if given repo name is a valid filesystem repository
1441
1441
1442 :param cls:
1442 :param cls:
1443 :param repo_name:
1443 :param repo_name:
1444 """
1444 """
1445 from rhodecode.lib.utils import is_valid_repo
1445 from rhodecode.lib.utils import is_valid_repo
1446
1446
1447 return is_valid_repo(repo_name, cls.base_path())
1447 return is_valid_repo(repo_name, cls.base_path())
1448
1448
1449 @classmethod
1449 @classmethod
1450 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1450 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1451 case_insensitive=True):
1451 case_insensitive=True):
1452 q = Repository.query()
1452 q = Repository.query()
1453
1453
1454 if not isinstance(user_id, Optional):
1454 if not isinstance(user_id, Optional):
1455 q = q.filter(Repository.user_id == user_id)
1455 q = q.filter(Repository.user_id == user_id)
1456
1456
1457 if not isinstance(group_id, Optional):
1457 if not isinstance(group_id, Optional):
1458 q = q.filter(Repository.group_id == group_id)
1458 q = q.filter(Repository.group_id == group_id)
1459
1459
1460 if case_insensitive:
1460 if case_insensitive:
1461 q = q.order_by(func.lower(Repository.repo_name))
1461 q = q.order_by(func.lower(Repository.repo_name))
1462 else:
1462 else:
1463 q = q.order_by(Repository.repo_name)
1463 q = q.order_by(Repository.repo_name)
1464 return q.all()
1464 return q.all()
1465
1465
1466 @property
1466 @property
1467 def forks(self):
1467 def forks(self):
1468 """
1468 """
1469 Return forks of this repo
1469 Return forks of this repo
1470 """
1470 """
1471 return Repository.get_repo_forks(self.repo_id)
1471 return Repository.get_repo_forks(self.repo_id)
1472
1472
1473 @property
1473 @property
1474 def parent(self):
1474 def parent(self):
1475 """
1475 """
1476 Returns fork parent
1476 Returns fork parent
1477 """
1477 """
1478 return self.fork
1478 return self.fork
1479
1479
1480 @property
1480 @property
1481 def just_name(self):
1481 def just_name(self):
1482 return self.repo_name.split(self.NAME_SEP)[-1]
1482 return self.repo_name.split(self.NAME_SEP)[-1]
1483
1483
1484 @property
1484 @property
1485 def groups_with_parents(self):
1485 def groups_with_parents(self):
1486 groups = []
1486 groups = []
1487 if self.group is None:
1487 if self.group is None:
1488 return groups
1488 return groups
1489
1489
1490 cur_gr = self.group
1490 cur_gr = self.group
1491 groups.insert(0, cur_gr)
1491 groups.insert(0, cur_gr)
1492 while 1:
1492 while 1:
1493 gr = getattr(cur_gr, 'parent_group', None)
1493 gr = getattr(cur_gr, 'parent_group', None)
1494 cur_gr = cur_gr.parent_group
1494 cur_gr = cur_gr.parent_group
1495 if gr is None:
1495 if gr is None:
1496 break
1496 break
1497 groups.insert(0, gr)
1497 groups.insert(0, gr)
1498
1498
1499 return groups
1499 return groups
1500
1500
1501 @property
1501 @property
1502 def groups_and_repo(self):
1502 def groups_and_repo(self):
1503 return self.groups_with_parents, self
1503 return self.groups_with_parents, self
1504
1504
1505 @LazyProperty
1505 @LazyProperty
1506 def repo_path(self):
1506 def repo_path(self):
1507 """
1507 """
1508 Returns base full path for that repository means where it actually
1508 Returns base full path for that repository means where it actually
1509 exists on a filesystem
1509 exists on a filesystem
1510 """
1510 """
1511 q = Session().query(RhodeCodeUi).filter(
1511 q = Session().query(RhodeCodeUi).filter(
1512 RhodeCodeUi.ui_key == self.NAME_SEP)
1512 RhodeCodeUi.ui_key == self.NAME_SEP)
1513 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1513 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1514 return q.one().ui_value
1514 return q.one().ui_value
1515
1515
1516 @property
1516 @property
1517 def repo_full_path(self):
1517 def repo_full_path(self):
1518 p = [self.repo_path]
1518 p = [self.repo_path]
1519 # we need to split the name by / since this is how we store the
1519 # we need to split the name by / since this is how we store the
1520 # names in the database, but that eventually needs to be converted
1520 # names in the database, but that eventually needs to be converted
1521 # into a valid system path
1521 # into a valid system path
1522 p += self.repo_name.split(self.NAME_SEP)
1522 p += self.repo_name.split(self.NAME_SEP)
1523 return os.path.join(*map(safe_unicode, p))
1523 return os.path.join(*map(safe_unicode, p))
1524
1524
1525 @property
1525 @property
1526 def cache_keys(self):
1526 def cache_keys(self):
1527 """
1527 """
1528 Returns associated cache keys for that repo
1528 Returns associated cache keys for that repo
1529 """
1529 """
1530 return CacheKey.query()\
1530 return CacheKey.query()\
1531 .filter(CacheKey.cache_args == self.repo_name)\
1531 .filter(CacheKey.cache_args == self.repo_name)\
1532 .order_by(CacheKey.cache_key)\
1532 .order_by(CacheKey.cache_key)\
1533 .all()
1533 .all()
1534
1534
1535 def get_new_name(self, repo_name):
1535 def get_new_name(self, repo_name):
1536 """
1536 """
1537 returns new full repository name based on assigned group and new new
1537 returns new full repository name based on assigned group and new new
1538
1538
1539 :param group_name:
1539 :param group_name:
1540 """
1540 """
1541 path_prefix = self.group.full_path_splitted if self.group else []
1541 path_prefix = self.group.full_path_splitted if self.group else []
1542 return self.NAME_SEP.join(path_prefix + [repo_name])
1542 return self.NAME_SEP.join(path_prefix + [repo_name])
1543
1543
1544 @property
1544 @property
1545 def _config(self):
1545 def _config(self):
1546 """
1546 """
1547 Returns db based config object.
1547 Returns db based config object.
1548 """
1548 """
1549 from rhodecode.lib.utils import make_db_config
1549 from rhodecode.lib.utils import make_db_config
1550 return make_db_config(clear_session=False, repo=self)
1550 return make_db_config(clear_session=False, repo=self)
1551
1551
1552 def permissions(self, with_admins=True, with_owner=True):
1552 def permissions(self, with_admins=True, with_owner=True):
1553 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1553 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1554 q = q.options(joinedload(UserRepoToPerm.repository),
1554 q = q.options(joinedload(UserRepoToPerm.repository),
1555 joinedload(UserRepoToPerm.user),
1555 joinedload(UserRepoToPerm.user),
1556 joinedload(UserRepoToPerm.permission),)
1556 joinedload(UserRepoToPerm.permission),)
1557
1557
1558 # get owners and admins and permissions. We do a trick of re-writing
1558 # get owners and admins and permissions. We do a trick of re-writing
1559 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1559 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1560 # has a global reference and changing one object propagates to all
1560 # has a global reference and changing one object propagates to all
1561 # others. This means if admin is also an owner admin_row that change
1561 # others. This means if admin is also an owner admin_row that change
1562 # would propagate to both objects
1562 # would propagate to both objects
1563 perm_rows = []
1563 perm_rows = []
1564 for _usr in q.all():
1564 for _usr in q.all():
1565 usr = AttributeDict(_usr.user.get_dict())
1565 usr = AttributeDict(_usr.user.get_dict())
1566 usr.permission = _usr.permission.permission_name
1566 usr.permission = _usr.permission.permission_name
1567 perm_rows.append(usr)
1567 perm_rows.append(usr)
1568
1568
1569 # filter the perm rows by 'default' first and then sort them by
1569 # filter the perm rows by 'default' first and then sort them by
1570 # admin,write,read,none permissions sorted again alphabetically in
1570 # admin,write,read,none permissions sorted again alphabetically in
1571 # each group
1571 # each group
1572 perm_rows = sorted(perm_rows, key=display_sort)
1572 perm_rows = sorted(perm_rows, key=display_sort)
1573
1573
1574 _admin_perm = 'repository.admin'
1574 _admin_perm = 'repository.admin'
1575 owner_row = []
1575 owner_row = []
1576 if with_owner:
1576 if with_owner:
1577 usr = AttributeDict(self.user.get_dict())
1577 usr = AttributeDict(self.user.get_dict())
1578 usr.owner_row = True
1578 usr.owner_row = True
1579 usr.permission = _admin_perm
1579 usr.permission = _admin_perm
1580 owner_row.append(usr)
1580 owner_row.append(usr)
1581
1581
1582 super_admin_rows = []
1582 super_admin_rows = []
1583 if with_admins:
1583 if with_admins:
1584 for usr in User.get_all_super_admins():
1584 for usr in User.get_all_super_admins():
1585 # if this admin is also owner, don't double the record
1585 # if this admin is also owner, don't double the record
1586 if usr.user_id == owner_row[0].user_id:
1586 if usr.user_id == owner_row[0].user_id:
1587 owner_row[0].admin_row = True
1587 owner_row[0].admin_row = True
1588 else:
1588 else:
1589 usr = AttributeDict(usr.get_dict())
1589 usr = AttributeDict(usr.get_dict())
1590 usr.admin_row = True
1590 usr.admin_row = True
1591 usr.permission = _admin_perm
1591 usr.permission = _admin_perm
1592 super_admin_rows.append(usr)
1592 super_admin_rows.append(usr)
1593
1593
1594 return super_admin_rows + owner_row + perm_rows
1594 return super_admin_rows + owner_row + perm_rows
1595
1595
1596 def permission_user_groups(self):
1596 def permission_user_groups(self):
1597 q = UserGroupRepoToPerm.query().filter(
1597 q = UserGroupRepoToPerm.query().filter(
1598 UserGroupRepoToPerm.repository == self)
1598 UserGroupRepoToPerm.repository == self)
1599 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1599 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1600 joinedload(UserGroupRepoToPerm.users_group),
1600 joinedload(UserGroupRepoToPerm.users_group),
1601 joinedload(UserGroupRepoToPerm.permission),)
1601 joinedload(UserGroupRepoToPerm.permission),)
1602
1602
1603 perm_rows = []
1603 perm_rows = []
1604 for _user_group in q.all():
1604 for _user_group in q.all():
1605 usr = AttributeDict(_user_group.users_group.get_dict())
1605 usr = AttributeDict(_user_group.users_group.get_dict())
1606 usr.permission = _user_group.permission.permission_name
1606 usr.permission = _user_group.permission.permission_name
1607 perm_rows.append(usr)
1607 perm_rows.append(usr)
1608
1608
1609 return perm_rows
1609 return perm_rows
1610
1610
1611 def get_api_data(self, include_secrets=False):
1611 def get_api_data(self, include_secrets=False):
1612 """
1612 """
1613 Common function for generating repo api data
1613 Common function for generating repo api data
1614
1614
1615 :param include_secrets: See :meth:`User.get_api_data`.
1615 :param include_secrets: See :meth:`User.get_api_data`.
1616
1616
1617 """
1617 """
1618 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1618 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1619 # move this methods on models level.
1619 # move this methods on models level.
1620 from rhodecode.model.settings import SettingsModel
1620 from rhodecode.model.settings import SettingsModel
1621
1621
1622 repo = self
1622 repo = self
1623 _user_id, _time, _reason = self.locked
1623 _user_id, _time, _reason = self.locked
1624
1624
1625 data = {
1625 data = {
1626 'repo_id': repo.repo_id,
1626 'repo_id': repo.repo_id,
1627 'repo_name': repo.repo_name,
1627 'repo_name': repo.repo_name,
1628 'repo_type': repo.repo_type,
1628 'repo_type': repo.repo_type,
1629 'clone_uri': repo.clone_uri or '',
1629 'clone_uri': repo.clone_uri or '',
1630 'private': repo.private,
1630 'private': repo.private,
1631 'created_on': repo.created_on,
1631 'created_on': repo.created_on,
1632 'description': repo.description,
1632 'description': repo.description,
1633 'landing_rev': repo.landing_rev,
1633 'landing_rev': repo.landing_rev,
1634 'owner': repo.user.username,
1634 'owner': repo.user.username,
1635 'fork_of': repo.fork.repo_name if repo.fork else None,
1635 'fork_of': repo.fork.repo_name if repo.fork else None,
1636 'enable_statistics': repo.enable_statistics,
1636 'enable_statistics': repo.enable_statistics,
1637 'enable_locking': repo.enable_locking,
1637 'enable_locking': repo.enable_locking,
1638 'enable_downloads': repo.enable_downloads,
1638 'enable_downloads': repo.enable_downloads,
1639 'last_changeset': repo.changeset_cache,
1639 'last_changeset': repo.changeset_cache,
1640 'locked_by': User.get(_user_id).get_api_data(
1640 'locked_by': User.get(_user_id).get_api_data(
1641 include_secrets=include_secrets) if _user_id else None,
1641 include_secrets=include_secrets) if _user_id else None,
1642 'locked_date': time_to_datetime(_time) if _time else None,
1642 'locked_date': time_to_datetime(_time) if _time else None,
1643 'lock_reason': _reason if _reason else None,
1643 'lock_reason': _reason if _reason else None,
1644 }
1644 }
1645
1645
1646 # TODO: mikhail: should be per-repo settings here
1646 # TODO: mikhail: should be per-repo settings here
1647 rc_config = SettingsModel().get_all_settings()
1647 rc_config = SettingsModel().get_all_settings()
1648 repository_fields = str2bool(
1648 repository_fields = str2bool(
1649 rc_config.get('rhodecode_repository_fields'))
1649 rc_config.get('rhodecode_repository_fields'))
1650 if repository_fields:
1650 if repository_fields:
1651 for f in self.extra_fields:
1651 for f in self.extra_fields:
1652 data[f.field_key_prefixed] = f.field_value
1652 data[f.field_key_prefixed] = f.field_value
1653
1653
1654 return data
1654 return data
1655
1655
1656 @classmethod
1656 @classmethod
1657 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1657 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1658 if not lock_time:
1658 if not lock_time:
1659 lock_time = time.time()
1659 lock_time = time.time()
1660 if not lock_reason:
1660 if not lock_reason:
1661 lock_reason = cls.LOCK_AUTOMATIC
1661 lock_reason = cls.LOCK_AUTOMATIC
1662 repo.locked = [user_id, lock_time, lock_reason]
1662 repo.locked = [user_id, lock_time, lock_reason]
1663 Session().add(repo)
1663 Session().add(repo)
1664 Session().commit()
1664 Session().commit()
1665
1665
1666 @classmethod
1666 @classmethod
1667 def unlock(cls, repo):
1667 def unlock(cls, repo):
1668 repo.locked = None
1668 repo.locked = None
1669 Session().add(repo)
1669 Session().add(repo)
1670 Session().commit()
1670 Session().commit()
1671
1671
1672 @classmethod
1672 @classmethod
1673 def getlock(cls, repo):
1673 def getlock(cls, repo):
1674 return repo.locked
1674 return repo.locked
1675
1675
1676 def is_user_lock(self, user_id):
1676 def is_user_lock(self, user_id):
1677 if self.lock[0]:
1677 if self.lock[0]:
1678 lock_user_id = safe_int(self.lock[0])
1678 lock_user_id = safe_int(self.lock[0])
1679 user_id = safe_int(user_id)
1679 user_id = safe_int(user_id)
1680 # both are ints, and they are equal
1680 # both are ints, and they are equal
1681 return all([lock_user_id, user_id]) and lock_user_id == user_id
1681 return all([lock_user_id, user_id]) and lock_user_id == user_id
1682
1682
1683 return False
1683 return False
1684
1684
1685 def get_locking_state(self, action, user_id, only_when_enabled=True):
1685 def get_locking_state(self, action, user_id, only_when_enabled=True):
1686 """
1686 """
1687 Checks locking on this repository, if locking is enabled and lock is
1687 Checks locking on this repository, if locking is enabled and lock is
1688 present returns a tuple of make_lock, locked, locked_by.
1688 present returns a tuple of make_lock, locked, locked_by.
1689 make_lock can have 3 states None (do nothing) True, make lock
1689 make_lock can have 3 states None (do nothing) True, make lock
1690 False release lock, This value is later propagated to hooks, which
1690 False release lock, This value is later propagated to hooks, which
1691 do the locking. Think about this as signals passed to hooks what to do.
1691 do the locking. Think about this as signals passed to hooks what to do.
1692
1692
1693 """
1693 """
1694 # TODO: johbo: This is part of the business logic and should be moved
1694 # TODO: johbo: This is part of the business logic and should be moved
1695 # into the RepositoryModel.
1695 # into the RepositoryModel.
1696
1696
1697 if action not in ('push', 'pull'):
1697 if action not in ('push', 'pull'):
1698 raise ValueError("Invalid action value: %s" % repr(action))
1698 raise ValueError("Invalid action value: %s" % repr(action))
1699
1699
1700 # defines if locked error should be thrown to user
1700 # defines if locked error should be thrown to user
1701 currently_locked = False
1701 currently_locked = False
1702 # defines if new lock should be made, tri-state
1702 # defines if new lock should be made, tri-state
1703 make_lock = None
1703 make_lock = None
1704 repo = self
1704 repo = self
1705 user = User.get(user_id)
1705 user = User.get(user_id)
1706
1706
1707 lock_info = repo.locked
1707 lock_info = repo.locked
1708
1708
1709 if repo and (repo.enable_locking or not only_when_enabled):
1709 if repo and (repo.enable_locking or not only_when_enabled):
1710 if action == 'push':
1710 if action == 'push':
1711 # check if it's already locked !, if it is compare users
1711 # check if it's already locked !, if it is compare users
1712 locked_by_user_id = lock_info[0]
1712 locked_by_user_id = lock_info[0]
1713 if user.user_id == locked_by_user_id:
1713 if user.user_id == locked_by_user_id:
1714 log.debug(
1714 log.debug(
1715 'Got `push` action from user %s, now unlocking', user)
1715 'Got `push` action from user %s, now unlocking', user)
1716 # unlock if we have push from user who locked
1716 # unlock if we have push from user who locked
1717 make_lock = False
1717 make_lock = False
1718 else:
1718 else:
1719 # we're not the same user who locked, ban with
1719 # we're not the same user who locked, ban with
1720 # code defined in settings (default is 423 HTTP Locked) !
1720 # code defined in settings (default is 423 HTTP Locked) !
1721 log.debug('Repo %s is currently locked by %s', repo, user)
1721 log.debug('Repo %s is currently locked by %s', repo, user)
1722 currently_locked = True
1722 currently_locked = True
1723 elif action == 'pull':
1723 elif action == 'pull':
1724 # [0] user [1] date
1724 # [0] user [1] date
1725 if lock_info[0] and lock_info[1]:
1725 if lock_info[0] and lock_info[1]:
1726 log.debug('Repo %s is currently locked by %s', repo, user)
1726 log.debug('Repo %s is currently locked by %s', repo, user)
1727 currently_locked = True
1727 currently_locked = True
1728 else:
1728 else:
1729 log.debug('Setting lock on repo %s by %s', repo, user)
1729 log.debug('Setting lock on repo %s by %s', repo, user)
1730 make_lock = True
1730 make_lock = True
1731
1731
1732 else:
1732 else:
1733 log.debug('Repository %s do not have locking enabled', repo)
1733 log.debug('Repository %s do not have locking enabled', repo)
1734
1734
1735 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1735 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1736 make_lock, currently_locked, lock_info)
1736 make_lock, currently_locked, lock_info)
1737
1737
1738 from rhodecode.lib.auth import HasRepoPermissionAny
1738 from rhodecode.lib.auth import HasRepoPermissionAny
1739 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1739 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1740 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1740 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1741 # if we don't have at least write permission we cannot make a lock
1741 # if we don't have at least write permission we cannot make a lock
1742 log.debug('lock state reset back to FALSE due to lack '
1742 log.debug('lock state reset back to FALSE due to lack '
1743 'of at least read permission')
1743 'of at least read permission')
1744 make_lock = False
1744 make_lock = False
1745
1745
1746 return make_lock, currently_locked, lock_info
1746 return make_lock, currently_locked, lock_info
1747
1747
1748 @property
1748 @property
1749 def last_db_change(self):
1749 def last_db_change(self):
1750 return self.updated_on
1750 return self.updated_on
1751
1751
1752 @property
1752 @property
1753 def clone_uri_hidden(self):
1753 def clone_uri_hidden(self):
1754 clone_uri = self.clone_uri
1754 clone_uri = self.clone_uri
1755 if clone_uri:
1755 if clone_uri:
1756 import urlobject
1756 import urlobject
1757 url_obj = urlobject.URLObject(self.clone_uri)
1757 url_obj = urlobject.URLObject(self.clone_uri)
1758 if url_obj.password:
1758 if url_obj.password:
1759 clone_uri = url_obj.with_password('*****')
1759 clone_uri = url_obj.with_password('*****')
1760 return clone_uri
1760 return clone_uri
1761
1761
1762 def clone_url(self, **override):
1762 def clone_url(self, **override):
1763 qualified_home_url = url('home', qualified=True)
1763 qualified_home_url = url('home', qualified=True)
1764
1764
1765 uri_tmpl = None
1765 uri_tmpl = None
1766 if 'with_id' in override:
1766 if 'with_id' in override:
1767 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1767 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1768 del override['with_id']
1768 del override['with_id']
1769
1769
1770 if 'uri_tmpl' in override:
1770 if 'uri_tmpl' in override:
1771 uri_tmpl = override['uri_tmpl']
1771 uri_tmpl = override['uri_tmpl']
1772 del override['uri_tmpl']
1772 del override['uri_tmpl']
1773
1773
1774 # we didn't override our tmpl from **overrides
1774 # we didn't override our tmpl from **overrides
1775 if not uri_tmpl:
1775 if not uri_tmpl:
1776 uri_tmpl = self.DEFAULT_CLONE_URI
1776 uri_tmpl = self.DEFAULT_CLONE_URI
1777 try:
1777 try:
1778 from pylons import tmpl_context as c
1778 from pylons import tmpl_context as c
1779 uri_tmpl = c.clone_uri_tmpl
1779 uri_tmpl = c.clone_uri_tmpl
1780 except Exception:
1780 except Exception:
1781 # in any case if we call this outside of request context,
1781 # in any case if we call this outside of request context,
1782 # ie, not having tmpl_context set up
1782 # ie, not having tmpl_context set up
1783 pass
1783 pass
1784
1784
1785 return get_clone_url(uri_tmpl=uri_tmpl,
1785 return get_clone_url(uri_tmpl=uri_tmpl,
1786 qualifed_home_url=qualified_home_url,
1786 qualifed_home_url=qualified_home_url,
1787 repo_name=self.repo_name,
1787 repo_name=self.repo_name,
1788 repo_id=self.repo_id, **override)
1788 repo_id=self.repo_id, **override)
1789
1789
1790 def set_state(self, state):
1790 def set_state(self, state):
1791 self.repo_state = state
1791 self.repo_state = state
1792 Session().add(self)
1792 Session().add(self)
1793 #==========================================================================
1793 #==========================================================================
1794 # SCM PROPERTIES
1794 # SCM PROPERTIES
1795 #==========================================================================
1795 #==========================================================================
1796
1796
1797 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1797 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1798 return get_commit_safe(
1798 return get_commit_safe(
1799 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1799 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1800
1800
1801 def get_changeset(self, rev=None, pre_load=None):
1801 def get_changeset(self, rev=None, pre_load=None):
1802 warnings.warn("Use get_commit", DeprecationWarning)
1802 warnings.warn("Use get_commit", DeprecationWarning)
1803 commit_id = None
1803 commit_id = None
1804 commit_idx = None
1804 commit_idx = None
1805 if isinstance(rev, basestring):
1805 if isinstance(rev, basestring):
1806 commit_id = rev
1806 commit_id = rev
1807 else:
1807 else:
1808 commit_idx = rev
1808 commit_idx = rev
1809 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1809 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1810 pre_load=pre_load)
1810 pre_load=pre_load)
1811
1811
1812 def get_landing_commit(self):
1812 def get_landing_commit(self):
1813 """
1813 """
1814 Returns landing commit, or if that doesn't exist returns the tip
1814 Returns landing commit, or if that doesn't exist returns the tip
1815 """
1815 """
1816 _rev_type, _rev = self.landing_rev
1816 _rev_type, _rev = self.landing_rev
1817 commit = self.get_commit(_rev)
1817 commit = self.get_commit(_rev)
1818 if isinstance(commit, EmptyCommit):
1818 if isinstance(commit, EmptyCommit):
1819 return self.get_commit()
1819 return self.get_commit()
1820 return commit
1820 return commit
1821
1821
1822 def update_commit_cache(self, cs_cache=None, config=None):
1822 def update_commit_cache(self, cs_cache=None, config=None):
1823 """
1823 """
1824 Update cache of last changeset for repository, keys should be::
1824 Update cache of last changeset for repository, keys should be::
1825
1825
1826 short_id
1826 short_id
1827 raw_id
1827 raw_id
1828 revision
1828 revision
1829 parents
1829 parents
1830 message
1830 message
1831 date
1831 date
1832 author
1832 author
1833
1833
1834 :param cs_cache:
1834 :param cs_cache:
1835 """
1835 """
1836 from rhodecode.lib.vcs.backends.base import BaseChangeset
1836 from rhodecode.lib.vcs.backends.base import BaseChangeset
1837 if cs_cache is None:
1837 if cs_cache is None:
1838 # use no-cache version here
1838 # use no-cache version here
1839 scm_repo = self.scm_instance(cache=False, config=config)
1839 scm_repo = self.scm_instance(cache=False, config=config)
1840 if scm_repo:
1840 if scm_repo:
1841 cs_cache = scm_repo.get_commit(
1841 cs_cache = scm_repo.get_commit(
1842 pre_load=["author", "date", "message", "parents"])
1842 pre_load=["author", "date", "message", "parents"])
1843 else:
1843 else:
1844 cs_cache = EmptyCommit()
1844 cs_cache = EmptyCommit()
1845
1845
1846 if isinstance(cs_cache, BaseChangeset):
1846 if isinstance(cs_cache, BaseChangeset):
1847 cs_cache = cs_cache.__json__()
1847 cs_cache = cs_cache.__json__()
1848
1848
1849 def is_outdated(new_cs_cache):
1849 def is_outdated(new_cs_cache):
1850 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1850 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1851 return True
1851 return True
1852 return False
1852 return False
1853
1853
1854 # check if we have maybe already latest cached revision
1854 # check if we have maybe already latest cached revision
1855 if is_outdated(cs_cache) or not self.changeset_cache:
1855 if is_outdated(cs_cache) or not self.changeset_cache:
1856 _default = datetime.datetime.fromtimestamp(0)
1856 _default = datetime.datetime.fromtimestamp(0)
1857 last_change = cs_cache.get('date') or _default
1857 last_change = cs_cache.get('date') or _default
1858 log.debug('updated repo %s with new cs cache %s',
1858 log.debug('updated repo %s with new cs cache %s',
1859 self.repo_name, cs_cache)
1859 self.repo_name, cs_cache)
1860 self.updated_on = last_change
1860 self.updated_on = last_change
1861 self.changeset_cache = cs_cache
1861 self.changeset_cache = cs_cache
1862 Session().add(self)
1862 Session().add(self)
1863 Session().commit()
1863 Session().commit()
1864 else:
1864 else:
1865 log.debug('Skipping update_commit_cache for repo:`%s` '
1865 log.debug('Skipping update_commit_cache for repo:`%s` '
1866 'commit already with latest changes', self.repo_name)
1866 'commit already with latest changes', self.repo_name)
1867
1867
1868 @property
1868 @property
1869 def tip(self):
1869 def tip(self):
1870 return self.get_commit('tip')
1870 return self.get_commit('tip')
1871
1871
1872 @property
1872 @property
1873 def author(self):
1873 def author(self):
1874 return self.tip.author
1874 return self.tip.author
1875
1875
1876 @property
1876 @property
1877 def last_change(self):
1877 def last_change(self):
1878 return self.scm_instance().last_change
1878 return self.scm_instance().last_change
1879
1879
1880 def get_comments(self, revisions=None):
1880 def get_comments(self, revisions=None):
1881 """
1881 """
1882 Returns comments for this repository grouped by revisions
1882 Returns comments for this repository grouped by revisions
1883
1883
1884 :param revisions: filter query by revisions only
1884 :param revisions: filter query by revisions only
1885 """
1885 """
1886 cmts = ChangesetComment.query()\
1886 cmts = ChangesetComment.query()\
1887 .filter(ChangesetComment.repo == self)
1887 .filter(ChangesetComment.repo == self)
1888 if revisions:
1888 if revisions:
1889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1890 grouped = collections.defaultdict(list)
1890 grouped = collections.defaultdict(list)
1891 for cmt in cmts.all():
1891 for cmt in cmts.all():
1892 grouped[cmt.revision].append(cmt)
1892 grouped[cmt.revision].append(cmt)
1893 return grouped
1893 return grouped
1894
1894
1895 def statuses(self, revisions=None):
1895 def statuses(self, revisions=None):
1896 """
1896 """
1897 Returns statuses for this repository
1897 Returns statuses for this repository
1898
1898
1899 :param revisions: list of revisions to get statuses for
1899 :param revisions: list of revisions to get statuses for
1900 """
1900 """
1901 statuses = ChangesetStatus.query()\
1901 statuses = ChangesetStatus.query()\
1902 .filter(ChangesetStatus.repo == self)\
1902 .filter(ChangesetStatus.repo == self)\
1903 .filter(ChangesetStatus.version == 0)
1903 .filter(ChangesetStatus.version == 0)
1904
1904
1905 if revisions:
1905 if revisions:
1906 # Try doing the filtering in chunks to avoid hitting limits
1906 # Try doing the filtering in chunks to avoid hitting limits
1907 size = 500
1907 size = 500
1908 status_results = []
1908 status_results = []
1909 for chunk in xrange(0, len(revisions), size):
1909 for chunk in xrange(0, len(revisions), size):
1910 status_results += statuses.filter(
1910 status_results += statuses.filter(
1911 ChangesetStatus.revision.in_(
1911 ChangesetStatus.revision.in_(
1912 revisions[chunk: chunk+size])
1912 revisions[chunk: chunk+size])
1913 ).all()
1913 ).all()
1914 else:
1914 else:
1915 status_results = statuses.all()
1915 status_results = statuses.all()
1916
1916
1917 grouped = {}
1917 grouped = {}
1918
1918
1919 # maybe we have open new pullrequest without a status?
1919 # maybe we have open new pullrequest without a status?
1920 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1920 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1921 status_lbl = ChangesetStatus.get_status_lbl(stat)
1921 status_lbl = ChangesetStatus.get_status_lbl(stat)
1922 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1922 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1923 for rev in pr.revisions:
1923 for rev in pr.revisions:
1924 pr_id = pr.pull_request_id
1924 pr_id = pr.pull_request_id
1925 pr_repo = pr.target_repo.repo_name
1925 pr_repo = pr.target_repo.repo_name
1926 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1926 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1927
1927
1928 for stat in status_results:
1928 for stat in status_results:
1929 pr_id = pr_repo = None
1929 pr_id = pr_repo = None
1930 if stat.pull_request:
1930 if stat.pull_request:
1931 pr_id = stat.pull_request.pull_request_id
1931 pr_id = stat.pull_request.pull_request_id
1932 pr_repo = stat.pull_request.target_repo.repo_name
1932 pr_repo = stat.pull_request.target_repo.repo_name
1933 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1933 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1934 pr_id, pr_repo]
1934 pr_id, pr_repo]
1935 return grouped
1935 return grouped
1936
1936
1937 # ==========================================================================
1937 # ==========================================================================
1938 # SCM CACHE INSTANCE
1938 # SCM CACHE INSTANCE
1939 # ==========================================================================
1939 # ==========================================================================
1940
1940
1941 def scm_instance(self, **kwargs):
1941 def scm_instance(self, **kwargs):
1942 import rhodecode
1942 import rhodecode
1943
1943
1944 # Passing a config will not hit the cache currently only used
1944 # Passing a config will not hit the cache currently only used
1945 # for repo2dbmapper
1945 # for repo2dbmapper
1946 config = kwargs.pop('config', None)
1946 config = kwargs.pop('config', None)
1947 cache = kwargs.pop('cache', None)
1947 cache = kwargs.pop('cache', None)
1948 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1948 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1949 # if cache is NOT defined use default global, else we have a full
1949 # if cache is NOT defined use default global, else we have a full
1950 # control over cache behaviour
1950 # control over cache behaviour
1951 if cache is None and full_cache and not config:
1951 if cache is None and full_cache and not config:
1952 return self._get_instance_cached()
1952 return self._get_instance_cached()
1953 return self._get_instance(cache=bool(cache), config=config)
1953 return self._get_instance(cache=bool(cache), config=config)
1954
1954
1955 def _get_instance_cached(self):
1955 def _get_instance_cached(self):
1956 @cache_region('long_term')
1956 @cache_region('long_term')
1957 def _get_repo(cache_key):
1957 def _get_repo(cache_key):
1958 return self._get_instance()
1958 return self._get_instance()
1959
1959
1960 invalidator_context = CacheKey.repo_context_cache(
1960 invalidator_context = CacheKey.repo_context_cache(
1961 _get_repo, self.repo_name, None)
1961 _get_repo, self.repo_name, None)
1962
1962
1963 with invalidator_context as context:
1963 with invalidator_context as context:
1964 context.invalidate()
1964 context.invalidate()
1965 repo = context.compute()
1965 repo = context.compute()
1966
1966
1967 return repo
1967 return repo
1968
1968
1969 def _get_instance(self, cache=True, config=None):
1969 def _get_instance(self, cache=True, config=None):
1970 repo_full_path = self.repo_full_path
1970 repo_full_path = self.repo_full_path
1971 try:
1971 try:
1972 vcs_alias = get_scm(repo_full_path)[0]
1972 vcs_alias = get_scm(repo_full_path)[0]
1973 log.debug(
1973 log.debug(
1974 'Creating instance of %s repository from %s',
1974 'Creating instance of %s repository from %s',
1975 vcs_alias, repo_full_path)
1975 vcs_alias, repo_full_path)
1976 backend = get_backend(vcs_alias)
1976 backend = get_backend(vcs_alias)
1977 except VCSError:
1977 except VCSError:
1978 log.exception(
1978 log.exception(
1979 'Perhaps this repository is in db and not in '
1979 'Perhaps this repository is in db and not in '
1980 'filesystem run rescan repositories with '
1980 'filesystem run rescan repositories with '
1981 '"destroy old data" option from admin panel')
1981 '"destroy old data" option from admin panel')
1982 return
1982 return
1983
1983
1984 config = config or self._config
1984 config = config or self._config
1985 custom_wire = {
1985 custom_wire = {
1986 'cache': cache # controls the vcs.remote cache
1986 'cache': cache # controls the vcs.remote cache
1987 }
1987 }
1988 repo = backend(
1988 repo = backend(
1989 safe_str(repo_full_path), config=config, create=False,
1989 safe_str(repo_full_path), config=config, create=False,
1990 with_wire=custom_wire)
1990 with_wire=custom_wire)
1991
1991
1992 return repo
1992 return repo
1993
1993
1994 def __json__(self):
1994 def __json__(self):
1995 return {'landing_rev': self.landing_rev}
1995 return {'landing_rev': self.landing_rev}
1996
1996
1997 def get_dict(self):
1997 def get_dict(self):
1998
1998
1999 # Since we transformed `repo_name` to a hybrid property, we need to
1999 # Since we transformed `repo_name` to a hybrid property, we need to
2000 # keep compatibility with the code which uses `repo_name` field.
2000 # keep compatibility with the code which uses `repo_name` field.
2001
2001
2002 result = super(Repository, self).get_dict()
2002 result = super(Repository, self).get_dict()
2003 result['repo_name'] = result.pop('_repo_name', None)
2003 result['repo_name'] = result.pop('_repo_name', None)
2004 return result
2004 return result
2005
2005
2006
2006
2007 class RepoGroup(Base, BaseModel):
2007 class RepoGroup(Base, BaseModel):
2008 __tablename__ = 'groups'
2008 __tablename__ = 'groups'
2009 __table_args__ = (
2009 __table_args__ = (
2010 UniqueConstraint('group_name', 'group_parent_id'),
2010 UniqueConstraint('group_name', 'group_parent_id'),
2011 CheckConstraint('group_id != group_parent_id'),
2011 CheckConstraint('group_id != group_parent_id'),
2012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2013 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2013 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2014 )
2014 )
2015 __mapper_args__ = {'order_by': 'group_name'}
2015 __mapper_args__ = {'order_by': 'group_name'}
2016
2016
2017 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2017 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2018
2018
2019 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2019 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2020 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2020 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2021 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2021 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2022 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2022 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2023 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2023 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2025 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2025 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2026
2026
2027 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2027 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2028 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2028 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2029 parent_group = relationship('RepoGroup', remote_side=group_id)
2029 parent_group = relationship('RepoGroup', remote_side=group_id)
2030 user = relationship('User')
2030 user = relationship('User')
2031
2031
2032 def __init__(self, group_name='', parent_group=None):
2032 def __init__(self, group_name='', parent_group=None):
2033 self.group_name = group_name
2033 self.group_name = group_name
2034 self.parent_group = parent_group
2034 self.parent_group = parent_group
2035
2035
2036 def __unicode__(self):
2036 def __unicode__(self):
2037 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2037 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2038 self.group_name)
2038 self.group_name)
2039
2039
2040 @classmethod
2040 @classmethod
2041 def _generate_choice(cls, repo_group):
2041 def _generate_choice(cls, repo_group):
2042 from webhelpers.html import literal as _literal
2042 from webhelpers.html import literal as _literal
2043 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2043 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2044 return repo_group.group_id, _name(repo_group.full_path_splitted)
2044 return repo_group.group_id, _name(repo_group.full_path_splitted)
2045
2045
2046 @classmethod
2046 @classmethod
2047 def groups_choices(cls, groups=None, show_empty_group=True):
2047 def groups_choices(cls, groups=None, show_empty_group=True):
2048 if not groups:
2048 if not groups:
2049 groups = cls.query().all()
2049 groups = cls.query().all()
2050
2050
2051 repo_groups = []
2051 repo_groups = []
2052 if show_empty_group:
2052 if show_empty_group:
2053 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2053 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2054
2054
2055 repo_groups.extend([cls._generate_choice(x) for x in groups])
2055 repo_groups.extend([cls._generate_choice(x) for x in groups])
2056
2056
2057 repo_groups = sorted(
2057 repo_groups = sorted(
2058 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2058 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2059 return repo_groups
2059 return repo_groups
2060
2060
2061 @classmethod
2061 @classmethod
2062 def url_sep(cls):
2062 def url_sep(cls):
2063 return URL_SEP
2063 return URL_SEP
2064
2064
2065 @classmethod
2065 @classmethod
2066 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2066 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2067 if case_insensitive:
2067 if case_insensitive:
2068 gr = cls.query().filter(func.lower(cls.group_name)
2068 gr = cls.query().filter(func.lower(cls.group_name)
2069 == func.lower(group_name))
2069 == func.lower(group_name))
2070 else:
2070 else:
2071 gr = cls.query().filter(cls.group_name == group_name)
2071 gr = cls.query().filter(cls.group_name == group_name)
2072 if cache:
2072 if cache:
2073 gr = gr.options(FromCache(
2073 gr = gr.options(FromCache(
2074 "sql_cache_short",
2074 "sql_cache_short",
2075 "get_group_%s" % _hash_key(group_name)))
2075 "get_group_%s" % _hash_key(group_name)))
2076 return gr.scalar()
2076 return gr.scalar()
2077
2077
2078 @classmethod
2078 @classmethod
2079 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2079 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2080 case_insensitive=True):
2080 case_insensitive=True):
2081 q = RepoGroup.query()
2081 q = RepoGroup.query()
2082
2082
2083 if not isinstance(user_id, Optional):
2083 if not isinstance(user_id, Optional):
2084 q = q.filter(RepoGroup.user_id == user_id)
2084 q = q.filter(RepoGroup.user_id == user_id)
2085
2085
2086 if not isinstance(group_id, Optional):
2086 if not isinstance(group_id, Optional):
2087 q = q.filter(RepoGroup.group_parent_id == group_id)
2087 q = q.filter(RepoGroup.group_parent_id == group_id)
2088
2088
2089 if case_insensitive:
2089 if case_insensitive:
2090 q = q.order_by(func.lower(RepoGroup.group_name))
2090 q = q.order_by(func.lower(RepoGroup.group_name))
2091 else:
2091 else:
2092 q = q.order_by(RepoGroup.group_name)
2092 q = q.order_by(RepoGroup.group_name)
2093 return q.all()
2093 return q.all()
2094
2094
2095 @property
2095 @property
2096 def parents(self):
2096 def parents(self):
2097 parents_recursion_limit = 10
2097 parents_recursion_limit = 10
2098 groups = []
2098 groups = []
2099 if self.parent_group is None:
2099 if self.parent_group is None:
2100 return groups
2100 return groups
2101 cur_gr = self.parent_group
2101 cur_gr = self.parent_group
2102 groups.insert(0, cur_gr)
2102 groups.insert(0, cur_gr)
2103 cnt = 0
2103 cnt = 0
2104 while 1:
2104 while 1:
2105 cnt += 1
2105 cnt += 1
2106 gr = getattr(cur_gr, 'parent_group', None)
2106 gr = getattr(cur_gr, 'parent_group', None)
2107 cur_gr = cur_gr.parent_group
2107 cur_gr = cur_gr.parent_group
2108 if gr is None:
2108 if gr is None:
2109 break
2109 break
2110 if cnt == parents_recursion_limit:
2110 if cnt == parents_recursion_limit:
2111 # this will prevent accidental infinit loops
2111 # this will prevent accidental infinit loops
2112 log.error(('more than %s parents found for group %s, stopping '
2112 log.error(('more than %s parents found for group %s, stopping '
2113 'recursive parent fetching' % (parents_recursion_limit, self)))
2113 'recursive parent fetching' % (parents_recursion_limit, self)))
2114 break
2114 break
2115
2115
2116 groups.insert(0, gr)
2116 groups.insert(0, gr)
2117 return groups
2117 return groups
2118
2118
2119 @property
2119 @property
2120 def children(self):
2120 def children(self):
2121 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2121 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2122
2122
2123 @property
2123 @property
2124 def name(self):
2124 def name(self):
2125 return self.group_name.split(RepoGroup.url_sep())[-1]
2125 return self.group_name.split(RepoGroup.url_sep())[-1]
2126
2126
2127 @property
2127 @property
2128 def full_path(self):
2128 def full_path(self):
2129 return self.group_name
2129 return self.group_name
2130
2130
2131 @property
2131 @property
2132 def full_path_splitted(self):
2132 def full_path_splitted(self):
2133 return self.group_name.split(RepoGroup.url_sep())
2133 return self.group_name.split(RepoGroup.url_sep())
2134
2134
2135 @property
2135 @property
2136 def repositories(self):
2136 def repositories(self):
2137 return Repository.query()\
2137 return Repository.query()\
2138 .filter(Repository.group == self)\
2138 .filter(Repository.group == self)\
2139 .order_by(Repository.repo_name)
2139 .order_by(Repository.repo_name)
2140
2140
2141 @property
2141 @property
2142 def repositories_recursive_count(self):
2142 def repositories_recursive_count(self):
2143 cnt = self.repositories.count()
2143 cnt = self.repositories.count()
2144
2144
2145 def children_count(group):
2145 def children_count(group):
2146 cnt = 0
2146 cnt = 0
2147 for child in group.children:
2147 for child in group.children:
2148 cnt += child.repositories.count()
2148 cnt += child.repositories.count()
2149 cnt += children_count(child)
2149 cnt += children_count(child)
2150 return cnt
2150 return cnt
2151
2151
2152 return cnt + children_count(self)
2152 return cnt + children_count(self)
2153
2153
2154 def _recursive_objects(self, include_repos=True):
2154 def _recursive_objects(self, include_repos=True):
2155 all_ = []
2155 all_ = []
2156
2156
2157 def _get_members(root_gr):
2157 def _get_members(root_gr):
2158 if include_repos:
2158 if include_repos:
2159 for r in root_gr.repositories:
2159 for r in root_gr.repositories:
2160 all_.append(r)
2160 all_.append(r)
2161 childs = root_gr.children.all()
2161 childs = root_gr.children.all()
2162 if childs:
2162 if childs:
2163 for gr in childs:
2163 for gr in childs:
2164 all_.append(gr)
2164 all_.append(gr)
2165 _get_members(gr)
2165 _get_members(gr)
2166
2166
2167 _get_members(self)
2167 _get_members(self)
2168 return [self] + all_
2168 return [self] + all_
2169
2169
2170 def recursive_groups_and_repos(self):
2170 def recursive_groups_and_repos(self):
2171 """
2171 """
2172 Recursive return all groups, with repositories in those groups
2172 Recursive return all groups, with repositories in those groups
2173 """
2173 """
2174 return self._recursive_objects()
2174 return self._recursive_objects()
2175
2175
2176 def recursive_groups(self):
2176 def recursive_groups(self):
2177 """
2177 """
2178 Returns all children groups for this group including children of children
2178 Returns all children groups for this group including children of children
2179 """
2179 """
2180 return self._recursive_objects(include_repos=False)
2180 return self._recursive_objects(include_repos=False)
2181
2181
2182 def get_new_name(self, group_name):
2182 def get_new_name(self, group_name):
2183 """
2183 """
2184 returns new full group name based on parent and new name
2184 returns new full group name based on parent and new name
2185
2185
2186 :param group_name:
2186 :param group_name:
2187 """
2187 """
2188 path_prefix = (self.parent_group.full_path_splitted if
2188 path_prefix = (self.parent_group.full_path_splitted if
2189 self.parent_group else [])
2189 self.parent_group else [])
2190 return RepoGroup.url_sep().join(path_prefix + [group_name])
2190 return RepoGroup.url_sep().join(path_prefix + [group_name])
2191
2191
2192 def permissions(self, with_admins=True, with_owner=True):
2192 def permissions(self, with_admins=True, with_owner=True):
2193 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2193 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2194 q = q.options(joinedload(UserRepoGroupToPerm.group),
2194 q = q.options(joinedload(UserRepoGroupToPerm.group),
2195 joinedload(UserRepoGroupToPerm.user),
2195 joinedload(UserRepoGroupToPerm.user),
2196 joinedload(UserRepoGroupToPerm.permission),)
2196 joinedload(UserRepoGroupToPerm.permission),)
2197
2197
2198 # get owners and admins and permissions. We do a trick of re-writing
2198 # get owners and admins and permissions. We do a trick of re-writing
2199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2200 # has a global reference and changing one object propagates to all
2200 # has a global reference and changing one object propagates to all
2201 # others. This means if admin is also an owner admin_row that change
2201 # others. This means if admin is also an owner admin_row that change
2202 # would propagate to both objects
2202 # would propagate to both objects
2203 perm_rows = []
2203 perm_rows = []
2204 for _usr in q.all():
2204 for _usr in q.all():
2205 usr = AttributeDict(_usr.user.get_dict())
2205 usr = AttributeDict(_usr.user.get_dict())
2206 usr.permission = _usr.permission.permission_name
2206 usr.permission = _usr.permission.permission_name
2207 perm_rows.append(usr)
2207 perm_rows.append(usr)
2208
2208
2209 # filter the perm rows by 'default' first and then sort them by
2209 # filter the perm rows by 'default' first and then sort them by
2210 # admin,write,read,none permissions sorted again alphabetically in
2210 # admin,write,read,none permissions sorted again alphabetically in
2211 # each group
2211 # each group
2212 perm_rows = sorted(perm_rows, key=display_sort)
2212 perm_rows = sorted(perm_rows, key=display_sort)
2213
2213
2214 _admin_perm = 'group.admin'
2214 _admin_perm = 'group.admin'
2215 owner_row = []
2215 owner_row = []
2216 if with_owner:
2216 if with_owner:
2217 usr = AttributeDict(self.user.get_dict())
2217 usr = AttributeDict(self.user.get_dict())
2218 usr.owner_row = True
2218 usr.owner_row = True
2219 usr.permission = _admin_perm
2219 usr.permission = _admin_perm
2220 owner_row.append(usr)
2220 owner_row.append(usr)
2221
2221
2222 super_admin_rows = []
2222 super_admin_rows = []
2223 if with_admins:
2223 if with_admins:
2224 for usr in User.get_all_super_admins():
2224 for usr in User.get_all_super_admins():
2225 # if this admin is also owner, don't double the record
2225 # if this admin is also owner, don't double the record
2226 if usr.user_id == owner_row[0].user_id:
2226 if usr.user_id == owner_row[0].user_id:
2227 owner_row[0].admin_row = True
2227 owner_row[0].admin_row = True
2228 else:
2228 else:
2229 usr = AttributeDict(usr.get_dict())
2229 usr = AttributeDict(usr.get_dict())
2230 usr.admin_row = True
2230 usr.admin_row = True
2231 usr.permission = _admin_perm
2231 usr.permission = _admin_perm
2232 super_admin_rows.append(usr)
2232 super_admin_rows.append(usr)
2233
2233
2234 return super_admin_rows + owner_row + perm_rows
2234 return super_admin_rows + owner_row + perm_rows
2235
2235
2236 def permission_user_groups(self):
2236 def permission_user_groups(self):
2237 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2237 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2238 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2238 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2239 joinedload(UserGroupRepoGroupToPerm.users_group),
2239 joinedload(UserGroupRepoGroupToPerm.users_group),
2240 joinedload(UserGroupRepoGroupToPerm.permission),)
2240 joinedload(UserGroupRepoGroupToPerm.permission),)
2241
2241
2242 perm_rows = []
2242 perm_rows = []
2243 for _user_group in q.all():
2243 for _user_group in q.all():
2244 usr = AttributeDict(_user_group.users_group.get_dict())
2244 usr = AttributeDict(_user_group.users_group.get_dict())
2245 usr.permission = _user_group.permission.permission_name
2245 usr.permission = _user_group.permission.permission_name
2246 perm_rows.append(usr)
2246 perm_rows.append(usr)
2247
2247
2248 return perm_rows
2248 return perm_rows
2249
2249
2250 def get_api_data(self):
2250 def get_api_data(self):
2251 """
2251 """
2252 Common function for generating api data
2252 Common function for generating api data
2253
2253
2254 """
2254 """
2255 group = self
2255 group = self
2256 data = {
2256 data = {
2257 'group_id': group.group_id,
2257 'group_id': group.group_id,
2258 'group_name': group.group_name,
2258 'group_name': group.group_name,
2259 'group_description': group.group_description,
2259 'group_description': group.group_description,
2260 'parent_group': group.parent_group.group_name if group.parent_group else None,
2260 'parent_group': group.parent_group.group_name if group.parent_group else None,
2261 'repositories': [x.repo_name for x in group.repositories],
2261 'repositories': [x.repo_name for x in group.repositories],
2262 'owner': group.user.username,
2262 'owner': group.user.username,
2263 }
2263 }
2264 return data
2264 return data
2265
2265
2266
2266
2267 class Permission(Base, BaseModel):
2267 class Permission(Base, BaseModel):
2268 __tablename__ = 'permissions'
2268 __tablename__ = 'permissions'
2269 __table_args__ = (
2269 __table_args__ = (
2270 Index('p_perm_name_idx', 'permission_name'),
2270 Index('p_perm_name_idx', 'permission_name'),
2271 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2271 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2272 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2272 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2273 )
2273 )
2274 PERMS = [
2274 PERMS = [
2275 ('hg.admin', _('RhodeCode Super Administrator')),
2275 ('hg.admin', _('RhodeCode Super Administrator')),
2276
2276
2277 ('repository.none', _('Repository no access')),
2277 ('repository.none', _('Repository no access')),
2278 ('repository.read', _('Repository read access')),
2278 ('repository.read', _('Repository read access')),
2279 ('repository.write', _('Repository write access')),
2279 ('repository.write', _('Repository write access')),
2280 ('repository.admin', _('Repository admin access')),
2280 ('repository.admin', _('Repository admin access')),
2281
2281
2282 ('group.none', _('Repository group no access')),
2282 ('group.none', _('Repository group no access')),
2283 ('group.read', _('Repository group read access')),
2283 ('group.read', _('Repository group read access')),
2284 ('group.write', _('Repository group write access')),
2284 ('group.write', _('Repository group write access')),
2285 ('group.admin', _('Repository group admin access')),
2285 ('group.admin', _('Repository group admin access')),
2286
2286
2287 ('usergroup.none', _('User group no access')),
2287 ('usergroup.none', _('User group no access')),
2288 ('usergroup.read', _('User group read access')),
2288 ('usergroup.read', _('User group read access')),
2289 ('usergroup.write', _('User group write access')),
2289 ('usergroup.write', _('User group write access')),
2290 ('usergroup.admin', _('User group admin access')),
2290 ('usergroup.admin', _('User group admin access')),
2291
2291
2292 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2292 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2293 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2293 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2294
2294
2295 ('hg.usergroup.create.false', _('User Group creation disabled')),
2295 ('hg.usergroup.create.false', _('User Group creation disabled')),
2296 ('hg.usergroup.create.true', _('User Group creation enabled')),
2296 ('hg.usergroup.create.true', _('User Group creation enabled')),
2297
2297
2298 ('hg.create.none', _('Repository creation disabled')),
2298 ('hg.create.none', _('Repository creation disabled')),
2299 ('hg.create.repository', _('Repository creation enabled')),
2299 ('hg.create.repository', _('Repository creation enabled')),
2300 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2300 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2301 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2301 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2302
2302
2303 ('hg.fork.none', _('Repository forking disabled')),
2303 ('hg.fork.none', _('Repository forking disabled')),
2304 ('hg.fork.repository', _('Repository forking enabled')),
2304 ('hg.fork.repository', _('Repository forking enabled')),
2305
2305
2306 ('hg.register.none', _('Registration disabled')),
2306 ('hg.register.none', _('Registration disabled')),
2307 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2307 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2308 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2308 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2309
2309
2310 ('hg.extern_activate.manual', _('Manual activation of external account')),
2310 ('hg.extern_activate.manual', _('Manual activation of external account')),
2311 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2311 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2312
2312
2313 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2313 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2314 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2314 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2315 ]
2315 ]
2316
2316
2317 # definition of system default permissions for DEFAULT user
2317 # definition of system default permissions for DEFAULT user
2318 DEFAULT_USER_PERMISSIONS = [
2318 DEFAULT_USER_PERMISSIONS = [
2319 'repository.read',
2319 'repository.read',
2320 'group.read',
2320 'group.read',
2321 'usergroup.read',
2321 'usergroup.read',
2322 'hg.create.repository',
2322 'hg.create.repository',
2323 'hg.repogroup.create.false',
2323 'hg.repogroup.create.false',
2324 'hg.usergroup.create.false',
2324 'hg.usergroup.create.false',
2325 'hg.create.write_on_repogroup.true',
2325 'hg.create.write_on_repogroup.true',
2326 'hg.fork.repository',
2326 'hg.fork.repository',
2327 'hg.register.manual_activate',
2327 'hg.register.manual_activate',
2328 'hg.extern_activate.auto',
2328 'hg.extern_activate.auto',
2329 'hg.inherit_default_perms.true',
2329 'hg.inherit_default_perms.true',
2330 ]
2330 ]
2331
2331
2332 # defines which permissions are more important higher the more important
2332 # defines which permissions are more important higher the more important
2333 # Weight defines which permissions are more important.
2333 # Weight defines which permissions are more important.
2334 # The higher number the more important.
2334 # The higher number the more important.
2335 PERM_WEIGHTS = {
2335 PERM_WEIGHTS = {
2336 'repository.none': 0,
2336 'repository.none': 0,
2337 'repository.read': 1,
2337 'repository.read': 1,
2338 'repository.write': 3,
2338 'repository.write': 3,
2339 'repository.admin': 4,
2339 'repository.admin': 4,
2340
2340
2341 'group.none': 0,
2341 'group.none': 0,
2342 'group.read': 1,
2342 'group.read': 1,
2343 'group.write': 3,
2343 'group.write': 3,
2344 'group.admin': 4,
2344 'group.admin': 4,
2345
2345
2346 'usergroup.none': 0,
2346 'usergroup.none': 0,
2347 'usergroup.read': 1,
2347 'usergroup.read': 1,
2348 'usergroup.write': 3,
2348 'usergroup.write': 3,
2349 'usergroup.admin': 4,
2349 'usergroup.admin': 4,
2350
2350
2351 'hg.repogroup.create.false': 0,
2351 'hg.repogroup.create.false': 0,
2352 'hg.repogroup.create.true': 1,
2352 'hg.repogroup.create.true': 1,
2353
2353
2354 'hg.usergroup.create.false': 0,
2354 'hg.usergroup.create.false': 0,
2355 'hg.usergroup.create.true': 1,
2355 'hg.usergroup.create.true': 1,
2356
2356
2357 'hg.fork.none': 0,
2357 'hg.fork.none': 0,
2358 'hg.fork.repository': 1,
2358 'hg.fork.repository': 1,
2359 'hg.create.none': 0,
2359 'hg.create.none': 0,
2360 'hg.create.repository': 1
2360 'hg.create.repository': 1
2361 }
2361 }
2362
2362
2363 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2363 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2364 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2364 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2365 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2365 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2366
2366
2367 def __unicode__(self):
2367 def __unicode__(self):
2368 return u"<%s('%s:%s')>" % (
2368 return u"<%s('%s:%s')>" % (
2369 self.__class__.__name__, self.permission_id, self.permission_name
2369 self.__class__.__name__, self.permission_id, self.permission_name
2370 )
2370 )
2371
2371
2372 @classmethod
2372 @classmethod
2373 def get_by_key(cls, key):
2373 def get_by_key(cls, key):
2374 return cls.query().filter(cls.permission_name == key).scalar()
2374 return cls.query().filter(cls.permission_name == key).scalar()
2375
2375
2376 @classmethod
2376 @classmethod
2377 def get_default_repo_perms(cls, user_id, repo_id=None):
2377 def get_default_repo_perms(cls, user_id, repo_id=None):
2378 q = Session().query(UserRepoToPerm, Repository, Permission)\
2378 q = Session().query(UserRepoToPerm, Repository, Permission)\
2379 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2379 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2380 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2380 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2381 .filter(UserRepoToPerm.user_id == user_id)
2381 .filter(UserRepoToPerm.user_id == user_id)
2382 if repo_id:
2382 if repo_id:
2383 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2383 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2384 return q.all()
2384 return q.all()
2385
2385
2386 @classmethod
2386 @classmethod
2387 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2387 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2388 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2388 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2389 .join(
2389 .join(
2390 Permission,
2390 Permission,
2391 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2391 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2392 .join(
2392 .join(
2393 Repository,
2393 Repository,
2394 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2394 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2395 .join(
2395 .join(
2396 UserGroup,
2396 UserGroup,
2397 UserGroupRepoToPerm.users_group_id ==
2397 UserGroupRepoToPerm.users_group_id ==
2398 UserGroup.users_group_id)\
2398 UserGroup.users_group_id)\
2399 .join(
2399 .join(
2400 UserGroupMember,
2400 UserGroupMember,
2401 UserGroupRepoToPerm.users_group_id ==
2401 UserGroupRepoToPerm.users_group_id ==
2402 UserGroupMember.users_group_id)\
2402 UserGroupMember.users_group_id)\
2403 .filter(
2403 .filter(
2404 UserGroupMember.user_id == user_id,
2404 UserGroupMember.user_id == user_id,
2405 UserGroup.users_group_active == true())
2405 UserGroup.users_group_active == true())
2406 if repo_id:
2406 if repo_id:
2407 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2407 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2408 return q.all()
2408 return q.all()
2409
2409
2410 @classmethod
2410 @classmethod
2411 def get_default_group_perms(cls, user_id, repo_group_id=None):
2411 def get_default_group_perms(cls, user_id, repo_group_id=None):
2412 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2412 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2413 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2413 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2414 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2414 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2415 .filter(UserRepoGroupToPerm.user_id == user_id)
2415 .filter(UserRepoGroupToPerm.user_id == user_id)
2416 if repo_group_id:
2416 if repo_group_id:
2417 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2417 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2418 return q.all()
2418 return q.all()
2419
2419
2420 @classmethod
2420 @classmethod
2421 def get_default_group_perms_from_user_group(
2421 def get_default_group_perms_from_user_group(
2422 cls, user_id, repo_group_id=None):
2422 cls, user_id, repo_group_id=None):
2423 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2423 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2424 .join(
2424 .join(
2425 Permission,
2425 Permission,
2426 UserGroupRepoGroupToPerm.permission_id ==
2426 UserGroupRepoGroupToPerm.permission_id ==
2427 Permission.permission_id)\
2427 Permission.permission_id)\
2428 .join(
2428 .join(
2429 RepoGroup,
2429 RepoGroup,
2430 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2430 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2431 .join(
2431 .join(
2432 UserGroup,
2432 UserGroup,
2433 UserGroupRepoGroupToPerm.users_group_id ==
2433 UserGroupRepoGroupToPerm.users_group_id ==
2434 UserGroup.users_group_id)\
2434 UserGroup.users_group_id)\
2435 .join(
2435 .join(
2436 UserGroupMember,
2436 UserGroupMember,
2437 UserGroupRepoGroupToPerm.users_group_id ==
2437 UserGroupRepoGroupToPerm.users_group_id ==
2438 UserGroupMember.users_group_id)\
2438 UserGroupMember.users_group_id)\
2439 .filter(
2439 .filter(
2440 UserGroupMember.user_id == user_id,
2440 UserGroupMember.user_id == user_id,
2441 UserGroup.users_group_active == true())
2441 UserGroup.users_group_active == true())
2442 if repo_group_id:
2442 if repo_group_id:
2443 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2443 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2444 return q.all()
2444 return q.all()
2445
2445
2446 @classmethod
2446 @classmethod
2447 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2447 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2448 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2448 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2449 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2449 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2450 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2450 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2451 .filter(UserUserGroupToPerm.user_id == user_id)
2451 .filter(UserUserGroupToPerm.user_id == user_id)
2452 if user_group_id:
2452 if user_group_id:
2453 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2453 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2454 return q.all()
2454 return q.all()
2455
2455
2456 @classmethod
2456 @classmethod
2457 def get_default_user_group_perms_from_user_group(
2457 def get_default_user_group_perms_from_user_group(
2458 cls, user_id, user_group_id=None):
2458 cls, user_id, user_group_id=None):
2459 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2459 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2460 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2460 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2461 .join(
2461 .join(
2462 Permission,
2462 Permission,
2463 UserGroupUserGroupToPerm.permission_id ==
2463 UserGroupUserGroupToPerm.permission_id ==
2464 Permission.permission_id)\
2464 Permission.permission_id)\
2465 .join(
2465 .join(
2466 TargetUserGroup,
2466 TargetUserGroup,
2467 UserGroupUserGroupToPerm.target_user_group_id ==
2467 UserGroupUserGroupToPerm.target_user_group_id ==
2468 TargetUserGroup.users_group_id)\
2468 TargetUserGroup.users_group_id)\
2469 .join(
2469 .join(
2470 UserGroup,
2470 UserGroup,
2471 UserGroupUserGroupToPerm.user_group_id ==
2471 UserGroupUserGroupToPerm.user_group_id ==
2472 UserGroup.users_group_id)\
2472 UserGroup.users_group_id)\
2473 .join(
2473 .join(
2474 UserGroupMember,
2474 UserGroupMember,
2475 UserGroupUserGroupToPerm.user_group_id ==
2475 UserGroupUserGroupToPerm.user_group_id ==
2476 UserGroupMember.users_group_id)\
2476 UserGroupMember.users_group_id)\
2477 .filter(
2477 .filter(
2478 UserGroupMember.user_id == user_id,
2478 UserGroupMember.user_id == user_id,
2479 UserGroup.users_group_active == true())
2479 UserGroup.users_group_active == true())
2480 if user_group_id:
2480 if user_group_id:
2481 q = q.filter(
2481 q = q.filter(
2482 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2482 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2483
2483
2484 return q.all()
2484 return q.all()
2485
2485
2486
2486
2487 class UserRepoToPerm(Base, BaseModel):
2487 class UserRepoToPerm(Base, BaseModel):
2488 __tablename__ = 'repo_to_perm'
2488 __tablename__ = 'repo_to_perm'
2489 __table_args__ = (
2489 __table_args__ = (
2490 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2490 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2491 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2491 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2492 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2492 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2493 )
2493 )
2494 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2494 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2495 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2495 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2496 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2496 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2497 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2497 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2498
2498
2499 user = relationship('User')
2499 user = relationship('User')
2500 repository = relationship('Repository')
2500 repository = relationship('Repository')
2501 permission = relationship('Permission')
2501 permission = relationship('Permission')
2502
2502
2503 @classmethod
2503 @classmethod
2504 def create(cls, user, repository, permission):
2504 def create(cls, user, repository, permission):
2505 n = cls()
2505 n = cls()
2506 n.user = user
2506 n.user = user
2507 n.repository = repository
2507 n.repository = repository
2508 n.permission = permission
2508 n.permission = permission
2509 Session().add(n)
2509 Session().add(n)
2510 return n
2510 return n
2511
2511
2512 def __unicode__(self):
2512 def __unicode__(self):
2513 return u'<%s => %s >' % (self.user, self.repository)
2513 return u'<%s => %s >' % (self.user, self.repository)
2514
2514
2515
2515
2516 class UserUserGroupToPerm(Base, BaseModel):
2516 class UserUserGroupToPerm(Base, BaseModel):
2517 __tablename__ = 'user_user_group_to_perm'
2517 __tablename__ = 'user_user_group_to_perm'
2518 __table_args__ = (
2518 __table_args__ = (
2519 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2519 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2521 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2521 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2522 )
2522 )
2523 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2523 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2524 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2524 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2525 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2525 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2526 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2526 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2527
2527
2528 user = relationship('User')
2528 user = relationship('User')
2529 user_group = relationship('UserGroup')
2529 user_group = relationship('UserGroup')
2530 permission = relationship('Permission')
2530 permission = relationship('Permission')
2531
2531
2532 @classmethod
2532 @classmethod
2533 def create(cls, user, user_group, permission):
2533 def create(cls, user, user_group, permission):
2534 n = cls()
2534 n = cls()
2535 n.user = user
2535 n.user = user
2536 n.user_group = user_group
2536 n.user_group = user_group
2537 n.permission = permission
2537 n.permission = permission
2538 Session().add(n)
2538 Session().add(n)
2539 return n
2539 return n
2540
2540
2541 def __unicode__(self):
2541 def __unicode__(self):
2542 return u'<%s => %s >' % (self.user, self.user_group)
2542 return u'<%s => %s >' % (self.user, self.user_group)
2543
2543
2544
2544
2545 class UserToPerm(Base, BaseModel):
2545 class UserToPerm(Base, BaseModel):
2546 __tablename__ = 'user_to_perm'
2546 __tablename__ = 'user_to_perm'
2547 __table_args__ = (
2547 __table_args__ = (
2548 UniqueConstraint('user_id', 'permission_id'),
2548 UniqueConstraint('user_id', 'permission_id'),
2549 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2549 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2550 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2550 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2551 )
2551 )
2552 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2552 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2553 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2553 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2554 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2554 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2555
2555
2556 user = relationship('User')
2556 user = relationship('User')
2557 permission = relationship('Permission', lazy='joined')
2557 permission = relationship('Permission', lazy='joined')
2558
2558
2559 def __unicode__(self):
2559 def __unicode__(self):
2560 return u'<%s => %s >' % (self.user, self.permission)
2560 return u'<%s => %s >' % (self.user, self.permission)
2561
2561
2562
2562
2563 class UserGroupRepoToPerm(Base, BaseModel):
2563 class UserGroupRepoToPerm(Base, BaseModel):
2564 __tablename__ = 'users_group_repo_to_perm'
2564 __tablename__ = 'users_group_repo_to_perm'
2565 __table_args__ = (
2565 __table_args__ = (
2566 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2566 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2569 )
2569 )
2570 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2570 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2571 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2571 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2573 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2573 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2574
2574
2575 users_group = relationship('UserGroup')
2575 users_group = relationship('UserGroup')
2576 permission = relationship('Permission')
2576 permission = relationship('Permission')
2577 repository = relationship('Repository')
2577 repository = relationship('Repository')
2578
2578
2579 @classmethod
2579 @classmethod
2580 def create(cls, users_group, repository, permission):
2580 def create(cls, users_group, repository, permission):
2581 n = cls()
2581 n = cls()
2582 n.users_group = users_group
2582 n.users_group = users_group
2583 n.repository = repository
2583 n.repository = repository
2584 n.permission = permission
2584 n.permission = permission
2585 Session().add(n)
2585 Session().add(n)
2586 return n
2586 return n
2587
2587
2588 def __unicode__(self):
2588 def __unicode__(self):
2589 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2589 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2590
2590
2591
2591
2592 class UserGroupUserGroupToPerm(Base, BaseModel):
2592 class UserGroupUserGroupToPerm(Base, BaseModel):
2593 __tablename__ = 'user_group_user_group_to_perm'
2593 __tablename__ = 'user_group_user_group_to_perm'
2594 __table_args__ = (
2594 __table_args__ = (
2595 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2595 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2596 CheckConstraint('target_user_group_id != user_group_id'),
2596 CheckConstraint('target_user_group_id != user_group_id'),
2597 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2597 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2598 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2598 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2599 )
2599 )
2600 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)
2600 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)
2601 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2601 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2602 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2602 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2603 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2603 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2604
2604
2605 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2605 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2606 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2606 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2607 permission = relationship('Permission')
2607 permission = relationship('Permission')
2608
2608
2609 @classmethod
2609 @classmethod
2610 def create(cls, target_user_group, user_group, permission):
2610 def create(cls, target_user_group, user_group, permission):
2611 n = cls()
2611 n = cls()
2612 n.target_user_group = target_user_group
2612 n.target_user_group = target_user_group
2613 n.user_group = user_group
2613 n.user_group = user_group
2614 n.permission = permission
2614 n.permission = permission
2615 Session().add(n)
2615 Session().add(n)
2616 return n
2616 return n
2617
2617
2618 def __unicode__(self):
2618 def __unicode__(self):
2619 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2619 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2620
2620
2621
2621
2622 class UserGroupToPerm(Base, BaseModel):
2622 class UserGroupToPerm(Base, BaseModel):
2623 __tablename__ = 'users_group_to_perm'
2623 __tablename__ = 'users_group_to_perm'
2624 __table_args__ = (
2624 __table_args__ = (
2625 UniqueConstraint('users_group_id', 'permission_id',),
2625 UniqueConstraint('users_group_id', 'permission_id',),
2626 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2626 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2627 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2627 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2628 )
2628 )
2629 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2629 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2630 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2630 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2631 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2631 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2632
2632
2633 users_group = relationship('UserGroup')
2633 users_group = relationship('UserGroup')
2634 permission = relationship('Permission')
2634 permission = relationship('Permission')
2635
2635
2636
2636
2637 class UserRepoGroupToPerm(Base, BaseModel):
2637 class UserRepoGroupToPerm(Base, BaseModel):
2638 __tablename__ = 'user_repo_group_to_perm'
2638 __tablename__ = 'user_repo_group_to_perm'
2639 __table_args__ = (
2639 __table_args__ = (
2640 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2640 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 )
2643 )
2644
2644
2645 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2646 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2646 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2647 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2647 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2648 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2648 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2649
2649
2650 user = relationship('User')
2650 user = relationship('User')
2651 group = relationship('RepoGroup')
2651 group = relationship('RepoGroup')
2652 permission = relationship('Permission')
2652 permission = relationship('Permission')
2653
2653
2654 @classmethod
2654 @classmethod
2655 def create(cls, user, repository_group, permission):
2655 def create(cls, user, repository_group, permission):
2656 n = cls()
2656 n = cls()
2657 n.user = user
2657 n.user = user
2658 n.group = repository_group
2658 n.group = repository_group
2659 n.permission = permission
2659 n.permission = permission
2660 Session().add(n)
2660 Session().add(n)
2661 return n
2661 return n
2662
2662
2663
2663
2664 class UserGroupRepoGroupToPerm(Base, BaseModel):
2664 class UserGroupRepoGroupToPerm(Base, BaseModel):
2665 __tablename__ = 'users_group_repo_group_to_perm'
2665 __tablename__ = 'users_group_repo_group_to_perm'
2666 __table_args__ = (
2666 __table_args__ = (
2667 UniqueConstraint('users_group_id', 'group_id'),
2667 UniqueConstraint('users_group_id', 'group_id'),
2668 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2668 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2669 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2669 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2670 )
2670 )
2671
2671
2672 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)
2672 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)
2673 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2673 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2674 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2674 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2675 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)
2676
2676
2677 users_group = relationship('UserGroup')
2677 users_group = relationship('UserGroup')
2678 permission = relationship('Permission')
2678 permission = relationship('Permission')
2679 group = relationship('RepoGroup')
2679 group = relationship('RepoGroup')
2680
2680
2681 @classmethod
2681 @classmethod
2682 def create(cls, user_group, repository_group, permission):
2682 def create(cls, user_group, repository_group, permission):
2683 n = cls()
2683 n = cls()
2684 n.users_group = user_group
2684 n.users_group = user_group
2685 n.group = repository_group
2685 n.group = repository_group
2686 n.permission = permission
2686 n.permission = permission
2687 Session().add(n)
2687 Session().add(n)
2688 return n
2688 return n
2689
2689
2690 def __unicode__(self):
2690 def __unicode__(self):
2691 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2691 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2692
2692
2693
2693
2694 class Statistics(Base, BaseModel):
2694 class Statistics(Base, BaseModel):
2695 __tablename__ = 'statistics'
2695 __tablename__ = 'statistics'
2696 __table_args__ = (
2696 __table_args__ = (
2697 UniqueConstraint('repository_id'),
2697 UniqueConstraint('repository_id'),
2698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2699 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2699 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2700 )
2700 )
2701 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2701 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2702 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2702 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2703 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2703 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2704 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2704 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2705 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2705 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2706 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2706 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2707
2707
2708 repository = relationship('Repository', single_parent=True)
2708 repository = relationship('Repository', single_parent=True)
2709
2709
2710
2710
2711 class UserFollowing(Base, BaseModel):
2711 class UserFollowing(Base, BaseModel):
2712 __tablename__ = 'user_followings'
2712 __tablename__ = 'user_followings'
2713 __table_args__ = (
2713 __table_args__ = (
2714 UniqueConstraint('user_id', 'follows_repository_id'),
2714 UniqueConstraint('user_id', 'follows_repository_id'),
2715 UniqueConstraint('user_id', 'follows_user_id'),
2715 UniqueConstraint('user_id', 'follows_user_id'),
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2718 )
2718 )
2719
2719
2720 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2720 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2721 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2721 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2722 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2722 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2723 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2723 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2724 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2724 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2725
2725
2726 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2726 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2727
2727
2728 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2728 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2729 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2729 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2730
2730
2731 @classmethod
2731 @classmethod
2732 def get_repo_followers(cls, repo_id):
2732 def get_repo_followers(cls, repo_id):
2733 return cls.query().filter(cls.follows_repo_id == repo_id)
2733 return cls.query().filter(cls.follows_repo_id == repo_id)
2734
2734
2735
2735
2736 class CacheKey(Base, BaseModel):
2736 class CacheKey(Base, BaseModel):
2737 __tablename__ = 'cache_invalidation'
2737 __tablename__ = 'cache_invalidation'
2738 __table_args__ = (
2738 __table_args__ = (
2739 UniqueConstraint('cache_key'),
2739 UniqueConstraint('cache_key'),
2740 Index('key_idx', 'cache_key'),
2740 Index('key_idx', 'cache_key'),
2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2743 )
2743 )
2744 CACHE_TYPE_ATOM = 'ATOM'
2744 CACHE_TYPE_ATOM = 'ATOM'
2745 CACHE_TYPE_RSS = 'RSS'
2745 CACHE_TYPE_RSS = 'RSS'
2746 CACHE_TYPE_README = 'README'
2746 CACHE_TYPE_README = 'README'
2747
2747
2748 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2748 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2749 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2749 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2750 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2750 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2751 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2751 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2752
2752
2753 def __init__(self, cache_key, cache_args=''):
2753 def __init__(self, cache_key, cache_args=''):
2754 self.cache_key = cache_key
2754 self.cache_key = cache_key
2755 self.cache_args = cache_args
2755 self.cache_args = cache_args
2756 self.cache_active = False
2756 self.cache_active = False
2757
2757
2758 def __unicode__(self):
2758 def __unicode__(self):
2759 return u"<%s('%s:%s[%s]')>" % (
2759 return u"<%s('%s:%s[%s]')>" % (
2760 self.__class__.__name__,
2760 self.__class__.__name__,
2761 self.cache_id, self.cache_key, self.cache_active)
2761 self.cache_id, self.cache_key, self.cache_active)
2762
2762
2763 def _cache_key_partition(self):
2763 def _cache_key_partition(self):
2764 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2764 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2765 return prefix, repo_name, suffix
2765 return prefix, repo_name, suffix
2766
2766
2767 def get_prefix(self):
2767 def get_prefix(self):
2768 """
2768 """
2769 Try to extract prefix from existing cache key. The key could consist
2769 Try to extract prefix from existing cache key. The key could consist
2770 of prefix, repo_name, suffix
2770 of prefix, repo_name, suffix
2771 """
2771 """
2772 # this returns prefix, repo_name, suffix
2772 # this returns prefix, repo_name, suffix
2773 return self._cache_key_partition()[0]
2773 return self._cache_key_partition()[0]
2774
2774
2775 def get_suffix(self):
2775 def get_suffix(self):
2776 """
2776 """
2777 get suffix that might have been used in _get_cache_key to
2777 get suffix that might have been used in _get_cache_key to
2778 generate self.cache_key. Only used for informational purposes
2778 generate self.cache_key. Only used for informational purposes
2779 in repo_edit.html.
2779 in repo_edit.html.
2780 """
2780 """
2781 # prefix, repo_name, suffix
2781 # prefix, repo_name, suffix
2782 return self._cache_key_partition()[2]
2782 return self._cache_key_partition()[2]
2783
2783
2784 @classmethod
2784 @classmethod
2785 def delete_all_cache(cls):
2785 def delete_all_cache(cls):
2786 """
2786 """
2787 Delete all cache keys from database.
2787 Delete all cache keys from database.
2788 Should only be run when all instances are down and all entries
2788 Should only be run when all instances are down and all entries
2789 thus stale.
2789 thus stale.
2790 """
2790 """
2791 cls.query().delete()
2791 cls.query().delete()
2792 Session().commit()
2792 Session().commit()
2793
2793
2794 @classmethod
2794 @classmethod
2795 def get_cache_key(cls, repo_name, cache_type):
2795 def get_cache_key(cls, repo_name, cache_type):
2796 """
2796 """
2797
2797
2798 Generate a cache key for this process of RhodeCode instance.
2798 Generate a cache key for this process of RhodeCode instance.
2799 Prefix most likely will be process id or maybe explicitly set
2799 Prefix most likely will be process id or maybe explicitly set
2800 instance_id from .ini file.
2800 instance_id from .ini file.
2801 """
2801 """
2802 import rhodecode
2802 import rhodecode
2803 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2803 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2804
2804
2805 repo_as_unicode = safe_unicode(repo_name)
2805 repo_as_unicode = safe_unicode(repo_name)
2806 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2806 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2807 if cache_type else repo_as_unicode
2807 if cache_type else repo_as_unicode
2808
2808
2809 return u'{}{}'.format(prefix, key)
2809 return u'{}{}'.format(prefix, key)
2810
2810
2811 @classmethod
2811 @classmethod
2812 def set_invalidate(cls, repo_name, delete=False):
2812 def set_invalidate(cls, repo_name, delete=False):
2813 """
2813 """
2814 Mark all caches of a repo as invalid in the database.
2814 Mark all caches of a repo as invalid in the database.
2815 """
2815 """
2816
2816
2817 try:
2817 try:
2818 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2818 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2819 if delete:
2819 if delete:
2820 log.debug('cache objects deleted for repo %s',
2820 log.debug('cache objects deleted for repo %s',
2821 safe_str(repo_name))
2821 safe_str(repo_name))
2822 qry.delete()
2822 qry.delete()
2823 else:
2823 else:
2824 log.debug('cache objects marked as invalid for repo %s',
2824 log.debug('cache objects marked as invalid for repo %s',
2825 safe_str(repo_name))
2825 safe_str(repo_name))
2826 qry.update({"cache_active": False})
2826 qry.update({"cache_active": False})
2827
2827
2828 Session().commit()
2828 Session().commit()
2829 except Exception:
2829 except Exception:
2830 log.exception(
2830 log.exception(
2831 'Cache key invalidation failed for repository %s',
2831 'Cache key invalidation failed for repository %s',
2832 safe_str(repo_name))
2832 safe_str(repo_name))
2833 Session().rollback()
2833 Session().rollback()
2834
2834
2835 @classmethod
2835 @classmethod
2836 def get_active_cache(cls, cache_key):
2836 def get_active_cache(cls, cache_key):
2837 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2837 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2838 if inv_obj:
2838 if inv_obj:
2839 return inv_obj
2839 return inv_obj
2840 return None
2840 return None
2841
2841
2842 @classmethod
2842 @classmethod
2843 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2843 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2844 """
2844 """
2845 @cache_region('long_term')
2845 @cache_region('long_term')
2846 def _heavy_calculation(cache_key):
2846 def _heavy_calculation(cache_key):
2847 return 'result'
2847 return 'result'
2848
2848
2849 cache_context = CacheKey.repo_context_cache(
2849 cache_context = CacheKey.repo_context_cache(
2850 _heavy_calculation, repo_name, cache_type)
2850 _heavy_calculation, repo_name, cache_type)
2851
2851
2852 with cache_context as context:
2852 with cache_context as context:
2853 context.invalidate()
2853 context.invalidate()
2854 computed = context.compute()
2854 computed = context.compute()
2855
2855
2856 assert computed == 'result'
2856 assert computed == 'result'
2857 """
2857 """
2858 from rhodecode.lib import caches
2858 from rhodecode.lib import caches
2859 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2859 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2860
2860
2861
2861
2862 class ChangesetComment(Base, BaseModel):
2862 class ChangesetComment(Base, BaseModel):
2863 __tablename__ = 'changeset_comments'
2863 __tablename__ = 'changeset_comments'
2864 __table_args__ = (
2864 __table_args__ = (
2865 Index('cc_revision_idx', 'revision'),
2865 Index('cc_revision_idx', 'revision'),
2866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2868 )
2868 )
2869
2869
2870 COMMENT_OUTDATED = u'comment_outdated'
2870 COMMENT_OUTDATED = u'comment_outdated'
2871
2871
2872 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2872 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2873 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2873 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2874 revision = Column('revision', String(40), nullable=True)
2874 revision = Column('revision', String(40), nullable=True)
2875 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2875 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2876 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2876 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2877 line_no = Column('line_no', Unicode(10), nullable=True)
2877 line_no = Column('line_no', Unicode(10), nullable=True)
2878 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2878 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2879 f_path = Column('f_path', Unicode(1000), nullable=True)
2879 f_path = Column('f_path', Unicode(1000), nullable=True)
2880 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2880 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2881 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2881 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2882 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2882 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2883 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2883 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2884 renderer = Column('renderer', Unicode(64), nullable=True)
2884 renderer = Column('renderer', Unicode(64), nullable=True)
2885 display_state = Column('display_state', Unicode(128), nullable=True)
2885 display_state = Column('display_state', Unicode(128), nullable=True)
2886
2886
2887 author = relationship('User', lazy='joined')
2887 author = relationship('User', lazy='joined')
2888 repo = relationship('Repository')
2888 repo = relationship('Repository')
2889 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2889 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2890 pull_request = relationship('PullRequest', lazy='joined')
2890 pull_request = relationship('PullRequest', lazy='joined')
2891 pull_request_version = relationship('PullRequestVersion')
2891 pull_request_version = relationship('PullRequestVersion')
2892
2892
2893 @classmethod
2893 @classmethod
2894 def get_users(cls, revision=None, pull_request_id=None):
2894 def get_users(cls, revision=None, pull_request_id=None):
2895 """
2895 """
2896 Returns user associated with this ChangesetComment. ie those
2896 Returns user associated with this ChangesetComment. ie those
2897 who actually commented
2897 who actually commented
2898
2898
2899 :param cls:
2899 :param cls:
2900 :param revision:
2900 :param revision:
2901 """
2901 """
2902 q = Session().query(User)\
2902 q = Session().query(User)\
2903 .join(ChangesetComment.author)
2903 .join(ChangesetComment.author)
2904 if revision:
2904 if revision:
2905 q = q.filter(cls.revision == revision)
2905 q = q.filter(cls.revision == revision)
2906 elif pull_request_id:
2906 elif pull_request_id:
2907 q = q.filter(cls.pull_request_id == pull_request_id)
2907 q = q.filter(cls.pull_request_id == pull_request_id)
2908 return q.all()
2908 return q.all()
2909
2909
2910 def render(self, mentions=False):
2910 def render(self, mentions=False):
2911 from rhodecode.lib import helpers as h
2911 from rhodecode.lib import helpers as h
2912 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2912 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2913
2913
2914 def __repr__(self):
2914 def __repr__(self):
2915 if self.comment_id:
2915 if self.comment_id:
2916 return '<DB:ChangesetComment #%s>' % self.comment_id
2916 return '<DB:ChangesetComment #%s>' % self.comment_id
2917 else:
2917 else:
2918 return '<DB:ChangesetComment at %#x>' % id(self)
2918 return '<DB:ChangesetComment at %#x>' % id(self)
2919
2919
2920
2920
2921 class ChangesetStatus(Base, BaseModel):
2921 class ChangesetStatus(Base, BaseModel):
2922 __tablename__ = 'changeset_statuses'
2922 __tablename__ = 'changeset_statuses'
2923 __table_args__ = (
2923 __table_args__ = (
2924 Index('cs_revision_idx', 'revision'),
2924 Index('cs_revision_idx', 'revision'),
2925 Index('cs_version_idx', 'version'),
2925 Index('cs_version_idx', 'version'),
2926 UniqueConstraint('repo_id', 'revision', 'version'),
2926 UniqueConstraint('repo_id', 'revision', 'version'),
2927 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2927 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2928 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2928 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2929 )
2929 )
2930 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2930 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2931 STATUS_APPROVED = 'approved'
2931 STATUS_APPROVED = 'approved'
2932 STATUS_REJECTED = 'rejected'
2932 STATUS_REJECTED = 'rejected'
2933 STATUS_UNDER_REVIEW = 'under_review'
2933 STATUS_UNDER_REVIEW = 'under_review'
2934
2934
2935 STATUSES = [
2935 STATUSES = [
2936 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2936 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2937 (STATUS_APPROVED, _("Approved")),
2937 (STATUS_APPROVED, _("Approved")),
2938 (STATUS_REJECTED, _("Rejected")),
2938 (STATUS_REJECTED, _("Rejected")),
2939 (STATUS_UNDER_REVIEW, _("Under Review")),
2939 (STATUS_UNDER_REVIEW, _("Under Review")),
2940 ]
2940 ]
2941
2941
2942 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2942 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2943 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2943 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2944 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2944 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2945 revision = Column('revision', String(40), nullable=False)
2945 revision = Column('revision', String(40), nullable=False)
2946 status = Column('status', String(128), nullable=False, default=DEFAULT)
2946 status = Column('status', String(128), nullable=False, default=DEFAULT)
2947 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2947 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2948 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2948 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2949 version = Column('version', Integer(), nullable=False, default=0)
2949 version = Column('version', Integer(), nullable=False, default=0)
2950 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2950 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2951
2951
2952 author = relationship('User', lazy='joined')
2952 author = relationship('User', lazy='joined')
2953 repo = relationship('Repository')
2953 repo = relationship('Repository')
2954 comment = relationship('ChangesetComment', lazy='joined')
2954 comment = relationship('ChangesetComment', lazy='joined')
2955 pull_request = relationship('PullRequest', lazy='joined')
2955 pull_request = relationship('PullRequest', lazy='joined')
2956
2956
2957 def __unicode__(self):
2957 def __unicode__(self):
2958 return u"<%s('%s[%s]:%s')>" % (
2958 return u"<%s('%s[%s]:%s')>" % (
2959 self.__class__.__name__,
2959 self.__class__.__name__,
2960 self.status, self.version, self.author
2960 self.status, self.version, self.author
2961 )
2961 )
2962
2962
2963 @classmethod
2963 @classmethod
2964 def get_status_lbl(cls, value):
2964 def get_status_lbl(cls, value):
2965 return dict(cls.STATUSES).get(value)
2965 return dict(cls.STATUSES).get(value)
2966
2966
2967 @property
2967 @property
2968 def status_lbl(self):
2968 def status_lbl(self):
2969 return ChangesetStatus.get_status_lbl(self.status)
2969 return ChangesetStatus.get_status_lbl(self.status)
2970
2970
2971
2971
2972 class _PullRequestBase(BaseModel):
2972 class _PullRequestBase(BaseModel):
2973 """
2973 """
2974 Common attributes of pull request and version entries.
2974 Common attributes of pull request and version entries.
2975 """
2975 """
2976
2976
2977 # .status values
2977 # .status values
2978 STATUS_NEW = u'new'
2978 STATUS_NEW = u'new'
2979 STATUS_OPEN = u'open'
2979 STATUS_OPEN = u'open'
2980 STATUS_CLOSED = u'closed'
2980 STATUS_CLOSED = u'closed'
2981
2981
2982 title = Column('title', Unicode(255), nullable=True)
2982 title = Column('title', Unicode(255), nullable=True)
2983 description = Column(
2983 description = Column(
2984 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2984 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2985 nullable=True)
2985 nullable=True)
2986 # new/open/closed status of pull request (not approve/reject/etc)
2986 # new/open/closed status of pull request (not approve/reject/etc)
2987 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2987 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2988 created_on = Column(
2988 created_on = Column(
2989 'created_on', DateTime(timezone=False), nullable=False,
2989 'created_on', DateTime(timezone=False), nullable=False,
2990 default=datetime.datetime.now)
2990 default=datetime.datetime.now)
2991 updated_on = Column(
2991 updated_on = Column(
2992 'updated_on', DateTime(timezone=False), nullable=False,
2992 'updated_on', DateTime(timezone=False), nullable=False,
2993 default=datetime.datetime.now)
2993 default=datetime.datetime.now)
2994
2994
2995 @declared_attr
2995 @declared_attr
2996 def user_id(cls):
2996 def user_id(cls):
2997 return Column(
2997 return Column(
2998 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2998 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2999 unique=None)
2999 unique=None)
3000
3000
3001 # 500 revisions max
3001 # 500 revisions max
3002 _revisions = Column(
3002 _revisions = Column(
3003 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3003 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3004
3004
3005 @declared_attr
3005 @declared_attr
3006 def source_repo_id(cls):
3006 def source_repo_id(cls):
3007 # TODO: dan: rename column to source_repo_id
3007 # TODO: dan: rename column to source_repo_id
3008 return Column(
3008 return Column(
3009 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3009 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3010 nullable=False)
3010 nullable=False)
3011
3011
3012 source_ref = Column('org_ref', Unicode(255), nullable=False)
3012 source_ref = Column('org_ref', Unicode(255), nullable=False)
3013
3013
3014 @declared_attr
3014 @declared_attr
3015 def target_repo_id(cls):
3015 def target_repo_id(cls):
3016 # TODO: dan: rename column to target_repo_id
3016 # TODO: dan: rename column to target_repo_id
3017 return Column(
3017 return Column(
3018 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3018 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3019 nullable=False)
3019 nullable=False)
3020
3020
3021 target_ref = Column('other_ref', Unicode(255), nullable=False)
3021 target_ref = Column('other_ref', Unicode(255), nullable=False)
3022
3022
3023 # TODO: dan: rename column to last_merge_source_rev
3023 # TODO: dan: rename column to last_merge_source_rev
3024 _last_merge_source_rev = Column(
3024 _last_merge_source_rev = Column(
3025 'last_merge_org_rev', String(40), nullable=True)
3025 'last_merge_org_rev', String(40), nullable=True)
3026 # TODO: dan: rename column to last_merge_target_rev
3026 # TODO: dan: rename column to last_merge_target_rev
3027 _last_merge_target_rev = Column(
3027 _last_merge_target_rev = Column(
3028 'last_merge_other_rev', String(40), nullable=True)
3028 'last_merge_other_rev', String(40), nullable=True)
3029 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3029 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3030 merge_rev = Column('merge_rev', String(40), nullable=True)
3030 merge_rev = Column('merge_rev', String(40), nullable=True)
3031
3031
3032 @hybrid_property
3032 @hybrid_property
3033 def revisions(self):
3033 def revisions(self):
3034 return self._revisions.split(':') if self._revisions else []
3034 return self._revisions.split(':') if self._revisions else []
3035
3035
3036 @revisions.setter
3036 @revisions.setter
3037 def revisions(self, val):
3037 def revisions(self, val):
3038 self._revisions = ':'.join(val)
3038 self._revisions = ':'.join(val)
3039
3039
3040 @declared_attr
3040 @declared_attr
3041 def author(cls):
3041 def author(cls):
3042 return relationship('User', lazy='joined')
3042 return relationship('User', lazy='joined')
3043
3043
3044 @declared_attr
3044 @declared_attr
3045 def source_repo(cls):
3045 def source_repo(cls):
3046 return relationship(
3046 return relationship(
3047 'Repository',
3047 'Repository',
3048 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3048 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3049
3049
3050 @property
3050 @property
3051 def source_ref_parts(self):
3051 def source_ref_parts(self):
3052 refs = self.source_ref.split(':')
3052 refs = self.source_ref.split(':')
3053 return Reference(refs[0], refs[1], refs[2])
3053 return Reference(refs[0], refs[1], refs[2])
3054
3054
3055 @declared_attr
3055 @declared_attr
3056 def target_repo(cls):
3056 def target_repo(cls):
3057 return relationship(
3057 return relationship(
3058 'Repository',
3058 'Repository',
3059 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3059 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3060
3060
3061 @property
3061 @property
3062 def target_ref_parts(self):
3062 def target_ref_parts(self):
3063 refs = self.target_ref.split(':')
3063 refs = self.target_ref.split(':')
3064 return Reference(refs[0], refs[1], refs[2])
3064 return Reference(refs[0], refs[1], refs[2])
3065
3065
3066
3066
3067 class PullRequest(Base, _PullRequestBase):
3067 class PullRequest(Base, _PullRequestBase):
3068 __tablename__ = 'pull_requests'
3068 __tablename__ = 'pull_requests'
3069 __table_args__ = (
3069 __table_args__ = (
3070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3072 )
3072 )
3073
3073
3074 pull_request_id = Column(
3074 pull_request_id = Column(
3075 'pull_request_id', Integer(), nullable=False, primary_key=True)
3075 'pull_request_id', Integer(), nullable=False, primary_key=True)
3076
3076
3077 def __repr__(self):
3077 def __repr__(self):
3078 if self.pull_request_id:
3078 if self.pull_request_id:
3079 return '<DB:PullRequest #%s>' % self.pull_request_id
3079 return '<DB:PullRequest #%s>' % self.pull_request_id
3080 else:
3080 else:
3081 return '<DB:PullRequest at %#x>' % id(self)
3081 return '<DB:PullRequest at %#x>' % id(self)
3082
3082
3083 reviewers = relationship('PullRequestReviewers',
3083 reviewers = relationship('PullRequestReviewers',
3084 cascade="all, delete, delete-orphan")
3084 cascade="all, delete, delete-orphan")
3085 statuses = relationship('ChangesetStatus')
3085 statuses = relationship('ChangesetStatus')
3086 comments = relationship('ChangesetComment',
3086 comments = relationship('ChangesetComment',
3087 cascade="all, delete, delete-orphan")
3087 cascade="all, delete, delete-orphan")
3088 versions = relationship('PullRequestVersion',
3088 versions = relationship('PullRequestVersion',
3089 cascade="all, delete, delete-orphan")
3089 cascade="all, delete, delete-orphan")
3090
3090
3091 def is_closed(self):
3091 def is_closed(self):
3092 return self.status == self.STATUS_CLOSED
3092 return self.status == self.STATUS_CLOSED
3093
3093
3094 def get_api_data(self):
3094 def get_api_data(self):
3095 from rhodecode.model.pull_request import PullRequestModel
3095 from rhodecode.model.pull_request import PullRequestModel
3096 pull_request = self
3096 pull_request = self
3097 merge_status = PullRequestModel().merge_status(pull_request)
3097 merge_status = PullRequestModel().merge_status(pull_request)
3098 data = {
3098 data = {
3099 'pull_request_id': pull_request.pull_request_id,
3099 'pull_request_id': pull_request.pull_request_id,
3100 'url': url('pullrequest_show',
3100 'url': url('pullrequest_show',
3101 repo_name=pull_request.target_repo.repo_name,
3101 repo_name=pull_request.target_repo.repo_name,
3102 pull_request_id=pull_request.pull_request_id,
3102 pull_request_id=pull_request.pull_request_id,
3103 qualified=True),
3103 qualified=True),
3104 'title': pull_request.title,
3104 'title': pull_request.title,
3105 'description': pull_request.description,
3105 'description': pull_request.description,
3106 'status': pull_request.status,
3106 'status': pull_request.status,
3107 'created_on': pull_request.created_on,
3107 'created_on': pull_request.created_on,
3108 'updated_on': pull_request.updated_on,
3108 'updated_on': pull_request.updated_on,
3109 'commit_ids': pull_request.revisions,
3109 'commit_ids': pull_request.revisions,
3110 'review_status': pull_request.calculated_review_status(),
3110 'review_status': pull_request.calculated_review_status(),
3111 'mergeable': {
3111 'mergeable': {
3112 'status': merge_status[0],
3112 'status': merge_status[0],
3113 'message': unicode(merge_status[1]),
3113 'message': unicode(merge_status[1]),
3114 },
3114 },
3115 'source': {
3115 'source': {
3116 'clone_url': pull_request.source_repo.clone_url(),
3116 'clone_url': pull_request.source_repo.clone_url(),
3117 'repository': pull_request.source_repo.repo_name,
3117 'repository': pull_request.source_repo.repo_name,
3118 'reference': {
3118 'reference': {
3119 'name': pull_request.source_ref_parts.name,
3119 'name': pull_request.source_ref_parts.name,
3120 'type': pull_request.source_ref_parts.type,
3120 'type': pull_request.source_ref_parts.type,
3121 'commit_id': pull_request.source_ref_parts.commit_id,
3121 'commit_id': pull_request.source_ref_parts.commit_id,
3122 },
3122 },
3123 },
3123 },
3124 'target': {
3124 'target': {
3125 'clone_url': pull_request.target_repo.clone_url(),
3125 'clone_url': pull_request.target_repo.clone_url(),
3126 'repository': pull_request.target_repo.repo_name,
3126 'repository': pull_request.target_repo.repo_name,
3127 'reference': {
3127 'reference': {
3128 'name': pull_request.target_ref_parts.name,
3128 'name': pull_request.target_ref_parts.name,
3129 'type': pull_request.target_ref_parts.type,
3129 'type': pull_request.target_ref_parts.type,
3130 'commit_id': pull_request.target_ref_parts.commit_id,
3130 'commit_id': pull_request.target_ref_parts.commit_id,
3131 },
3131 },
3132 },
3132 },
3133 'author': pull_request.author.get_api_data(include_secrets=False,
3133 'author': pull_request.author.get_api_data(include_secrets=False,
3134 details='basic'),
3134 details='basic'),
3135 'reviewers': [
3135 'reviewers': [
3136 {
3136 {
3137 'user': reviewer.get_api_data(include_secrets=False,
3137 'user': reviewer.get_api_data(include_secrets=False,
3138 details='basic'),
3138 details='basic'),
3139 'review_status': st[0][1].status if st else 'not_reviewed',
3139 'review_status': st[0][1].status if st else 'not_reviewed',
3140 }
3140 }
3141 for reviewer, st in pull_request.reviewers_statuses()
3141 for reviewer, st in pull_request.reviewers_statuses()
3142 ]
3142 ]
3143 }
3143 }
3144
3144
3145 return data
3145 return data
3146
3146
3147 def __json__(self):
3147 def __json__(self):
3148 return {
3148 return {
3149 'revisions': self.revisions,
3149 'revisions': self.revisions,
3150 }
3150 }
3151
3151
3152 def calculated_review_status(self):
3152 def calculated_review_status(self):
3153 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3153 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3154 # because it's tricky on how to use ChangesetStatusModel from there
3154 # because it's tricky on how to use ChangesetStatusModel from there
3155 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3155 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3156 from rhodecode.model.changeset_status import ChangesetStatusModel
3156 from rhodecode.model.changeset_status import ChangesetStatusModel
3157 return ChangesetStatusModel().calculated_review_status(self)
3157 return ChangesetStatusModel().calculated_review_status(self)
3158
3158
3159 def reviewers_statuses(self):
3159 def reviewers_statuses(self):
3160 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3160 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3161 from rhodecode.model.changeset_status import ChangesetStatusModel
3161 from rhodecode.model.changeset_status import ChangesetStatusModel
3162 return ChangesetStatusModel().reviewers_statuses(self)
3162 return ChangesetStatusModel().reviewers_statuses(self)
3163
3163
3164
3164
3165 class PullRequestVersion(Base, _PullRequestBase):
3165 class PullRequestVersion(Base, _PullRequestBase):
3166 __tablename__ = 'pull_request_versions'
3166 __tablename__ = 'pull_request_versions'
3167 __table_args__ = (
3167 __table_args__ = (
3168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3170 )
3170 )
3171
3171
3172 pull_request_version_id = Column(
3172 pull_request_version_id = Column(
3173 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3173 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3174 pull_request_id = Column(
3174 pull_request_id = Column(
3175 'pull_request_id', Integer(),
3175 'pull_request_id', Integer(),
3176 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3176 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3177 pull_request = relationship('PullRequest')
3177 pull_request = relationship('PullRequest')
3178
3178
3179 def __repr__(self):
3179 def __repr__(self):
3180 if self.pull_request_version_id:
3180 if self.pull_request_version_id:
3181 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3181 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3182 else:
3182 else:
3183 return '<DB:PullRequestVersion at %#x>' % id(self)
3183 return '<DB:PullRequestVersion at %#x>' % id(self)
3184
3184
3185
3185
3186 class PullRequestReviewers(Base, BaseModel):
3186 class PullRequestReviewers(Base, BaseModel):
3187 __tablename__ = 'pull_request_reviewers'
3187 __tablename__ = 'pull_request_reviewers'
3188 __table_args__ = (
3188 __table_args__ = (
3189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3190 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3190 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3191 )
3191 )
3192
3192
3193 def __init__(self, user=None, pull_request=None):
3193 def __init__(self, user=None, pull_request=None):
3194 self.user = user
3194 self.user = user
3195 self.pull_request = pull_request
3195 self.pull_request = pull_request
3196
3196
3197 pull_requests_reviewers_id = Column(
3197 pull_requests_reviewers_id = Column(
3198 'pull_requests_reviewers_id', Integer(), nullable=False,
3198 'pull_requests_reviewers_id', Integer(), nullable=False,
3199 primary_key=True)
3199 primary_key=True)
3200 pull_request_id = Column(
3200 pull_request_id = Column(
3201 "pull_request_id", Integer(),
3201 "pull_request_id", Integer(),
3202 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3202 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3203 user_id = Column(
3203 user_id = Column(
3204 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3204 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3205
3205
3206 user = relationship('User')
3206 user = relationship('User')
3207 pull_request = relationship('PullRequest')
3207 pull_request = relationship('PullRequest')
3208
3208
3209
3209
3210 class Notification(Base, BaseModel):
3210 class Notification(Base, BaseModel):
3211 __tablename__ = 'notifications'
3211 __tablename__ = 'notifications'
3212 __table_args__ = (
3212 __table_args__ = (
3213 Index('notification_type_idx', 'type'),
3213 Index('notification_type_idx', 'type'),
3214 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3214 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3215 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3215 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3216 )
3216 )
3217
3217
3218 TYPE_CHANGESET_COMMENT = u'cs_comment'
3218 TYPE_CHANGESET_COMMENT = u'cs_comment'
3219 TYPE_MESSAGE = u'message'
3219 TYPE_MESSAGE = u'message'
3220 TYPE_MENTION = u'mention'
3220 TYPE_MENTION = u'mention'
3221 TYPE_REGISTRATION = u'registration'
3221 TYPE_REGISTRATION = u'registration'
3222 TYPE_PULL_REQUEST = u'pull_request'
3222 TYPE_PULL_REQUEST = u'pull_request'
3223 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3223 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3224
3224
3225 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3225 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3226 subject = Column('subject', Unicode(512), nullable=True)
3226 subject = Column('subject', Unicode(512), nullable=True)
3227 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3227 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3228 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3228 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3229 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3229 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3230 type_ = Column('type', Unicode(255))
3230 type_ = Column('type', Unicode(255))
3231
3231
3232 created_by_user = relationship('User')
3232 created_by_user = relationship('User')
3233 notifications_to_users = relationship('UserNotification', lazy='joined',
3233 notifications_to_users = relationship('UserNotification', lazy='joined',
3234 cascade="all, delete, delete-orphan")
3234 cascade="all, delete, delete-orphan")
3235
3235
3236 @property
3236 @property
3237 def recipients(self):
3237 def recipients(self):
3238 return [x.user for x in UserNotification.query()\
3238 return [x.user for x in UserNotification.query()\
3239 .filter(UserNotification.notification == self)\
3239 .filter(UserNotification.notification == self)\
3240 .order_by(UserNotification.user_id.asc()).all()]
3240 .order_by(UserNotification.user_id.asc()).all()]
3241
3241
3242 @classmethod
3242 @classmethod
3243 def create(cls, created_by, subject, body, recipients, type_=None):
3243 def create(cls, created_by, subject, body, recipients, type_=None):
3244 if type_ is None:
3244 if type_ is None:
3245 type_ = Notification.TYPE_MESSAGE
3245 type_ = Notification.TYPE_MESSAGE
3246
3246
3247 notification = cls()
3247 notification = cls()
3248 notification.created_by_user = created_by
3248 notification.created_by_user = created_by
3249 notification.subject = subject
3249 notification.subject = subject
3250 notification.body = body
3250 notification.body = body
3251 notification.type_ = type_
3251 notification.type_ = type_
3252 notification.created_on = datetime.datetime.now()
3252 notification.created_on = datetime.datetime.now()
3253
3253
3254 for u in recipients:
3254 for u in recipients:
3255 assoc = UserNotification()
3255 assoc = UserNotification()
3256 assoc.notification = notification
3256 assoc.notification = notification
3257
3257
3258 # if created_by is inside recipients mark his notification
3258 # if created_by is inside recipients mark his notification
3259 # as read
3259 # as read
3260 if u.user_id == created_by.user_id:
3260 if u.user_id == created_by.user_id:
3261 assoc.read = True
3261 assoc.read = True
3262
3262
3263 u.notifications.append(assoc)
3263 u.notifications.append(assoc)
3264 Session().add(notification)
3264 Session().add(notification)
3265
3265
3266 return notification
3266 return notification
3267
3267
3268 @property
3268 @property
3269 def description(self):
3269 def description(self):
3270 from rhodecode.model.notification import NotificationModel
3270 from rhodecode.model.notification import NotificationModel
3271 return NotificationModel().make_description(self)
3271 return NotificationModel().make_description(self)
3272
3272
3273
3273
3274 class UserNotification(Base, BaseModel):
3274 class UserNotification(Base, BaseModel):
3275 __tablename__ = 'user_to_notification'
3275 __tablename__ = 'user_to_notification'
3276 __table_args__ = (
3276 __table_args__ = (
3277 UniqueConstraint('user_id', 'notification_id'),
3277 UniqueConstraint('user_id', 'notification_id'),
3278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3280 )
3280 )
3281 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3281 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3282 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3282 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3283 read = Column('read', Boolean, default=False)
3283 read = Column('read', Boolean, default=False)
3284 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3284 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3285
3285
3286 user = relationship('User', lazy="joined")
3286 user = relationship('User', lazy="joined")
3287 notification = relationship('Notification', lazy="joined",
3287 notification = relationship('Notification', lazy="joined",
3288 order_by=lambda: Notification.created_on.desc(),)
3288 order_by=lambda: Notification.created_on.desc(),)
3289
3289
3290 def mark_as_read(self):
3290 def mark_as_read(self):
3291 self.read = True
3291 self.read = True
3292 Session().add(self)
3292 Session().add(self)
3293
3293
3294
3294
3295 class Gist(Base, BaseModel):
3295 class Gist(Base, BaseModel):
3296 __tablename__ = 'gists'
3296 __tablename__ = 'gists'
3297 __table_args__ = (
3297 __table_args__ = (
3298 Index('g_gist_access_id_idx', 'gist_access_id'),
3298 Index('g_gist_access_id_idx', 'gist_access_id'),
3299 Index('g_created_on_idx', 'created_on'),
3299 Index('g_created_on_idx', 'created_on'),
3300 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3300 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3301 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3301 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3302 )
3302 )
3303 GIST_PUBLIC = u'public'
3303 GIST_PUBLIC = u'public'
3304 GIST_PRIVATE = u'private'
3304 GIST_PRIVATE = u'private'
3305 DEFAULT_FILENAME = u'gistfile1.txt'
3305 DEFAULT_FILENAME = u'gistfile1.txt'
3306
3306
3307 ACL_LEVEL_PUBLIC = u'acl_public'
3307 ACL_LEVEL_PUBLIC = u'acl_public'
3308 ACL_LEVEL_PRIVATE = u'acl_private'
3308 ACL_LEVEL_PRIVATE = u'acl_private'
3309
3309
3310 gist_id = Column('gist_id', Integer(), primary_key=True)
3310 gist_id = Column('gist_id', Integer(), primary_key=True)
3311 gist_access_id = Column('gist_access_id', Unicode(250))
3311 gist_access_id = Column('gist_access_id', Unicode(250))
3312 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3312 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3313 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3313 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3314 gist_expires = Column('gist_expires', Float(53), nullable=False)
3314 gist_expires = Column('gist_expires', Float(53), nullable=False)
3315 gist_type = Column('gist_type', Unicode(128), nullable=False)
3315 gist_type = Column('gist_type', Unicode(128), nullable=False)
3316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3317 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3317 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3318 acl_level = Column('acl_level', Unicode(128), nullable=True)
3318 acl_level = Column('acl_level', Unicode(128), nullable=True)
3319
3319
3320 owner = relationship('User')
3320 owner = relationship('User')
3321
3321
3322 def __repr__(self):
3322 def __repr__(self):
3323 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3323 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3324
3324
3325 @classmethod
3325 @classmethod
3326 def get_or_404(cls, id_):
3326 def get_or_404(cls, id_):
3327 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3327 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3328 if not res:
3328 if not res:
3329 raise HTTPNotFound
3329 raise HTTPNotFound
3330 return res
3330 return res
3331
3331
3332 @classmethod
3332 @classmethod
3333 def get_by_access_id(cls, gist_access_id):
3333 def get_by_access_id(cls, gist_access_id):
3334 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3334 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3335
3335
3336 def gist_url(self):
3336 def gist_url(self):
3337 import rhodecode
3337 import rhodecode
3338 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3338 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3339 if alias_url:
3339 if alias_url:
3340 return alias_url.replace('{gistid}', self.gist_access_id)
3340 return alias_url.replace('{gistid}', self.gist_access_id)
3341
3341
3342 return url('gist', gist_id=self.gist_access_id, qualified=True)
3342 return url('gist', gist_id=self.gist_access_id, qualified=True)
3343
3343
3344 @classmethod
3344 @classmethod
3345 def base_path(cls):
3345 def base_path(cls):
3346 """
3346 """
3347 Returns base path when all gists are stored
3347 Returns base path when all gists are stored
3348
3348
3349 :param cls:
3349 :param cls:
3350 """
3350 """
3351 from rhodecode.model.gist import GIST_STORE_LOC
3351 from rhodecode.model.gist import GIST_STORE_LOC
3352 q = Session().query(RhodeCodeUi)\
3352 q = Session().query(RhodeCodeUi)\
3353 .filter(RhodeCodeUi.ui_key == URL_SEP)
3353 .filter(RhodeCodeUi.ui_key == URL_SEP)
3354 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3354 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3355 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3355 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3356
3356
3357 def get_api_data(self):
3357 def get_api_data(self):
3358 """
3358 """
3359 Common function for generating gist related data for API
3359 Common function for generating gist related data for API
3360 """
3360 """
3361 gist = self
3361 gist = self
3362 data = {
3362 data = {
3363 'gist_id': gist.gist_id,
3363 'gist_id': gist.gist_id,
3364 'type': gist.gist_type,
3364 'type': gist.gist_type,
3365 'access_id': gist.gist_access_id,
3365 'access_id': gist.gist_access_id,
3366 'description': gist.gist_description,
3366 'description': gist.gist_description,
3367 'url': gist.gist_url(),
3367 'url': gist.gist_url(),
3368 'expires': gist.gist_expires,
3368 'expires': gist.gist_expires,
3369 'created_on': gist.created_on,
3369 'created_on': gist.created_on,
3370 'modified_at': gist.modified_at,
3370 'modified_at': gist.modified_at,
3371 'content': None,
3371 'content': None,
3372 'acl_level': gist.acl_level,
3372 'acl_level': gist.acl_level,
3373 }
3373 }
3374 return data
3374 return data
3375
3375
3376 def __json__(self):
3376 def __json__(self):
3377 data = dict(
3377 data = dict(
3378 )
3378 )
3379 data.update(self.get_api_data())
3379 data.update(self.get_api_data())
3380 return data
3380 return data
3381 # SCM functions
3381 # SCM functions
3382
3382
3383 def scm_instance(self, **kwargs):
3383 def scm_instance(self, **kwargs):
3384 from rhodecode.lib.vcs import get_repo
3384 from rhodecode.lib.vcs import get_repo
3385 base_path = self.base_path()
3385 base_path = self.base_path()
3386 return get_repo(os.path.join(*map(safe_str,
3386 return get_repo(os.path.join(*map(safe_str,
3387 [base_path, self.gist_access_id])))
3387 [base_path, self.gist_access_id])))
3388
3388
3389
3389
3390 class DbMigrateVersion(Base, BaseModel):
3390 class DbMigrateVersion(Base, BaseModel):
3391 __tablename__ = 'db_migrate_version'
3391 __tablename__ = 'db_migrate_version'
3392 __table_args__ = (
3392 __table_args__ = (
3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3395 )
3395 )
3396 repository_id = Column('repository_id', String(250), primary_key=True)
3396 repository_id = Column('repository_id', String(250), primary_key=True)
3397 repository_path = Column('repository_path', Text)
3397 repository_path = Column('repository_path', Text)
3398 version = Column('version', Integer)
3398 version = Column('version', Integer)
3399
3399
3400
3400
3401 class ExternalIdentity(Base, BaseModel):
3401 class ExternalIdentity(Base, BaseModel):
3402 __tablename__ = 'external_identities'
3402 __tablename__ = 'external_identities'
3403 __table_args__ = (
3403 __table_args__ = (
3404 Index('local_user_id_idx', 'local_user_id'),
3404 Index('local_user_id_idx', 'local_user_id'),
3405 Index('external_id_idx', 'external_id'),
3405 Index('external_id_idx', 'external_id'),
3406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3407 'mysql_charset': 'utf8'})
3407 'mysql_charset': 'utf8'})
3408
3408
3409 external_id = Column('external_id', Unicode(255), default=u'',
3409 external_id = Column('external_id', Unicode(255), default=u'',
3410 primary_key=True)
3410 primary_key=True)
3411 external_username = Column('external_username', Unicode(1024), default=u'')
3411 external_username = Column('external_username', Unicode(1024), default=u'')
3412 local_user_id = Column('local_user_id', Integer(),
3412 local_user_id = Column('local_user_id', Integer(),
3413 ForeignKey('users.user_id'), primary_key=True)
3413 ForeignKey('users.user_id'), primary_key=True)
3414 provider_name = Column('provider_name', Unicode(255), default=u'',
3414 provider_name = Column('provider_name', Unicode(255), default=u'',
3415 primary_key=True)
3415 primary_key=True)
3416 access_token = Column('access_token', String(1024), default=u'')
3416 access_token = Column('access_token', String(1024), default=u'')
3417 alt_token = Column('alt_token', String(1024), default=u'')
3417 alt_token = Column('alt_token', String(1024), default=u'')
3418 token_secret = Column('token_secret', String(1024), default=u'')
3418 token_secret = Column('token_secret', String(1024), default=u'')
3419
3419
3420 @classmethod
3420 @classmethod
3421 def by_external_id_and_provider(cls, external_id, provider_name,
3421 def by_external_id_and_provider(cls, external_id, provider_name,
3422 local_user_id=None):
3422 local_user_id=None):
3423 """
3423 """
3424 Returns ExternalIdentity instance based on search params
3424 Returns ExternalIdentity instance based on search params
3425
3425
3426 :param external_id:
3426 :param external_id:
3427 :param provider_name:
3427 :param provider_name:
3428 :return: ExternalIdentity
3428 :return: ExternalIdentity
3429 """
3429 """
3430 query = cls.query()
3430 query = cls.query()
3431 query = query.filter(cls.external_id == external_id)
3431 query = query.filter(cls.external_id == external_id)
3432 query = query.filter(cls.provider_name == provider_name)
3432 query = query.filter(cls.provider_name == provider_name)
3433 if local_user_id:
3433 if local_user_id:
3434 query = query.filter(cls.local_user_id == local_user_id)
3434 query = query.filter(cls.local_user_id == local_user_id)
3435 return query.first()
3435 return query.first()
3436
3436
3437 @classmethod
3437 @classmethod
3438 def user_by_external_id_and_provider(cls, external_id, provider_name):
3438 def user_by_external_id_and_provider(cls, external_id, provider_name):
3439 """
3439 """
3440 Returns User instance based on search params
3440 Returns User instance based on search params
3441
3441
3442 :param external_id:
3442 :param external_id:
3443 :param provider_name:
3443 :param provider_name:
3444 :return: User
3444 :return: User
3445 """
3445 """
3446 query = User.query()
3446 query = User.query()
3447 query = query.filter(cls.external_id == external_id)
3447 query = query.filter(cls.external_id == external_id)
3448 query = query.filter(cls.provider_name == provider_name)
3448 query = query.filter(cls.provider_name == provider_name)
3449 query = query.filter(User.user_id == cls.local_user_id)
3449 query = query.filter(User.user_id == cls.local_user_id)
3450 return query.first()
3450 return query.first()
3451
3451
3452 @classmethod
3452 @classmethod
3453 def by_local_user_id(cls, local_user_id):
3453 def by_local_user_id(cls, local_user_id):
3454 """
3454 """
3455 Returns all tokens for user
3455 Returns all tokens for user
3456
3456
3457 :param local_user_id:
3457 :param local_user_id:
3458 :return: ExternalIdentity
3458 :return: ExternalIdentity
3459 """
3459 """
3460 query = cls.query()
3460 query = cls.query()
3461 query = query.filter(cls.local_user_id == local_user_id)
3461 query = query.filter(cls.local_user_id == local_user_id)
3462 return query
3462 return query
@@ -1,924 +1,924 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime
31 from datetime import datetime
32
32
33 from sqlalchemy.sql import func
33 from sqlalchemy.sql import func
34 from sqlalchemy.sql.expression import true, or_
34 from sqlalchemy.sql.expression import true, or_
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.exceptions import AttachedForksError
40 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.hooks_base import log_delete_repository
41 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.utils import make_db_config
42 from rhodecode.lib.utils import make_db_config
43 from rhodecode.lib.utils2 import (
43 from rhodecode.lib.utils2 import (
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 from rhodecode.lib.vcs.backends import get_backend
46 from rhodecode.lib.vcs.backends import get_backend
47 from rhodecode.model import BaseModel
47 from rhodecode.model import BaseModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 RepoGroup, RepositoryField)
51 RepoGroup, RepositoryField)
52 from rhodecode.model.scm import UserGroupList
52 from rhodecode.model.scm import UserGroupList
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class RepoModel(BaseModel):
59 class RepoModel(BaseModel):
60
60
61 cls = Repository
61 cls = Repository
62
62
63 def _get_user_group(self, users_group):
63 def _get_user_group(self, users_group):
64 return self._get_instance(UserGroup, users_group,
64 return self._get_instance(UserGroup, users_group,
65 callback=UserGroup.get_by_group_name)
65 callback=UserGroup.get_by_group_name)
66
66
67 def _get_repo_group(self, repo_group):
67 def _get_repo_group(self, repo_group):
68 return self._get_instance(RepoGroup, repo_group,
68 return self._get_instance(RepoGroup, repo_group,
69 callback=RepoGroup.get_by_group_name)
69 callback=RepoGroup.get_by_group_name)
70
70
71 def _create_default_perms(self, repository, private):
71 def _create_default_perms(self, repository, private):
72 # create default permission
72 # create default permission
73 default = 'repository.read'
73 default = 'repository.read'
74 def_user = User.get_default_user()
74 def_user = User.get_default_user()
75 for p in def_user.user_perms:
75 for p in def_user.user_perms:
76 if p.permission.permission_name.startswith('repository.'):
76 if p.permission.permission_name.startswith('repository.'):
77 default = p.permission.permission_name
77 default = p.permission.permission_name
78 break
78 break
79
79
80 default_perm = 'repository.none' if private else default
80 default_perm = 'repository.none' if private else default
81
81
82 repo_to_perm = UserRepoToPerm()
82 repo_to_perm = UserRepoToPerm()
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
84
84
85 repo_to_perm.repository = repository
85 repo_to_perm.repository = repository
86 repo_to_perm.user_id = def_user.user_id
86 repo_to_perm.user_id = def_user.user_id
87
87
88 return repo_to_perm
88 return repo_to_perm
89
89
90 @LazyProperty
90 @LazyProperty
91 def repos_path(self):
91 def repos_path(self):
92 """
92 """
93 Gets the repositories root path from database
93 Gets the repositories root path from database
94 """
94 """
95 settings_model = VcsSettingsModel(sa=self.sa)
95 settings_model = VcsSettingsModel(sa=self.sa)
96 return settings_model.get_repos_location()
96 return settings_model.get_repos_location()
97
97
98 def get(self, repo_id, cache=False):
98 def get(self, repo_id, cache=False):
99 repo = self.sa.query(Repository) \
99 repo = self.sa.query(Repository) \
100 .filter(Repository.repo_id == repo_id)
100 .filter(Repository.repo_id == repo_id)
101
101
102 if cache:
102 if cache:
103 repo = repo.options(FromCache("sql_cache_short",
103 repo = repo.options(FromCache("sql_cache_short",
104 "get_repo_%s" % repo_id))
104 "get_repo_%s" % repo_id))
105 return repo.scalar()
105 return repo.scalar()
106
106
107 def get_repo(self, repository):
107 def get_repo(self, repository):
108 return self._get_repo(repository)
108 return self._get_repo(repository)
109
109
110 def get_by_repo_name(self, repo_name, cache=False):
110 def get_by_repo_name(self, repo_name, cache=False):
111 repo = self.sa.query(Repository) \
111 repo = self.sa.query(Repository) \
112 .filter(Repository.repo_name == repo_name)
112 .filter(Repository.repo_name == repo_name)
113
113
114 if cache:
114 if cache:
115 repo = repo.options(FromCache("sql_cache_short",
115 repo = repo.options(FromCache("sql_cache_short",
116 "get_repo_%s" % repo_name))
116 "get_repo_%s" % repo_name))
117 return repo.scalar()
117 return repo.scalar()
118
118
119 def _extract_id_from_repo_name(self, repo_name):
119 def _extract_id_from_repo_name(self, repo_name):
120 if repo_name.startswith('/'):
120 if repo_name.startswith('/'):
121 repo_name = repo_name.lstrip('/')
121 repo_name = repo_name.lstrip('/')
122 by_id_match = re.match(r'^_(\d{1,})', repo_name)
122 by_id_match = re.match(r'^_(\d{1,})', repo_name)
123 if by_id_match:
123 if by_id_match:
124 return by_id_match.groups()[0]
124 return by_id_match.groups()[0]
125
125
126 def get_repo_by_id(self, repo_name):
126 def get_repo_by_id(self, repo_name):
127 """
127 """
128 Extracts repo_name by id from special urls.
128 Extracts repo_name by id from special urls.
129 Example url is _11/repo_name
129 Example url is _11/repo_name
130
130
131 :param repo_name:
131 :param repo_name:
132 :return: repo object if matched else None
132 :return: repo object if matched else None
133 """
133 """
134 try:
134 try:
135 _repo_id = self._extract_id_from_repo_name(repo_name)
135 _repo_id = self._extract_id_from_repo_name(repo_name)
136 if _repo_id:
136 if _repo_id:
137 return self.get(_repo_id)
137 return self.get(_repo_id)
138 except Exception:
138 except Exception:
139 log.exception('Failed to extract repo_name from URL')
139 log.exception('Failed to extract repo_name from URL')
140
140
141 return None
141 return None
142
142
143 def get_users(self, name_contains=None, limit=20, only_active=True):
143 def get_users(self, name_contains=None, limit=20, only_active=True):
144 # TODO: mikhail: move this method to the UserModel.
144 # TODO: mikhail: move this method to the UserModel.
145 query = self.sa.query(User)
145 query = self.sa.query(User)
146 if only_active:
146 if only_active:
147 query = query.filter(User.active == true())
147 query = query.filter(User.active == true())
148
148
149 if name_contains:
149 if name_contains:
150 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
150 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
151 query = query.filter(
151 query = query.filter(
152 or_(
152 or_(
153 User.name.ilike(ilike_expression),
153 User.name.ilike(ilike_expression),
154 User.lastname.ilike(ilike_expression),
154 User.lastname.ilike(ilike_expression),
155 User.username.ilike(ilike_expression)
155 User.username.ilike(ilike_expression)
156 )
156 )
157 )
157 )
158 query = query.limit(limit)
158 query = query.limit(limit)
159 users = query.all()
159 users = query.all()
160
160
161 _users = [
161 _users = [
162 {
162 {
163 'id': user.user_id,
163 'id': user.user_id,
164 'first_name': user.name,
164 'first_name': user.name,
165 'last_name': user.lastname,
165 'last_name': user.lastname,
166 'username': user.username,
166 'username': user.username,
167 'icon_link': h.gravatar_url(user.email, 14),
167 'icon_link': h.gravatar_url(user.email, 14),
168 'value_display': h.person(user.email),
168 'value_display': h.person(user.email),
169 'value': user.username,
169 'value': user.username,
170 'value_type': 'user',
170 'value_type': 'user',
171 'active': user.active,
171 'active': user.active,
172 }
172 }
173 for user in users
173 for user in users
174 ]
174 ]
175 return _users
175 return _users
176
176
177 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
177 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
178 # TODO: mikhail: move this method to the UserGroupModel.
178 # TODO: mikhail: move this method to the UserGroupModel.
179 query = self.sa.query(UserGroup)
179 query = self.sa.query(UserGroup)
180 if only_active:
180 if only_active:
181 query = query.filter(UserGroup.users_group_active == true())
181 query = query.filter(UserGroup.users_group_active == true())
182
182
183 if name_contains:
183 if name_contains:
184 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
184 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
185 query = query.filter(
185 query = query.filter(
186 UserGroup.users_group_name.ilike(ilike_expression))\
186 UserGroup.users_group_name.ilike(ilike_expression))\
187 .order_by(func.length(UserGroup.users_group_name))\
187 .order_by(func.length(UserGroup.users_group_name))\
188 .order_by(UserGroup.users_group_name)
188 .order_by(UserGroup.users_group_name)
189
189
190 query = query.limit(limit)
190 query = query.limit(limit)
191 user_groups = query.all()
191 user_groups = query.all()
192 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
192 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
193 user_groups = UserGroupList(user_groups, perm_set=perm_set)
193 user_groups = UserGroupList(user_groups, perm_set=perm_set)
194
194
195 _groups = [
195 _groups = [
196 {
196 {
197 'id': group.users_group_id,
197 'id': group.users_group_id,
198 # TODO: marcink figure out a way to generate the url for the
198 # TODO: marcink figure out a way to generate the url for the
199 # icon
199 # icon
200 'icon_link': '',
200 'icon_link': '',
201 'value_display': 'Group: %s (%d members)' % (
201 'value_display': 'Group: %s (%d members)' % (
202 group.users_group_name, len(group.members),),
202 group.users_group_name, len(group.members),),
203 'value': group.users_group_name,
203 'value': group.users_group_name,
204 'value_type': 'user_group',
204 'value_type': 'user_group',
205 'active': group.users_group_active,
205 'active': group.users_group_active,
206 }
206 }
207 for group in user_groups
207 for group in user_groups
208 ]
208 ]
209 return _groups
209 return _groups
210
210
211 @classmethod
211 @classmethod
212 def update_repoinfo(cls, repositories=None):
212 def update_repoinfo(cls, repositories=None):
213 if not repositories:
213 if not repositories:
214 repositories = Repository.getAll()
214 repositories = Repository.getAll()
215 for repo in repositories:
215 for repo in repositories:
216 repo.update_commit_cache()
216 repo.update_commit_cache()
217
217
218 def get_repos_as_dict(self, repo_list=None, admin=False,
218 def get_repos_as_dict(self, repo_list=None, admin=False,
219 super_user_actions=False):
219 super_user_actions=False):
220
220
221 from rhodecode.lib.utils import PartialRenderer
221 from rhodecode.lib.utils import PartialRenderer
222 _render = PartialRenderer('data_table/_dt_elements.html')
222 _render = PartialRenderer('data_table/_dt_elements.html')
223 c = _render.c
223 c = _render.c
224
224
225 def quick_menu(repo_name):
225 def quick_menu(repo_name):
226 return _render('quick_menu', repo_name)
226 return _render('quick_menu', repo_name)
227
227
228 def repo_lnk(name, rtype, rstate, private, fork_of):
228 def repo_lnk(name, rtype, rstate, private, fork_of):
229 return _render('repo_name', name, rtype, rstate, private, fork_of,
229 return _render('repo_name', name, rtype, rstate, private, fork_of,
230 short_name=not admin, admin=False)
230 short_name=not admin, admin=False)
231
231
232 def last_change(last_change):
232 def last_change(last_change):
233 return _render("last_change", last_change)
233 return _render("last_change", last_change)
234
234
235 def rss_lnk(repo_name):
235 def rss_lnk(repo_name):
236 return _render("rss", repo_name)
236 return _render("rss", repo_name)
237
237
238 def atom_lnk(repo_name):
238 def atom_lnk(repo_name):
239 return _render("atom", repo_name)
239 return _render("atom", repo_name)
240
240
241 def last_rev(repo_name, cs_cache):
241 def last_rev(repo_name, cs_cache):
242 return _render('revision', repo_name, cs_cache.get('revision'),
242 return _render('revision', repo_name, cs_cache.get('revision'),
243 cs_cache.get('raw_id'), cs_cache.get('author'),
243 cs_cache.get('raw_id'), cs_cache.get('author'),
244 cs_cache.get('message'))
244 cs_cache.get('message'))
245
245
246 def desc(desc):
246 def desc(desc):
247 if c.visual.stylify_metatags:
247 if c.visual.stylify_metatags:
248 return h.urlify_text(h.escaped_stylize(h.truncate(desc, 60)))
248 return h.urlify_text(h.escaped_stylize(h.truncate(desc, 60)))
249 else:
249 else:
250 return h.urlify_text(h.html_escape(h.truncate(desc, 60)))
250 return h.urlify_text(h.html_escape(h.truncate(desc, 60)))
251
251
252 def state(repo_state):
252 def state(repo_state):
253 return _render("repo_state", repo_state)
253 return _render("repo_state", repo_state)
254
254
255 def repo_actions(repo_name):
255 def repo_actions(repo_name):
256 return _render('repo_actions', repo_name, super_user_actions)
256 return _render('repo_actions', repo_name, super_user_actions)
257
257
258 def user_profile(username):
258 def user_profile(username):
259 return _render('user_profile', username)
259 return _render('user_profile', username)
260
260
261 repos_data = []
261 repos_data = []
262 for repo in repo_list:
262 for repo in repo_list:
263 cs_cache = repo.changeset_cache
263 cs_cache = repo.changeset_cache
264 row = {
264 row = {
265 "menu": quick_menu(repo.repo_name),
265 "menu": quick_menu(repo.repo_name),
266
266
267 "name": repo_lnk(repo.repo_name, repo.repo_type,
267 "name": repo_lnk(repo.repo_name, repo.repo_type,
268 repo.repo_state, repo.private, repo.fork),
268 repo.repo_state, repo.private, repo.fork),
269 "name_raw": repo.repo_name.lower(),
269 "name_raw": repo.repo_name.lower(),
270
270
271 "last_change": last_change(repo.last_db_change),
271 "last_change": last_change(repo.last_db_change),
272 "last_change_raw": datetime_to_time(repo.last_db_change),
272 "last_change_raw": datetime_to_time(repo.last_db_change),
273
273
274 "last_changeset": last_rev(repo.repo_name, cs_cache),
274 "last_changeset": last_rev(repo.repo_name, cs_cache),
275 "last_changeset_raw": cs_cache.get('revision'),
275 "last_changeset_raw": cs_cache.get('revision'),
276
276
277 "desc": desc(repo.description),
277 "desc": desc(repo.description),
278 "owner": user_profile(repo.user.username),
278 "owner": user_profile(repo.user.username),
279
279
280 "state": state(repo.repo_state),
280 "state": state(repo.repo_state),
281 "rss": rss_lnk(repo.repo_name),
281 "rss": rss_lnk(repo.repo_name),
282
282
283 "atom": atom_lnk(repo.repo_name),
283 "atom": atom_lnk(repo.repo_name),
284 }
284 }
285 if admin:
285 if admin:
286 row.update({
286 row.update({
287 "action": repo_actions(repo.repo_name),
287 "action": repo_actions(repo.repo_name),
288 })
288 })
289 repos_data.append(row)
289 repos_data.append(row)
290
290
291 return repos_data
291 return repos_data
292
292
293 def _get_defaults(self, repo_name):
293 def _get_defaults(self, repo_name):
294 """
294 """
295 Gets information about repository, and returns a dict for
295 Gets information about repository, and returns a dict for
296 usage in forms
296 usage in forms
297
297
298 :param repo_name:
298 :param repo_name:
299 """
299 """
300
300
301 repo_info = Repository.get_by_repo_name(repo_name)
301 repo_info = Repository.get_by_repo_name(repo_name)
302
302
303 if repo_info is None:
303 if repo_info is None:
304 return None
304 return None
305
305
306 defaults = repo_info.get_dict()
306 defaults = repo_info.get_dict()
307 defaults['repo_name'] = repo_info.just_name
307 defaults['repo_name'] = repo_info.just_name
308
308
309 groups = repo_info.groups_with_parents
309 groups = repo_info.groups_with_parents
310 parent_group = groups[-1] if groups else None
310 parent_group = groups[-1] if groups else None
311
311
312 # we use -1 as this is how in HTML, we mark an empty group
312 # we use -1 as this is how in HTML, we mark an empty group
313 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
313 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
314
314
315 keys_to_process = (
315 keys_to_process = (
316 {'k': 'repo_type', 'strip': False},
316 {'k': 'repo_type', 'strip': False},
317 {'k': 'repo_enable_downloads', 'strip': True},
317 {'k': 'repo_enable_downloads', 'strip': True},
318 {'k': 'repo_description', 'strip': True},
318 {'k': 'repo_description', 'strip': True},
319 {'k': 'repo_enable_locking', 'strip': True},
319 {'k': 'repo_enable_locking', 'strip': True},
320 {'k': 'repo_landing_rev', 'strip': True},
320 {'k': 'repo_landing_rev', 'strip': True},
321 {'k': 'clone_uri', 'strip': False},
321 {'k': 'clone_uri', 'strip': False},
322 {'k': 'repo_private', 'strip': True},
322 {'k': 'repo_private', 'strip': True},
323 {'k': 'repo_enable_statistics', 'strip': True}
323 {'k': 'repo_enable_statistics', 'strip': True}
324 )
324 )
325
325
326 for item in keys_to_process:
326 for item in keys_to_process:
327 attr = item['k']
327 attr = item['k']
328 if item['strip']:
328 if item['strip']:
329 attr = remove_prefix(item['k'], 'repo_')
329 attr = remove_prefix(item['k'], 'repo_')
330
330
331 val = defaults[attr]
331 val = defaults[attr]
332 if item['k'] == 'repo_landing_rev':
332 if item['k'] == 'repo_landing_rev':
333 val = ':'.join(defaults[attr])
333 val = ':'.join(defaults[attr])
334 defaults[item['k']] = val
334 defaults[item['k']] = val
335 if item['k'] == 'clone_uri':
335 if item['k'] == 'clone_uri':
336 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
336 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
337
337
338 # fill owner
338 # fill owner
339 if repo_info.user:
339 if repo_info.user:
340 defaults.update({'user': repo_info.user.username})
340 defaults.update({'user': repo_info.user.username})
341 else:
341 else:
342 replacement_user = User.get_first_admin().username
342 replacement_user = User.get_first_super_admin().username
343 defaults.update({'user': replacement_user})
343 defaults.update({'user': replacement_user})
344
344
345 # fill repository users
345 # fill repository users
346 for p in repo_info.repo_to_perm:
346 for p in repo_info.repo_to_perm:
347 defaults.update({'u_perm_%s' % p.user.user_id:
347 defaults.update({'u_perm_%s' % p.user.user_id:
348 p.permission.permission_name})
348 p.permission.permission_name})
349
349
350 # fill repository groups
350 # fill repository groups
351 for p in repo_info.users_group_to_perm:
351 for p in repo_info.users_group_to_perm:
352 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
352 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
353 p.permission.permission_name})
353 p.permission.permission_name})
354
354
355 return defaults
355 return defaults
356
356
357 def update(self, repo, **kwargs):
357 def update(self, repo, **kwargs):
358 try:
358 try:
359 cur_repo = self._get_repo(repo)
359 cur_repo = self._get_repo(repo)
360 source_repo_name = cur_repo.repo_name
360 source_repo_name = cur_repo.repo_name
361 if 'user' in kwargs:
361 if 'user' in kwargs:
362 cur_repo.user = User.get_by_username(kwargs['user'])
362 cur_repo.user = User.get_by_username(kwargs['user'])
363
363
364 if 'repo_group' in kwargs:
364 if 'repo_group' in kwargs:
365 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
365 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
366 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
366 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
367
367
368 update_keys = [
368 update_keys = [
369 (1, 'repo_enable_downloads'),
369 (1, 'repo_enable_downloads'),
370 (1, 'repo_description'),
370 (1, 'repo_description'),
371 (1, 'repo_enable_locking'),
371 (1, 'repo_enable_locking'),
372 (1, 'repo_landing_rev'),
372 (1, 'repo_landing_rev'),
373 (1, 'repo_private'),
373 (1, 'repo_private'),
374 (1, 'repo_enable_statistics'),
374 (1, 'repo_enable_statistics'),
375 (0, 'clone_uri'),
375 (0, 'clone_uri'),
376 (0, 'fork_id')
376 (0, 'fork_id')
377 ]
377 ]
378 for strip, k in update_keys:
378 for strip, k in update_keys:
379 if k in kwargs:
379 if k in kwargs:
380 val = kwargs[k]
380 val = kwargs[k]
381 if strip:
381 if strip:
382 k = remove_prefix(k, 'repo_')
382 k = remove_prefix(k, 'repo_')
383 if k == 'clone_uri':
383 if k == 'clone_uri':
384 from rhodecode.model.validators import Missing
384 from rhodecode.model.validators import Missing
385 _change = kwargs.get('clone_uri_change')
385 _change = kwargs.get('clone_uri_change')
386 if _change in [Missing, 'OLD']:
386 if _change in [Missing, 'OLD']:
387 # we don't change the value, so use original one
387 # we don't change the value, so use original one
388 val = cur_repo.clone_uri
388 val = cur_repo.clone_uri
389
389
390 setattr(cur_repo, k, val)
390 setattr(cur_repo, k, val)
391
391
392 new_name = cur_repo.get_new_name(kwargs['repo_name'])
392 new_name = cur_repo.get_new_name(kwargs['repo_name'])
393 cur_repo.repo_name = new_name
393 cur_repo.repo_name = new_name
394
394
395 # if private flag is set, reset default permission to NONE
395 # if private flag is set, reset default permission to NONE
396 if kwargs.get('repo_private'):
396 if kwargs.get('repo_private'):
397 EMPTY_PERM = 'repository.none'
397 EMPTY_PERM = 'repository.none'
398 RepoModel().grant_user_permission(
398 RepoModel().grant_user_permission(
399 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
399 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
400 )
400 )
401
401
402 # handle extra fields
402 # handle extra fields
403 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
403 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
404 kwargs):
404 kwargs):
405 k = RepositoryField.un_prefix_key(field)
405 k = RepositoryField.un_prefix_key(field)
406 ex_field = RepositoryField.get_by_key_name(
406 ex_field = RepositoryField.get_by_key_name(
407 key=k, repo=cur_repo)
407 key=k, repo=cur_repo)
408 if ex_field:
408 if ex_field:
409 ex_field.field_value = kwargs[field]
409 ex_field.field_value = kwargs[field]
410 self.sa.add(ex_field)
410 self.sa.add(ex_field)
411 self.sa.add(cur_repo)
411 self.sa.add(cur_repo)
412
412
413 if source_repo_name != new_name:
413 if source_repo_name != new_name:
414 # rename repository
414 # rename repository
415 self._rename_filesystem_repo(
415 self._rename_filesystem_repo(
416 old=source_repo_name, new=new_name)
416 old=source_repo_name, new=new_name)
417
417
418 return cur_repo
418 return cur_repo
419 except Exception:
419 except Exception:
420 log.error(traceback.format_exc())
420 log.error(traceback.format_exc())
421 raise
421 raise
422
422
423 def _create_repo(self, repo_name, repo_type, description, owner,
423 def _create_repo(self, repo_name, repo_type, description, owner,
424 private=False, clone_uri=None, repo_group=None,
424 private=False, clone_uri=None, repo_group=None,
425 landing_rev='rev:tip', fork_of=None,
425 landing_rev='rev:tip', fork_of=None,
426 copy_fork_permissions=False, enable_statistics=False,
426 copy_fork_permissions=False, enable_statistics=False,
427 enable_locking=False, enable_downloads=False,
427 enable_locking=False, enable_downloads=False,
428 copy_group_permissions=False,
428 copy_group_permissions=False,
429 state=Repository.STATE_PENDING):
429 state=Repository.STATE_PENDING):
430 """
430 """
431 Create repository inside database with PENDING state, this should be
431 Create repository inside database with PENDING state, this should be
432 only executed by create() repo. With exception of importing existing
432 only executed by create() repo. With exception of importing existing
433 repos
433 repos
434 """
434 """
435 from rhodecode.model.scm import ScmModel
435 from rhodecode.model.scm import ScmModel
436
436
437 owner = self._get_user(owner)
437 owner = self._get_user(owner)
438 fork_of = self._get_repo(fork_of)
438 fork_of = self._get_repo(fork_of)
439 repo_group = self._get_repo_group(safe_int(repo_group))
439 repo_group = self._get_repo_group(safe_int(repo_group))
440
440
441 try:
441 try:
442 repo_name = safe_unicode(repo_name)
442 repo_name = safe_unicode(repo_name)
443 description = safe_unicode(description)
443 description = safe_unicode(description)
444 # repo name is just a name of repository
444 # repo name is just a name of repository
445 # while repo_name_full is a full qualified name that is combined
445 # while repo_name_full is a full qualified name that is combined
446 # with name and path of group
446 # with name and path of group
447 repo_name_full = repo_name
447 repo_name_full = repo_name
448 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
448 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
449
449
450 new_repo = Repository()
450 new_repo = Repository()
451 new_repo.repo_state = state
451 new_repo.repo_state = state
452 new_repo.enable_statistics = False
452 new_repo.enable_statistics = False
453 new_repo.repo_name = repo_name_full
453 new_repo.repo_name = repo_name_full
454 new_repo.repo_type = repo_type
454 new_repo.repo_type = repo_type
455 new_repo.user = owner
455 new_repo.user = owner
456 new_repo.group = repo_group
456 new_repo.group = repo_group
457 new_repo.description = description or repo_name
457 new_repo.description = description or repo_name
458 new_repo.private = private
458 new_repo.private = private
459 new_repo.clone_uri = clone_uri
459 new_repo.clone_uri = clone_uri
460 new_repo.landing_rev = landing_rev
460 new_repo.landing_rev = landing_rev
461
461
462 new_repo.enable_statistics = enable_statistics
462 new_repo.enable_statistics = enable_statistics
463 new_repo.enable_locking = enable_locking
463 new_repo.enable_locking = enable_locking
464 new_repo.enable_downloads = enable_downloads
464 new_repo.enable_downloads = enable_downloads
465
465
466 if repo_group:
466 if repo_group:
467 new_repo.enable_locking = repo_group.enable_locking
467 new_repo.enable_locking = repo_group.enable_locking
468
468
469 if fork_of:
469 if fork_of:
470 parent_repo = fork_of
470 parent_repo = fork_of
471 new_repo.fork = parent_repo
471 new_repo.fork = parent_repo
472
472
473 self.sa.add(new_repo)
473 self.sa.add(new_repo)
474
474
475 EMPTY_PERM = 'repository.none'
475 EMPTY_PERM = 'repository.none'
476 if fork_of and copy_fork_permissions:
476 if fork_of and copy_fork_permissions:
477 repo = fork_of
477 repo = fork_of
478 user_perms = UserRepoToPerm.query() \
478 user_perms = UserRepoToPerm.query() \
479 .filter(UserRepoToPerm.repository == repo).all()
479 .filter(UserRepoToPerm.repository == repo).all()
480 group_perms = UserGroupRepoToPerm.query() \
480 group_perms = UserGroupRepoToPerm.query() \
481 .filter(UserGroupRepoToPerm.repository == repo).all()
481 .filter(UserGroupRepoToPerm.repository == repo).all()
482
482
483 for perm in user_perms:
483 for perm in user_perms:
484 UserRepoToPerm.create(
484 UserRepoToPerm.create(
485 perm.user, new_repo, perm.permission)
485 perm.user, new_repo, perm.permission)
486
486
487 for perm in group_perms:
487 for perm in group_perms:
488 UserGroupRepoToPerm.create(
488 UserGroupRepoToPerm.create(
489 perm.users_group, new_repo, perm.permission)
489 perm.users_group, new_repo, perm.permission)
490 # in case we copy permissions and also set this repo to private
490 # in case we copy permissions and also set this repo to private
491 # override the default user permission to make it a private
491 # override the default user permission to make it a private
492 # repo
492 # repo
493 if private:
493 if private:
494 RepoModel(self.sa).grant_user_permission(
494 RepoModel(self.sa).grant_user_permission(
495 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
495 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
496
496
497 elif repo_group and copy_group_permissions:
497 elif repo_group and copy_group_permissions:
498 user_perms = UserRepoGroupToPerm.query() \
498 user_perms = UserRepoGroupToPerm.query() \
499 .filter(UserRepoGroupToPerm.group == repo_group).all()
499 .filter(UserRepoGroupToPerm.group == repo_group).all()
500
500
501 group_perms = UserGroupRepoGroupToPerm.query() \
501 group_perms = UserGroupRepoGroupToPerm.query() \
502 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
502 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
503
503
504 for perm in user_perms:
504 for perm in user_perms:
505 perm_name = perm.permission.permission_name.replace(
505 perm_name = perm.permission.permission_name.replace(
506 'group.', 'repository.')
506 'group.', 'repository.')
507 perm_obj = Permission.get_by_key(perm_name)
507 perm_obj = Permission.get_by_key(perm_name)
508 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
508 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
509
509
510 for perm in group_perms:
510 for perm in group_perms:
511 perm_name = perm.permission.permission_name.replace(
511 perm_name = perm.permission.permission_name.replace(
512 'group.', 'repository.')
512 'group.', 'repository.')
513 perm_obj = Permission.get_by_key(perm_name)
513 perm_obj = Permission.get_by_key(perm_name)
514 UserGroupRepoToPerm.create(
514 UserGroupRepoToPerm.create(
515 perm.users_group, new_repo, perm_obj)
515 perm.users_group, new_repo, perm_obj)
516
516
517 if private:
517 if private:
518 RepoModel(self.sa).grant_user_permission(
518 RepoModel(self.sa).grant_user_permission(
519 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
519 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
520
520
521 else:
521 else:
522 perm_obj = self._create_default_perms(new_repo, private)
522 perm_obj = self._create_default_perms(new_repo, private)
523 self.sa.add(perm_obj)
523 self.sa.add(perm_obj)
524
524
525 # now automatically start following this repository as owner
525 # now automatically start following this repository as owner
526 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
526 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
527 owner.user_id)
527 owner.user_id)
528 # we need to flush here, in order to check if database won't
528 # we need to flush here, in order to check if database won't
529 # throw any exceptions, create filesystem dirs at the very end
529 # throw any exceptions, create filesystem dirs at the very end
530 self.sa.flush()
530 self.sa.flush()
531
531
532 return new_repo
532 return new_repo
533 except Exception:
533 except Exception:
534 log.error(traceback.format_exc())
534 log.error(traceback.format_exc())
535 raise
535 raise
536
536
537 def create(self, form_data, cur_user):
537 def create(self, form_data, cur_user):
538 """
538 """
539 Create repository using celery tasks
539 Create repository using celery tasks
540
540
541 :param form_data:
541 :param form_data:
542 :param cur_user:
542 :param cur_user:
543 """
543 """
544 from rhodecode.lib.celerylib import tasks, run_task
544 from rhodecode.lib.celerylib import tasks, run_task
545 return run_task(tasks.create_repo, form_data, cur_user)
545 return run_task(tasks.create_repo, form_data, cur_user)
546
546
547 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
547 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
548 perm_deletions=None, check_perms=True,
548 perm_deletions=None, check_perms=True,
549 cur_user=None):
549 cur_user=None):
550 if not perm_additions:
550 if not perm_additions:
551 perm_additions = []
551 perm_additions = []
552 if not perm_updates:
552 if not perm_updates:
553 perm_updates = []
553 perm_updates = []
554 if not perm_deletions:
554 if not perm_deletions:
555 perm_deletions = []
555 perm_deletions = []
556
556
557 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
557 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
558
558
559 # update permissions
559 # update permissions
560 for member_id, perm, member_type in perm_updates:
560 for member_id, perm, member_type in perm_updates:
561 member_id = int(member_id)
561 member_id = int(member_id)
562 if member_type == 'user':
562 if member_type == 'user':
563 # this updates also current one if found
563 # this updates also current one if found
564 self.grant_user_permission(
564 self.grant_user_permission(
565 repo=repo, user=member_id, perm=perm)
565 repo=repo, user=member_id, perm=perm)
566 else: # set for user group
566 else: # set for user group
567 # check if we have permissions to alter this usergroup
567 # check if we have permissions to alter this usergroup
568 member_name = UserGroup.get(member_id).users_group_name
568 member_name = UserGroup.get(member_id).users_group_name
569 if not check_perms or HasUserGroupPermissionAny(
569 if not check_perms or HasUserGroupPermissionAny(
570 *req_perms)(member_name, user=cur_user):
570 *req_perms)(member_name, user=cur_user):
571 self.grant_user_group_permission(
571 self.grant_user_group_permission(
572 repo=repo, group_name=member_id, perm=perm)
572 repo=repo, group_name=member_id, perm=perm)
573
573
574 # set new permissions
574 # set new permissions
575 for member_id, perm, member_type in perm_additions:
575 for member_id, perm, member_type in perm_additions:
576 member_id = int(member_id)
576 member_id = int(member_id)
577 if member_type == 'user':
577 if member_type == 'user':
578 self.grant_user_permission(
578 self.grant_user_permission(
579 repo=repo, user=member_id, perm=perm)
579 repo=repo, user=member_id, perm=perm)
580 else: # set for user group
580 else: # set for user group
581 # check if we have permissions to alter this usergroup
581 # check if we have permissions to alter this usergroup
582 member_name = UserGroup.get(member_id).users_group_name
582 member_name = UserGroup.get(member_id).users_group_name
583 if not check_perms or HasUserGroupPermissionAny(
583 if not check_perms or HasUserGroupPermissionAny(
584 *req_perms)(member_name, user=cur_user):
584 *req_perms)(member_name, user=cur_user):
585 self.grant_user_group_permission(
585 self.grant_user_group_permission(
586 repo=repo, group_name=member_id, perm=perm)
586 repo=repo, group_name=member_id, perm=perm)
587
587
588 # delete permissions
588 # delete permissions
589 for member_id, perm, member_type in perm_deletions:
589 for member_id, perm, member_type in perm_deletions:
590 member_id = int(member_id)
590 member_id = int(member_id)
591 if member_type == 'user':
591 if member_type == 'user':
592 self.revoke_user_permission(repo=repo, user=member_id)
592 self.revoke_user_permission(repo=repo, user=member_id)
593 else: # set for user group
593 else: # set for user group
594 # check if we have permissions to alter this usergroup
594 # check if we have permissions to alter this usergroup
595 member_name = UserGroup.get(member_id).users_group_name
595 member_name = UserGroup.get(member_id).users_group_name
596 if not check_perms or HasUserGroupPermissionAny(
596 if not check_perms or HasUserGroupPermissionAny(
597 *req_perms)(member_name, user=cur_user):
597 *req_perms)(member_name, user=cur_user):
598 self.revoke_user_group_permission(
598 self.revoke_user_group_permission(
599 repo=repo, group_name=member_id)
599 repo=repo, group_name=member_id)
600
600
601 def create_fork(self, form_data, cur_user):
601 def create_fork(self, form_data, cur_user):
602 """
602 """
603 Simple wrapper into executing celery task for fork creation
603 Simple wrapper into executing celery task for fork creation
604
604
605 :param form_data:
605 :param form_data:
606 :param cur_user:
606 :param cur_user:
607 """
607 """
608 from rhodecode.lib.celerylib import tasks, run_task
608 from rhodecode.lib.celerylib import tasks, run_task
609 return run_task(tasks.create_repo_fork, form_data, cur_user)
609 return run_task(tasks.create_repo_fork, form_data, cur_user)
610
610
611 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
611 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
612 """
612 """
613 Delete given repository, forks parameter defines what do do with
613 Delete given repository, forks parameter defines what do do with
614 attached forks. Throws AttachedForksError if deleted repo has attached
614 attached forks. Throws AttachedForksError if deleted repo has attached
615 forks
615 forks
616
616
617 :param repo:
617 :param repo:
618 :param forks: str 'delete' or 'detach'
618 :param forks: str 'delete' or 'detach'
619 :param fs_remove: remove(archive) repo from filesystem
619 :param fs_remove: remove(archive) repo from filesystem
620 """
620 """
621 if not cur_user:
621 if not cur_user:
622 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
622 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
623 repo = self._get_repo(repo)
623 repo = self._get_repo(repo)
624 if repo:
624 if repo:
625 if forks == 'detach':
625 if forks == 'detach':
626 for r in repo.forks:
626 for r in repo.forks:
627 r.fork = None
627 r.fork = None
628 self.sa.add(r)
628 self.sa.add(r)
629 elif forks == 'delete':
629 elif forks == 'delete':
630 for r in repo.forks:
630 for r in repo.forks:
631 self.delete(r, forks='delete')
631 self.delete(r, forks='delete')
632 elif [f for f in repo.forks]:
632 elif [f for f in repo.forks]:
633 raise AttachedForksError()
633 raise AttachedForksError()
634
634
635 old_repo_dict = repo.get_dict()
635 old_repo_dict = repo.get_dict()
636 try:
636 try:
637 self.sa.delete(repo)
637 self.sa.delete(repo)
638 if fs_remove:
638 if fs_remove:
639 self._delete_filesystem_repo(repo)
639 self._delete_filesystem_repo(repo)
640 else:
640 else:
641 log.debug('skipping removal from filesystem')
641 log.debug('skipping removal from filesystem')
642 old_repo_dict.update({
642 old_repo_dict.update({
643 'deleted_by': cur_user,
643 'deleted_by': cur_user,
644 'deleted_on': time.time(),
644 'deleted_on': time.time(),
645 })
645 })
646 log_delete_repository(**old_repo_dict)
646 log_delete_repository(**old_repo_dict)
647 except Exception:
647 except Exception:
648 log.error(traceback.format_exc())
648 log.error(traceback.format_exc())
649 raise
649 raise
650
650
651 def grant_user_permission(self, repo, user, perm):
651 def grant_user_permission(self, repo, user, perm):
652 """
652 """
653 Grant permission for user on given repository, or update existing one
653 Grant permission for user on given repository, or update existing one
654 if found
654 if found
655
655
656 :param repo: Instance of Repository, repository_id, or repository name
656 :param repo: Instance of Repository, repository_id, or repository name
657 :param user: Instance of User, user_id or username
657 :param user: Instance of User, user_id or username
658 :param perm: Instance of Permission, or permission_name
658 :param perm: Instance of Permission, or permission_name
659 """
659 """
660 user = self._get_user(user)
660 user = self._get_user(user)
661 repo = self._get_repo(repo)
661 repo = self._get_repo(repo)
662 permission = self._get_perm(perm)
662 permission = self._get_perm(perm)
663
663
664 # check if we have that permission already
664 # check if we have that permission already
665 obj = self.sa.query(UserRepoToPerm) \
665 obj = self.sa.query(UserRepoToPerm) \
666 .filter(UserRepoToPerm.user == user) \
666 .filter(UserRepoToPerm.user == user) \
667 .filter(UserRepoToPerm.repository == repo) \
667 .filter(UserRepoToPerm.repository == repo) \
668 .scalar()
668 .scalar()
669 if obj is None:
669 if obj is None:
670 # create new !
670 # create new !
671 obj = UserRepoToPerm()
671 obj = UserRepoToPerm()
672 obj.repository = repo
672 obj.repository = repo
673 obj.user = user
673 obj.user = user
674 obj.permission = permission
674 obj.permission = permission
675 self.sa.add(obj)
675 self.sa.add(obj)
676 log.debug('Granted perm %s to %s on %s', perm, user, repo)
676 log.debug('Granted perm %s to %s on %s', perm, user, repo)
677 action_logger_generic(
677 action_logger_generic(
678 'granted permission: {} to user: {} on repo: {}'.format(
678 'granted permission: {} to user: {} on repo: {}'.format(
679 perm, user, repo), namespace='security.repo')
679 perm, user, repo), namespace='security.repo')
680 return obj
680 return obj
681
681
682 def revoke_user_permission(self, repo, user):
682 def revoke_user_permission(self, repo, user):
683 """
683 """
684 Revoke permission for user on given repository
684 Revoke permission for user on given repository
685
685
686 :param repo: Instance of Repository, repository_id, or repository name
686 :param repo: Instance of Repository, repository_id, or repository name
687 :param user: Instance of User, user_id or username
687 :param user: Instance of User, user_id or username
688 """
688 """
689
689
690 user = self._get_user(user)
690 user = self._get_user(user)
691 repo = self._get_repo(repo)
691 repo = self._get_repo(repo)
692
692
693 obj = self.sa.query(UserRepoToPerm) \
693 obj = self.sa.query(UserRepoToPerm) \
694 .filter(UserRepoToPerm.repository == repo) \
694 .filter(UserRepoToPerm.repository == repo) \
695 .filter(UserRepoToPerm.user == user) \
695 .filter(UserRepoToPerm.user == user) \
696 .scalar()
696 .scalar()
697 if obj:
697 if obj:
698 self.sa.delete(obj)
698 self.sa.delete(obj)
699 log.debug('Revoked perm on %s on %s', repo, user)
699 log.debug('Revoked perm on %s on %s', repo, user)
700 action_logger_generic(
700 action_logger_generic(
701 'revoked permission from user: {} on repo: {}'.format(
701 'revoked permission from user: {} on repo: {}'.format(
702 user, repo), namespace='security.repo')
702 user, repo), namespace='security.repo')
703
703
704 def grant_user_group_permission(self, repo, group_name, perm):
704 def grant_user_group_permission(self, repo, group_name, perm):
705 """
705 """
706 Grant permission for user group on given repository, or update
706 Grant permission for user group on given repository, or update
707 existing one if found
707 existing one if found
708
708
709 :param repo: Instance of Repository, repository_id, or repository name
709 :param repo: Instance of Repository, repository_id, or repository name
710 :param group_name: Instance of UserGroup, users_group_id,
710 :param group_name: Instance of UserGroup, users_group_id,
711 or user group name
711 or user group name
712 :param perm: Instance of Permission, or permission_name
712 :param perm: Instance of Permission, or permission_name
713 """
713 """
714 repo = self._get_repo(repo)
714 repo = self._get_repo(repo)
715 group_name = self._get_user_group(group_name)
715 group_name = self._get_user_group(group_name)
716 permission = self._get_perm(perm)
716 permission = self._get_perm(perm)
717
717
718 # check if we have that permission already
718 # check if we have that permission already
719 obj = self.sa.query(UserGroupRepoToPerm) \
719 obj = self.sa.query(UserGroupRepoToPerm) \
720 .filter(UserGroupRepoToPerm.users_group == group_name) \
720 .filter(UserGroupRepoToPerm.users_group == group_name) \
721 .filter(UserGroupRepoToPerm.repository == repo) \
721 .filter(UserGroupRepoToPerm.repository == repo) \
722 .scalar()
722 .scalar()
723
723
724 if obj is None:
724 if obj is None:
725 # create new
725 # create new
726 obj = UserGroupRepoToPerm()
726 obj = UserGroupRepoToPerm()
727
727
728 obj.repository = repo
728 obj.repository = repo
729 obj.users_group = group_name
729 obj.users_group = group_name
730 obj.permission = permission
730 obj.permission = permission
731 self.sa.add(obj)
731 self.sa.add(obj)
732 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
732 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
733 action_logger_generic(
733 action_logger_generic(
734 'granted permission: {} to usergroup: {} on repo: {}'.format(
734 'granted permission: {} to usergroup: {} on repo: {}'.format(
735 perm, group_name, repo), namespace='security.repo')
735 perm, group_name, repo), namespace='security.repo')
736
736
737 return obj
737 return obj
738
738
739 def revoke_user_group_permission(self, repo, group_name):
739 def revoke_user_group_permission(self, repo, group_name):
740 """
740 """
741 Revoke permission for user group on given repository
741 Revoke permission for user group on given repository
742
742
743 :param repo: Instance of Repository, repository_id, or repository name
743 :param repo: Instance of Repository, repository_id, or repository name
744 :param group_name: Instance of UserGroup, users_group_id,
744 :param group_name: Instance of UserGroup, users_group_id,
745 or user group name
745 or user group name
746 """
746 """
747 repo = self._get_repo(repo)
747 repo = self._get_repo(repo)
748 group_name = self._get_user_group(group_name)
748 group_name = self._get_user_group(group_name)
749
749
750 obj = self.sa.query(UserGroupRepoToPerm) \
750 obj = self.sa.query(UserGroupRepoToPerm) \
751 .filter(UserGroupRepoToPerm.repository == repo) \
751 .filter(UserGroupRepoToPerm.repository == repo) \
752 .filter(UserGroupRepoToPerm.users_group == group_name) \
752 .filter(UserGroupRepoToPerm.users_group == group_name) \
753 .scalar()
753 .scalar()
754 if obj:
754 if obj:
755 self.sa.delete(obj)
755 self.sa.delete(obj)
756 log.debug('Revoked perm to %s on %s', repo, group_name)
756 log.debug('Revoked perm to %s on %s', repo, group_name)
757 action_logger_generic(
757 action_logger_generic(
758 'revoked permission from usergroup: {} on repo: {}'.format(
758 'revoked permission from usergroup: {} on repo: {}'.format(
759 group_name, repo), namespace='security.repo')
759 group_name, repo), namespace='security.repo')
760
760
761 def delete_stats(self, repo_name):
761 def delete_stats(self, repo_name):
762 """
762 """
763 removes stats for given repo
763 removes stats for given repo
764
764
765 :param repo_name:
765 :param repo_name:
766 """
766 """
767 repo = self._get_repo(repo_name)
767 repo = self._get_repo(repo_name)
768 try:
768 try:
769 obj = self.sa.query(Statistics) \
769 obj = self.sa.query(Statistics) \
770 .filter(Statistics.repository == repo).scalar()
770 .filter(Statistics.repository == repo).scalar()
771 if obj:
771 if obj:
772 self.sa.delete(obj)
772 self.sa.delete(obj)
773 except Exception:
773 except Exception:
774 log.error(traceback.format_exc())
774 log.error(traceback.format_exc())
775 raise
775 raise
776
776
777 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
777 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
778 field_type='str', field_desc=''):
778 field_type='str', field_desc=''):
779
779
780 repo = self._get_repo(repo_name)
780 repo = self._get_repo(repo_name)
781
781
782 new_field = RepositoryField()
782 new_field = RepositoryField()
783 new_field.repository = repo
783 new_field.repository = repo
784 new_field.field_key = field_key
784 new_field.field_key = field_key
785 new_field.field_type = field_type # python type
785 new_field.field_type = field_type # python type
786 new_field.field_value = field_value
786 new_field.field_value = field_value
787 new_field.field_desc = field_desc
787 new_field.field_desc = field_desc
788 new_field.field_label = field_label
788 new_field.field_label = field_label
789 self.sa.add(new_field)
789 self.sa.add(new_field)
790 return new_field
790 return new_field
791
791
792 def delete_repo_field(self, repo_name, field_key):
792 def delete_repo_field(self, repo_name, field_key):
793 repo = self._get_repo(repo_name)
793 repo = self._get_repo(repo_name)
794 field = RepositoryField.get_by_key_name(field_key, repo)
794 field = RepositoryField.get_by_key_name(field_key, repo)
795 if field:
795 if field:
796 self.sa.delete(field)
796 self.sa.delete(field)
797
797
798 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
798 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
799 clone_uri=None, repo_store_location=None,
799 clone_uri=None, repo_store_location=None,
800 use_global_config=False):
800 use_global_config=False):
801 """
801 """
802 makes repository on filesystem. It's group aware means it'll create
802 makes repository on filesystem. It's group aware means it'll create
803 a repository within a group, and alter the paths accordingly of
803 a repository within a group, and alter the paths accordingly of
804 group location
804 group location
805
805
806 :param repo_name:
806 :param repo_name:
807 :param alias:
807 :param alias:
808 :param parent:
808 :param parent:
809 :param clone_uri:
809 :param clone_uri:
810 :param repo_store_location:
810 :param repo_store_location:
811 """
811 """
812 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
812 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
813 from rhodecode.model.scm import ScmModel
813 from rhodecode.model.scm import ScmModel
814
814
815 if Repository.NAME_SEP in repo_name:
815 if Repository.NAME_SEP in repo_name:
816 raise ValueError(
816 raise ValueError(
817 'repo_name must not contain groups got `%s`' % repo_name)
817 'repo_name must not contain groups got `%s`' % repo_name)
818
818
819 if isinstance(repo_group, RepoGroup):
819 if isinstance(repo_group, RepoGroup):
820 new_parent_path = os.sep.join(repo_group.full_path_splitted)
820 new_parent_path = os.sep.join(repo_group.full_path_splitted)
821 else:
821 else:
822 new_parent_path = repo_group or ''
822 new_parent_path = repo_group or ''
823
823
824 if repo_store_location:
824 if repo_store_location:
825 _paths = [repo_store_location]
825 _paths = [repo_store_location]
826 else:
826 else:
827 _paths = [self.repos_path, new_parent_path, repo_name]
827 _paths = [self.repos_path, new_parent_path, repo_name]
828 # we need to make it str for mercurial
828 # we need to make it str for mercurial
829 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
829 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
830
830
831 # check if this path is not a repository
831 # check if this path is not a repository
832 if is_valid_repo(repo_path, self.repos_path):
832 if is_valid_repo(repo_path, self.repos_path):
833 raise Exception('This path %s is a valid repository' % repo_path)
833 raise Exception('This path %s is a valid repository' % repo_path)
834
834
835 # check if this path is a group
835 # check if this path is a group
836 if is_valid_repo_group(repo_path, self.repos_path):
836 if is_valid_repo_group(repo_path, self.repos_path):
837 raise Exception('This path %s is a valid group' % repo_path)
837 raise Exception('This path %s is a valid group' % repo_path)
838
838
839 log.info('creating repo %s in %s from url: `%s`',
839 log.info('creating repo %s in %s from url: `%s`',
840 repo_name, safe_unicode(repo_path),
840 repo_name, safe_unicode(repo_path),
841 obfuscate_url_pw(clone_uri))
841 obfuscate_url_pw(clone_uri))
842
842
843 backend = get_backend(repo_type)
843 backend = get_backend(repo_type)
844
844
845 config_repo = None if use_global_config else repo_name
845 config_repo = None if use_global_config else repo_name
846 if config_repo and new_parent_path:
846 if config_repo and new_parent_path:
847 config_repo = Repository.NAME_SEP.join(
847 config_repo = Repository.NAME_SEP.join(
848 (new_parent_path, config_repo))
848 (new_parent_path, config_repo))
849 config = make_db_config(clear_session=False, repo=config_repo)
849 config = make_db_config(clear_session=False, repo=config_repo)
850 config.set('extensions', 'largefiles', '')
850 config.set('extensions', 'largefiles', '')
851
851
852 # patch and reset hooks section of UI config to not run any
852 # patch and reset hooks section of UI config to not run any
853 # hooks on creating remote repo
853 # hooks on creating remote repo
854 config.clear_section('hooks')
854 config.clear_section('hooks')
855
855
856 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
856 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
857 if repo_type == 'git':
857 if repo_type == 'git':
858 repo = backend(
858 repo = backend(
859 repo_path, config=config, create=True, src_url=clone_uri,
859 repo_path, config=config, create=True, src_url=clone_uri,
860 bare=True)
860 bare=True)
861 else:
861 else:
862 repo = backend(
862 repo = backend(
863 repo_path, config=config, create=True, src_url=clone_uri)
863 repo_path, config=config, create=True, src_url=clone_uri)
864
864
865 ScmModel().install_hooks(repo, repo_type=repo_type)
865 ScmModel().install_hooks(repo, repo_type=repo_type)
866
866
867 log.debug('Created repo %s with %s backend',
867 log.debug('Created repo %s with %s backend',
868 safe_unicode(repo_name), safe_unicode(repo_type))
868 safe_unicode(repo_name), safe_unicode(repo_type))
869 return repo
869 return repo
870
870
871 def _rename_filesystem_repo(self, old, new):
871 def _rename_filesystem_repo(self, old, new):
872 """
872 """
873 renames repository on filesystem
873 renames repository on filesystem
874
874
875 :param old: old name
875 :param old: old name
876 :param new: new name
876 :param new: new name
877 """
877 """
878 log.info('renaming repo from %s to %s', old, new)
878 log.info('renaming repo from %s to %s', old, new)
879
879
880 old_path = os.path.join(self.repos_path, old)
880 old_path = os.path.join(self.repos_path, old)
881 new_path = os.path.join(self.repos_path, new)
881 new_path = os.path.join(self.repos_path, new)
882 if os.path.isdir(new_path):
882 if os.path.isdir(new_path):
883 raise Exception(
883 raise Exception(
884 'Was trying to rename to already existing dir %s' % new_path
884 'Was trying to rename to already existing dir %s' % new_path
885 )
885 )
886 shutil.move(old_path, new_path)
886 shutil.move(old_path, new_path)
887
887
888 def _delete_filesystem_repo(self, repo):
888 def _delete_filesystem_repo(self, repo):
889 """
889 """
890 removes repo from filesystem, the removal is acctually made by
890 removes repo from filesystem, the removal is acctually made by
891 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
891 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
892 repository is no longer valid for rhodecode, can be undeleted later on
892 repository is no longer valid for rhodecode, can be undeleted later on
893 by reverting the renames on this repository
893 by reverting the renames on this repository
894
894
895 :param repo: repo object
895 :param repo: repo object
896 """
896 """
897 rm_path = os.path.join(self.repos_path, repo.repo_name)
897 rm_path = os.path.join(self.repos_path, repo.repo_name)
898 repo_group = repo.group
898 repo_group = repo.group
899 log.info("Removing repository %s", rm_path)
899 log.info("Removing repository %s", rm_path)
900 # disable hg/git internal that it doesn't get detected as repo
900 # disable hg/git internal that it doesn't get detected as repo
901 alias = repo.repo_type
901 alias = repo.repo_type
902
902
903 config = make_db_config(clear_session=False)
903 config = make_db_config(clear_session=False)
904 config.set('extensions', 'largefiles', '')
904 config.set('extensions', 'largefiles', '')
905 bare = getattr(repo.scm_instance(config=config), 'bare', False)
905 bare = getattr(repo.scm_instance(config=config), 'bare', False)
906
906
907 # skip this for bare git repos
907 # skip this for bare git repos
908 if not bare:
908 if not bare:
909 # disable VCS repo
909 # disable VCS repo
910 vcs_path = os.path.join(rm_path, '.%s' % alias)
910 vcs_path = os.path.join(rm_path, '.%s' % alias)
911 if os.path.exists(vcs_path):
911 if os.path.exists(vcs_path):
912 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
912 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
913
913
914 _now = datetime.now()
914 _now = datetime.now()
915 _ms = str(_now.microsecond).rjust(6, '0')
915 _ms = str(_now.microsecond).rjust(6, '0')
916 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
916 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
917 repo.just_name)
917 repo.just_name)
918 if repo_group:
918 if repo_group:
919 # if repository is in group, prefix the removal path with the group
919 # if repository is in group, prefix the removal path with the group
920 args = repo_group.full_path_splitted + [_d]
920 args = repo_group.full_path_splitted + [_d]
921 _d = os.path.join(*args)
921 _d = os.path.join(*args)
922
922
923 if os.path.isdir(rm_path):
923 if os.path.isdir(rm_path):
924 shutil.move(rm_path, os.path.join(self.repos_path, _d))
924 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,838 +1,838 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27
27
28 import datetime
28 import datetime
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 import ipaddress
31 import ipaddress
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.sql.expression import true, false
33 from sqlalchemy.sql.expression import true, false
34
34
35 from rhodecode.events import UserPreCreate, UserPreUpdate
35 from rhodecode.events import UserPreCreate, UserPreUpdate
36 from rhodecode.lib.utils2 import (
36 from rhodecode.lib.utils2 import (
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 AttributeDict)
38 AttributeDict)
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 User, UserToPerm, UserEmailMap, UserIpMap)
43 User, UserToPerm, UserEmailMap, UserIpMap)
44 from rhodecode.lib.exceptions import (
44 from rhodecode.lib.exceptions import (
45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(FromCache("sql_cache_short",
60 user = user.options(FromCache("sql_cache_short",
61 "get_user_%s" % user_id))
61 "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def get_by_username(self, username, cache=False, case_insensitive=False):
67 def get_by_username(self, username, cache=False, case_insensitive=False):
68
68
69 if case_insensitive:
69 if case_insensitive:
70 user = self.sa.query(User).filter(User.username.ilike(username))
70 user = self.sa.query(User).filter(User.username.ilike(username))
71 else:
71 else:
72 user = self.sa.query(User)\
72 user = self.sa.query(User)\
73 .filter(User.username == username)
73 .filter(User.username == username)
74 if cache:
74 if cache:
75 user = user.options(FromCache("sql_cache_short",
75 user = user.options(FromCache("sql_cache_short",
76 "get_user_%s" % username))
76 "get_user_%s" % username))
77 return user.scalar()
77 return user.scalar()
78
78
79 def get_by_email(self, email, cache=False, case_insensitive=False):
79 def get_by_email(self, email, cache=False, case_insensitive=False):
80 return User.get_by_email(email, case_insensitive, cache)
80 return User.get_by_email(email, case_insensitive, cache)
81
81
82 def get_by_auth_token(self, auth_token, cache=False):
82 def get_by_auth_token(self, auth_token, cache=False):
83 return User.get_by_auth_token(auth_token, cache)
83 return User.get_by_auth_token(auth_token, cache)
84
84
85 def get_active_user_count(self, cache=False):
85 def get_active_user_count(self, cache=False):
86 return User.query().filter(
86 return User.query().filter(
87 User.active == True).filter(
87 User.active == True).filter(
88 User.username != User.DEFAULT_USER).count()
88 User.username != User.DEFAULT_USER).count()
89
89
90 def create(self, form_data, cur_user=None):
90 def create(self, form_data, cur_user=None):
91 if not cur_user:
91 if not cur_user:
92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
93
93
94 user_data = {
94 user_data = {
95 'username': form_data['username'],
95 'username': form_data['username'],
96 'password': form_data['password'],
96 'password': form_data['password'],
97 'email': form_data['email'],
97 'email': form_data['email'],
98 'firstname': form_data['firstname'],
98 'firstname': form_data['firstname'],
99 'lastname': form_data['lastname'],
99 'lastname': form_data['lastname'],
100 'active': form_data['active'],
100 'active': form_data['active'],
101 'extern_type': form_data['extern_type'],
101 'extern_type': form_data['extern_type'],
102 'extern_name': form_data['extern_name'],
102 'extern_name': form_data['extern_name'],
103 'admin': False,
103 'admin': False,
104 'cur_user': cur_user
104 'cur_user': cur_user
105 }
105 }
106
106
107 try:
107 try:
108 if form_data.get('create_repo_group'):
108 if form_data.get('create_repo_group'):
109 user_data['create_repo_group'] = True
109 user_data['create_repo_group'] = True
110 if form_data.get('password_change'):
110 if form_data.get('password_change'):
111 user_data['force_password_change'] = True
111 user_data['force_password_change'] = True
112
112
113 return UserModel().create_or_update(**user_data)
113 return UserModel().create_or_update(**user_data)
114 except Exception:
114 except Exception:
115 log.error(traceback.format_exc())
115 log.error(traceback.format_exc())
116 raise
116 raise
117
117
118 def update_user(self, user, skip_attrs=None, **kwargs):
118 def update_user(self, user, skip_attrs=None, **kwargs):
119 from rhodecode.lib.auth import get_crypt_password
119 from rhodecode.lib.auth import get_crypt_password
120
120
121 user = self._get_user(user)
121 user = self._get_user(user)
122 if user.username == User.DEFAULT_USER:
122 if user.username == User.DEFAULT_USER:
123 raise DefaultUserException(
123 raise DefaultUserException(
124 _("You can't Edit this user since it's"
124 _("You can't Edit this user since it's"
125 " crucial for entire application"))
125 " crucial for entire application"))
126
126
127 # first store only defaults
127 # first store only defaults
128 user_attrs = {
128 user_attrs = {
129 'updating_user_id': user.user_id,
129 'updating_user_id': user.user_id,
130 'username': user.username,
130 'username': user.username,
131 'password': user.password,
131 'password': user.password,
132 'email': user.email,
132 'email': user.email,
133 'firstname': user.name,
133 'firstname': user.name,
134 'lastname': user.lastname,
134 'lastname': user.lastname,
135 'active': user.active,
135 'active': user.active,
136 'admin': user.admin,
136 'admin': user.admin,
137 'extern_name': user.extern_name,
137 'extern_name': user.extern_name,
138 'extern_type': user.extern_type,
138 'extern_type': user.extern_type,
139 'language': user.user_data.get('language')
139 'language': user.user_data.get('language')
140 }
140 }
141
141
142 # in case there's new_password, that comes from form, use it to
142 # in case there's new_password, that comes from form, use it to
143 # store password
143 # store password
144 if kwargs.get('new_password'):
144 if kwargs.get('new_password'):
145 kwargs['password'] = kwargs['new_password']
145 kwargs['password'] = kwargs['new_password']
146
146
147 # cleanups, my_account password change form
147 # cleanups, my_account password change form
148 kwargs.pop('current_password', None)
148 kwargs.pop('current_password', None)
149 kwargs.pop('new_password', None)
149 kwargs.pop('new_password', None)
150 kwargs.pop('new_password_confirmation', None)
150 kwargs.pop('new_password_confirmation', None)
151
151
152 # cleanups, user edit password change form
152 # cleanups, user edit password change form
153 kwargs.pop('password_confirmation', None)
153 kwargs.pop('password_confirmation', None)
154 kwargs.pop('password_change', None)
154 kwargs.pop('password_change', None)
155
155
156 # create repo group on user creation
156 # create repo group on user creation
157 kwargs.pop('create_repo_group', None)
157 kwargs.pop('create_repo_group', None)
158
158
159 # legacy forms send name, which is the firstname
159 # legacy forms send name, which is the firstname
160 firstname = kwargs.pop('name', None)
160 firstname = kwargs.pop('name', None)
161 if firstname:
161 if firstname:
162 kwargs['firstname'] = firstname
162 kwargs['firstname'] = firstname
163
163
164 for k, v in kwargs.items():
164 for k, v in kwargs.items():
165 # skip if we don't want to update this
165 # skip if we don't want to update this
166 if skip_attrs and k in skip_attrs:
166 if skip_attrs and k in skip_attrs:
167 continue
167 continue
168
168
169 user_attrs[k] = v
169 user_attrs[k] = v
170
170
171 try:
171 try:
172 return self.create_or_update(**user_attrs)
172 return self.create_or_update(**user_attrs)
173 except Exception:
173 except Exception:
174 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
175 raise
175 raise
176
176
177 def create_or_update(
177 def create_or_update(
178 self, username, password, email, firstname='', lastname='',
178 self, username, password, email, firstname='', lastname='',
179 active=True, admin=False, extern_type=None, extern_name=None,
179 active=True, admin=False, extern_type=None, extern_name=None,
180 cur_user=None, plugin=None, force_password_change=False,
180 cur_user=None, plugin=None, force_password_change=False,
181 allow_to_create_user=True, create_repo_group=False,
181 allow_to_create_user=True, create_repo_group=False,
182 updating_user_id=None, language=None, strict_creation_check=True):
182 updating_user_id=None, language=None, strict_creation_check=True):
183 """
183 """
184 Creates a new instance if not found, or updates current one
184 Creates a new instance if not found, or updates current one
185
185
186 :param username:
186 :param username:
187 :param password:
187 :param password:
188 :param email:
188 :param email:
189 :param firstname:
189 :param firstname:
190 :param lastname:
190 :param lastname:
191 :param active:
191 :param active:
192 :param admin:
192 :param admin:
193 :param extern_type:
193 :param extern_type:
194 :param extern_name:
194 :param extern_name:
195 :param cur_user:
195 :param cur_user:
196 :param plugin: optional plugin this method was called from
196 :param plugin: optional plugin this method was called from
197 :param force_password_change: toggles new or existing user flag
197 :param force_password_change: toggles new or existing user flag
198 for password change
198 for password change
199 :param allow_to_create_user: Defines if the method can actually create
199 :param allow_to_create_user: Defines if the method can actually create
200 new users
200 new users
201 :param create_repo_group: Defines if the method should also
201 :param create_repo_group: Defines if the method should also
202 create an repo group with user name, and owner
202 create an repo group with user name, and owner
203 :param updating_user_id: if we set it up this is the user we want to
203 :param updating_user_id: if we set it up this is the user we want to
204 update this allows to editing username.
204 update this allows to editing username.
205 :param language: language of user from interface.
205 :param language: language of user from interface.
206
206
207 :returns: new User object with injected `is_new_user` attribute.
207 :returns: new User object with injected `is_new_user` attribute.
208 """
208 """
209 if not cur_user:
209 if not cur_user:
210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
211
211
212 from rhodecode.lib.auth import (
212 from rhodecode.lib.auth import (
213 get_crypt_password, check_password, generate_auth_token)
213 get_crypt_password, check_password, generate_auth_token)
214 from rhodecode.lib.hooks_base import (
214 from rhodecode.lib.hooks_base import (
215 log_create_user, check_allowed_create_user)
215 log_create_user, check_allowed_create_user)
216
216
217 def _password_change(new_user, password):
217 def _password_change(new_user, password):
218 # empty password
218 # empty password
219 if not new_user.password:
219 if not new_user.password:
220 return False
220 return False
221
221
222 # password check is only needed for RhodeCode internal auth calls
222 # password check is only needed for RhodeCode internal auth calls
223 # in case it's a plugin we don't care
223 # in case it's a plugin we don't care
224 if not plugin:
224 if not plugin:
225
225
226 # first check if we gave crypted password back, and if it matches
226 # first check if we gave crypted password back, and if it matches
227 # it's not password change
227 # it's not password change
228 if new_user.password == password:
228 if new_user.password == password:
229 return False
229 return False
230
230
231 password_match = check_password(password, new_user.password)
231 password_match = check_password(password, new_user.password)
232 if not password_match:
232 if not password_match:
233 return True
233 return True
234
234
235 return False
235 return False
236
236
237 user_data = {
237 user_data = {
238 'username': username,
238 'username': username,
239 'password': password,
239 'password': password,
240 'email': email,
240 'email': email,
241 'firstname': firstname,
241 'firstname': firstname,
242 'lastname': lastname,
242 'lastname': lastname,
243 'active': active,
243 'active': active,
244 'admin': admin
244 'admin': admin
245 }
245 }
246
246
247 if updating_user_id:
247 if updating_user_id:
248 log.debug('Checking for existing account in RhodeCode '
248 log.debug('Checking for existing account in RhodeCode '
249 'database with user_id `%s` ' % (updating_user_id,))
249 'database with user_id `%s` ' % (updating_user_id,))
250 user = User.get(updating_user_id)
250 user = User.get(updating_user_id)
251 else:
251 else:
252 log.debug('Checking for existing account in RhodeCode '
252 log.debug('Checking for existing account in RhodeCode '
253 'database with username `%s` ' % (username,))
253 'database with username `%s` ' % (username,))
254 user = User.get_by_username(username, case_insensitive=True)
254 user = User.get_by_username(username, case_insensitive=True)
255
255
256 if user is None:
256 if user is None:
257 # we check internal flag if this method is actually allowed to
257 # we check internal flag if this method is actually allowed to
258 # create new user
258 # create new user
259 if not allow_to_create_user:
259 if not allow_to_create_user:
260 msg = ('Method wants to create new user, but it is not '
260 msg = ('Method wants to create new user, but it is not '
261 'allowed to do so')
261 'allowed to do so')
262 log.warning(msg)
262 log.warning(msg)
263 raise NotAllowedToCreateUserError(msg)
263 raise NotAllowedToCreateUserError(msg)
264
264
265 log.debug('Creating new user %s', username)
265 log.debug('Creating new user %s', username)
266
266
267 # only if we create user that is active
267 # only if we create user that is active
268 new_active_user = active
268 new_active_user = active
269 if new_active_user and strict_creation_check:
269 if new_active_user and strict_creation_check:
270 # raises UserCreationError if it's not allowed for any reason to
270 # raises UserCreationError if it's not allowed for any reason to
271 # create new active user, this also executes pre-create hooks
271 # create new active user, this also executes pre-create hooks
272 check_allowed_create_user(user_data, cur_user, strict_check=True)
272 check_allowed_create_user(user_data, cur_user, strict_check=True)
273 self.send_event(UserPreCreate(user_data))
273 self.send_event(UserPreCreate(user_data))
274 new_user = User()
274 new_user = User()
275 edit = False
275 edit = False
276 else:
276 else:
277 log.debug('updating user %s', username)
277 log.debug('updating user %s', username)
278 self.send_event(UserPreUpdate(user, user_data))
278 self.send_event(UserPreUpdate(user, user_data))
279 new_user = user
279 new_user = user
280 edit = True
280 edit = True
281
281
282 # we're not allowed to edit default user
282 # we're not allowed to edit default user
283 if user.username == User.DEFAULT_USER:
283 if user.username == User.DEFAULT_USER:
284 raise DefaultUserException(
284 raise DefaultUserException(
285 _("You can't edit this user (`%(username)s`) since it's "
285 _("You can't edit this user (`%(username)s`) since it's "
286 "crucial for entire application") % {'username': user.username})
286 "crucial for entire application") % {'username': user.username})
287
287
288 # inject special attribute that will tell us if User is new or old
288 # inject special attribute that will tell us if User is new or old
289 new_user.is_new_user = not edit
289 new_user.is_new_user = not edit
290 # for users that didn's specify auth type, we use RhodeCode built in
290 # for users that didn's specify auth type, we use RhodeCode built in
291 from rhodecode.authentication.plugins import auth_rhodecode
291 from rhodecode.authentication.plugins import auth_rhodecode
292 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
292 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
293 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
293 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
294
294
295 try:
295 try:
296 new_user.username = username
296 new_user.username = username
297 new_user.admin = admin
297 new_user.admin = admin
298 new_user.email = email
298 new_user.email = email
299 new_user.active = active
299 new_user.active = active
300 new_user.extern_name = safe_unicode(extern_name)
300 new_user.extern_name = safe_unicode(extern_name)
301 new_user.extern_type = safe_unicode(extern_type)
301 new_user.extern_type = safe_unicode(extern_type)
302 new_user.name = firstname
302 new_user.name = firstname
303 new_user.lastname = lastname
303 new_user.lastname = lastname
304
304
305 if not edit:
305 if not edit:
306 new_user.api_key = generate_auth_token(username)
306 new_user.api_key = generate_auth_token(username)
307
307
308 # set password only if creating an user or password is changed
308 # set password only if creating an user or password is changed
309 if not edit or _password_change(new_user, password):
309 if not edit or _password_change(new_user, password):
310 reason = 'new password' if edit else 'new user'
310 reason = 'new password' if edit else 'new user'
311 log.debug('Updating password reason=>%s', reason)
311 log.debug('Updating password reason=>%s', reason)
312 new_user.password = get_crypt_password(password) if password else None
312 new_user.password = get_crypt_password(password) if password else None
313
313
314 if force_password_change:
314 if force_password_change:
315 new_user.update_userdata(force_password_change=True)
315 new_user.update_userdata(force_password_change=True)
316 if language:
316 if language:
317 new_user.update_userdata(language=language)
317 new_user.update_userdata(language=language)
318
318
319 self.sa.add(new_user)
319 self.sa.add(new_user)
320
320
321 if not edit and create_repo_group:
321 if not edit and create_repo_group:
322 # create new group same as username, and make this user an owner
322 # create new group same as username, and make this user an owner
323 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
323 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
324 RepoGroupModel().create(group_name=username,
324 RepoGroupModel().create(group_name=username,
325 group_description=desc,
325 group_description=desc,
326 owner=username, commit_early=False)
326 owner=username, commit_early=False)
327 if not edit:
327 if not edit:
328 # add the RSS token
328 # add the RSS token
329 AuthTokenModel().create(username,
329 AuthTokenModel().create(username,
330 description='Generated feed token',
330 description='Generated feed token',
331 role=AuthTokenModel.cls.ROLE_FEED)
331 role=AuthTokenModel.cls.ROLE_FEED)
332 log_create_user(created_by=cur_user, **new_user.get_dict())
332 log_create_user(created_by=cur_user, **new_user.get_dict())
333 return new_user
333 return new_user
334 except (DatabaseError,):
334 except (DatabaseError,):
335 log.error(traceback.format_exc())
335 log.error(traceback.format_exc())
336 raise
336 raise
337
337
338 def create_registration(self, form_data):
338 def create_registration(self, form_data):
339 from rhodecode.model.notification import NotificationModel
339 from rhodecode.model.notification import NotificationModel
340 from rhodecode.model.notification import EmailNotificationModel
340 from rhodecode.model.notification import EmailNotificationModel
341
341
342 try:
342 try:
343 form_data['admin'] = False
343 form_data['admin'] = False
344 form_data['extern_name'] = 'rhodecode'
344 form_data['extern_name'] = 'rhodecode'
345 form_data['extern_type'] = 'rhodecode'
345 form_data['extern_type'] = 'rhodecode'
346 new_user = self.create(form_data)
346 new_user = self.create(form_data)
347
347
348 self.sa.add(new_user)
348 self.sa.add(new_user)
349 self.sa.flush()
349 self.sa.flush()
350
350
351 user_data = new_user.get_dict()
351 user_data = new_user.get_dict()
352 kwargs = {
352 kwargs = {
353 # use SQLALCHEMY safe dump of user data
353 # use SQLALCHEMY safe dump of user data
354 'user': AttributeDict(user_data),
354 'user': AttributeDict(user_data),
355 'date': datetime.datetime.now()
355 'date': datetime.datetime.now()
356 }
356 }
357 notification_type = EmailNotificationModel.TYPE_REGISTRATION
357 notification_type = EmailNotificationModel.TYPE_REGISTRATION
358 # pre-generate the subject for notification itself
358 # pre-generate the subject for notification itself
359 (subject,
359 (subject,
360 _h, _e, # we don't care about those
360 _h, _e, # we don't care about those
361 body_plaintext) = EmailNotificationModel().render_email(
361 body_plaintext) = EmailNotificationModel().render_email(
362 notification_type, **kwargs)
362 notification_type, **kwargs)
363
363
364 # create notification objects, and emails
364 # create notification objects, and emails
365 NotificationModel().create(
365 NotificationModel().create(
366 created_by=new_user,
366 created_by=new_user,
367 notification_subject=subject,
367 notification_subject=subject,
368 notification_body=body_plaintext,
368 notification_body=body_plaintext,
369 notification_type=notification_type,
369 notification_type=notification_type,
370 recipients=None, # all admins
370 recipients=None, # all admins
371 email_kwargs=kwargs,
371 email_kwargs=kwargs,
372 )
372 )
373
373
374 return new_user
374 return new_user
375 except Exception:
375 except Exception:
376 log.error(traceback.format_exc())
376 log.error(traceback.format_exc())
377 raise
377 raise
378
378
379 def _handle_user_repos(self, username, repositories, handle_mode=None):
379 def _handle_user_repos(self, username, repositories, handle_mode=None):
380 _superadmin = self.cls.get_first_admin()
380 _superadmin = self.cls.get_first_super_admin()
381 left_overs = True
381 left_overs = True
382
382
383 from rhodecode.model.repo import RepoModel
383 from rhodecode.model.repo import RepoModel
384
384
385 if handle_mode == 'detach':
385 if handle_mode == 'detach':
386 for obj in repositories:
386 for obj in repositories:
387 obj.user = _superadmin
387 obj.user = _superadmin
388 # set description we know why we super admin now owns
388 # set description we know why we super admin now owns
389 # additional repositories that were orphaned !
389 # additional repositories that were orphaned !
390 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
390 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
391 self.sa.add(obj)
391 self.sa.add(obj)
392 left_overs = False
392 left_overs = False
393 elif handle_mode == 'delete':
393 elif handle_mode == 'delete':
394 for obj in repositories:
394 for obj in repositories:
395 RepoModel().delete(obj, forks='detach')
395 RepoModel().delete(obj, forks='detach')
396 left_overs = False
396 left_overs = False
397
397
398 # if nothing is done we have left overs left
398 # if nothing is done we have left overs left
399 return left_overs
399 return left_overs
400
400
401 def _handle_user_repo_groups(self, username, repository_groups,
401 def _handle_user_repo_groups(self, username, repository_groups,
402 handle_mode=None):
402 handle_mode=None):
403 _superadmin = self.cls.get_first_admin()
403 _superadmin = self.cls.get_first_super_admin()
404 left_overs = True
404 left_overs = True
405
405
406 from rhodecode.model.repo_group import RepoGroupModel
406 from rhodecode.model.repo_group import RepoGroupModel
407
407
408 if handle_mode == 'detach':
408 if handle_mode == 'detach':
409 for r in repository_groups:
409 for r in repository_groups:
410 r.user = _superadmin
410 r.user = _superadmin
411 # set description we know why we super admin now owns
411 # set description we know why we super admin now owns
412 # additional repositories that were orphaned !
412 # additional repositories that were orphaned !
413 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
413 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
414 self.sa.add(r)
414 self.sa.add(r)
415 left_overs = False
415 left_overs = False
416 elif handle_mode == 'delete':
416 elif handle_mode == 'delete':
417 for r in repository_groups:
417 for r in repository_groups:
418 RepoGroupModel().delete(r)
418 RepoGroupModel().delete(r)
419 left_overs = False
419 left_overs = False
420
420
421 # if nothing is done we have left overs left
421 # if nothing is done we have left overs left
422 return left_overs
422 return left_overs
423
423
424 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
424 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
425 _superadmin = self.cls.get_first_admin()
425 _superadmin = self.cls.get_first_super_admin()
426 left_overs = True
426 left_overs = True
427
427
428 from rhodecode.model.user_group import UserGroupModel
428 from rhodecode.model.user_group import UserGroupModel
429
429
430 if handle_mode == 'detach':
430 if handle_mode == 'detach':
431 for r in user_groups:
431 for r in user_groups:
432 for user_user_group_to_perm in r.user_user_group_to_perm:
432 for user_user_group_to_perm in r.user_user_group_to_perm:
433 if user_user_group_to_perm.user.username == username:
433 if user_user_group_to_perm.user.username == username:
434 user_user_group_to_perm.user = _superadmin
434 user_user_group_to_perm.user = _superadmin
435 r.user = _superadmin
435 r.user = _superadmin
436 # set description we know why we super admin now owns
436 # set description we know why we super admin now owns
437 # additional repositories that were orphaned !
437 # additional repositories that were orphaned !
438 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
438 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
439 self.sa.add(r)
439 self.sa.add(r)
440 left_overs = False
440 left_overs = False
441 elif handle_mode == 'delete':
441 elif handle_mode == 'delete':
442 for r in user_groups:
442 for r in user_groups:
443 UserGroupModel().delete(r)
443 UserGroupModel().delete(r)
444 left_overs = False
444 left_overs = False
445
445
446 # if nothing is done we have left overs left
446 # if nothing is done we have left overs left
447 return left_overs
447 return left_overs
448
448
449 def delete(self, user, cur_user=None, handle_repos=None,
449 def delete(self, user, cur_user=None, handle_repos=None,
450 handle_repo_groups=None, handle_user_groups=None):
450 handle_repo_groups=None, handle_user_groups=None):
451 if not cur_user:
451 if not cur_user:
452 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
452 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
453 user = self._get_user(user)
453 user = self._get_user(user)
454
454
455 try:
455 try:
456 if user.username == User.DEFAULT_USER:
456 if user.username == User.DEFAULT_USER:
457 raise DefaultUserException(
457 raise DefaultUserException(
458 _(u"You can't remove this user since it's"
458 _(u"You can't remove this user since it's"
459 u" crucial for entire application"))
459 u" crucial for entire application"))
460
460
461 left_overs = self._handle_user_repos(
461 left_overs = self._handle_user_repos(
462 user.username, user.repositories, handle_repos)
462 user.username, user.repositories, handle_repos)
463 if left_overs and user.repositories:
463 if left_overs and user.repositories:
464 repos = [x.repo_name for x in user.repositories]
464 repos = [x.repo_name for x in user.repositories]
465 raise UserOwnsReposException(
465 raise UserOwnsReposException(
466 _(u'user "%s" still owns %s repositories and cannot be '
466 _(u'user "%s" still owns %s repositories and cannot be '
467 u'removed. Switch owners or remove those repositories:%s')
467 u'removed. Switch owners or remove those repositories:%s')
468 % (user.username, len(repos), ', '.join(repos)))
468 % (user.username, len(repos), ', '.join(repos)))
469
469
470 left_overs = self._handle_user_repo_groups(
470 left_overs = self._handle_user_repo_groups(
471 user.username, user.repository_groups, handle_repo_groups)
471 user.username, user.repository_groups, handle_repo_groups)
472 if left_overs and user.repository_groups:
472 if left_overs and user.repository_groups:
473 repo_groups = [x.group_name for x in user.repository_groups]
473 repo_groups = [x.group_name for x in user.repository_groups]
474 raise UserOwnsRepoGroupsException(
474 raise UserOwnsRepoGroupsException(
475 _(u'user "%s" still owns %s repository groups and cannot be '
475 _(u'user "%s" still owns %s repository groups and cannot be '
476 u'removed. Switch owners or remove those repository groups:%s')
476 u'removed. Switch owners or remove those repository groups:%s')
477 % (user.username, len(repo_groups), ', '.join(repo_groups)))
477 % (user.username, len(repo_groups), ', '.join(repo_groups)))
478
478
479 left_overs = self._handle_user_user_groups(
479 left_overs = self._handle_user_user_groups(
480 user.username, user.user_groups, handle_user_groups)
480 user.username, user.user_groups, handle_user_groups)
481 if left_overs and user.user_groups:
481 if left_overs and user.user_groups:
482 user_groups = [x.users_group_name for x in user.user_groups]
482 user_groups = [x.users_group_name for x in user.user_groups]
483 raise UserOwnsUserGroupsException(
483 raise UserOwnsUserGroupsException(
484 _(u'user "%s" still owns %s user groups and cannot be '
484 _(u'user "%s" still owns %s user groups and cannot be '
485 u'removed. Switch owners or remove those user groups:%s')
485 u'removed. Switch owners or remove those user groups:%s')
486 % (user.username, len(user_groups), ', '.join(user_groups)))
486 % (user.username, len(user_groups), ', '.join(user_groups)))
487
487
488 # we might change the user data with detach/delete, make sure
488 # we might change the user data with detach/delete, make sure
489 # the object is marked as expired before actually deleting !
489 # the object is marked as expired before actually deleting !
490 self.sa.expire(user)
490 self.sa.expire(user)
491 self.sa.delete(user)
491 self.sa.delete(user)
492 from rhodecode.lib.hooks_base import log_delete_user
492 from rhodecode.lib.hooks_base import log_delete_user
493 log_delete_user(deleted_by=cur_user, **user.get_dict())
493 log_delete_user(deleted_by=cur_user, **user.get_dict())
494 except Exception:
494 except Exception:
495 log.error(traceback.format_exc())
495 log.error(traceback.format_exc())
496 raise
496 raise
497
497
498 def reset_password_link(self, data, pwd_reset_url):
498 def reset_password_link(self, data, pwd_reset_url):
499 from rhodecode.lib.celerylib import tasks, run_task
499 from rhodecode.lib.celerylib import tasks, run_task
500 from rhodecode.model.notification import EmailNotificationModel
500 from rhodecode.model.notification import EmailNotificationModel
501 user_email = data['email']
501 user_email = data['email']
502 try:
502 try:
503 user = User.get_by_email(user_email)
503 user = User.get_by_email(user_email)
504 if user:
504 if user:
505 log.debug('password reset user found %s', user)
505 log.debug('password reset user found %s', user)
506
506
507 email_kwargs = {
507 email_kwargs = {
508 'password_reset_url': pwd_reset_url,
508 'password_reset_url': pwd_reset_url,
509 'user': user,
509 'user': user,
510 'email': user_email,
510 'email': user_email,
511 'date': datetime.datetime.now()
511 'date': datetime.datetime.now()
512 }
512 }
513
513
514 (subject, headers, email_body,
514 (subject, headers, email_body,
515 email_body_plaintext) = EmailNotificationModel().render_email(
515 email_body_plaintext) = EmailNotificationModel().render_email(
516 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
516 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
517
517
518 recipients = [user_email]
518 recipients = [user_email]
519
519
520 action_logger_generic(
520 action_logger_generic(
521 'sending password reset email to user: {}'.format(
521 'sending password reset email to user: {}'.format(
522 user), namespace='security.password_reset')
522 user), namespace='security.password_reset')
523
523
524 run_task(tasks.send_email, recipients, subject,
524 run_task(tasks.send_email, recipients, subject,
525 email_body_plaintext, email_body)
525 email_body_plaintext, email_body)
526
526
527 else:
527 else:
528 log.debug("password reset email %s not found", user_email)
528 log.debug("password reset email %s not found", user_email)
529 except Exception:
529 except Exception:
530 log.error(traceback.format_exc())
530 log.error(traceback.format_exc())
531 return False
531 return False
532
532
533 return True
533 return True
534
534
535 def reset_password(self, data):
535 def reset_password(self, data):
536 from rhodecode.lib.celerylib import tasks, run_task
536 from rhodecode.lib.celerylib import tasks, run_task
537 from rhodecode.model.notification import EmailNotificationModel
537 from rhodecode.model.notification import EmailNotificationModel
538 from rhodecode.lib import auth
538 from rhodecode.lib import auth
539 user_email = data['email']
539 user_email = data['email']
540 pre_db = True
540 pre_db = True
541 try:
541 try:
542 user = User.get_by_email(user_email)
542 user = User.get_by_email(user_email)
543 new_passwd = auth.PasswordGenerator().gen_password(
543 new_passwd = auth.PasswordGenerator().gen_password(
544 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
544 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
545 if user:
545 if user:
546 user.password = auth.get_crypt_password(new_passwd)
546 user.password = auth.get_crypt_password(new_passwd)
547 # also force this user to reset his password !
547 # also force this user to reset his password !
548 user.update_userdata(force_password_change=True)
548 user.update_userdata(force_password_change=True)
549
549
550 Session().add(user)
550 Session().add(user)
551 Session().commit()
551 Session().commit()
552 log.info('change password for %s', user_email)
552 log.info('change password for %s', user_email)
553 if new_passwd is None:
553 if new_passwd is None:
554 raise Exception('unable to generate new password')
554 raise Exception('unable to generate new password')
555
555
556 pre_db = False
556 pre_db = False
557
557
558 email_kwargs = {
558 email_kwargs = {
559 'new_password': new_passwd,
559 'new_password': new_passwd,
560 'user': user,
560 'user': user,
561 'email': user_email,
561 'email': user_email,
562 'date': datetime.datetime.now()
562 'date': datetime.datetime.now()
563 }
563 }
564
564
565 (subject, headers, email_body,
565 (subject, headers, email_body,
566 email_body_plaintext) = EmailNotificationModel().render_email(
566 email_body_plaintext) = EmailNotificationModel().render_email(
567 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
567 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
568
568
569 recipients = [user_email]
569 recipients = [user_email]
570
570
571 action_logger_generic(
571 action_logger_generic(
572 'sent new password to user: {} with email: {}'.format(
572 'sent new password to user: {} with email: {}'.format(
573 user, user_email), namespace='security.password_reset')
573 user, user_email), namespace='security.password_reset')
574
574
575 run_task(tasks.send_email, recipients, subject,
575 run_task(tasks.send_email, recipients, subject,
576 email_body_plaintext, email_body)
576 email_body_plaintext, email_body)
577
577
578 except Exception:
578 except Exception:
579 log.error('Failed to update user password')
579 log.error('Failed to update user password')
580 log.error(traceback.format_exc())
580 log.error(traceback.format_exc())
581 if pre_db:
581 if pre_db:
582 # we rollback only if local db stuff fails. If it goes into
582 # we rollback only if local db stuff fails. If it goes into
583 # run_task, we're pass rollback state this wouldn't work then
583 # run_task, we're pass rollback state this wouldn't work then
584 Session().rollback()
584 Session().rollback()
585
585
586 return True
586 return True
587
587
588 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
588 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
589 """
589 """
590 Fetches auth_user by user_id,or api_key if present.
590 Fetches auth_user by user_id,or api_key if present.
591 Fills auth_user attributes with those taken from database.
591 Fills auth_user attributes with those taken from database.
592 Additionally set's is_authenitated if lookup fails
592 Additionally set's is_authenitated if lookup fails
593 present in database
593 present in database
594
594
595 :param auth_user: instance of user to set attributes
595 :param auth_user: instance of user to set attributes
596 :param user_id: user id to fetch by
596 :param user_id: user id to fetch by
597 :param api_key: api key to fetch by
597 :param api_key: api key to fetch by
598 :param username: username to fetch by
598 :param username: username to fetch by
599 """
599 """
600 if user_id is None and api_key is None and username is None:
600 if user_id is None and api_key is None and username is None:
601 raise Exception('You need to pass user_id, api_key or username')
601 raise Exception('You need to pass user_id, api_key or username')
602
602
603 log.debug(
603 log.debug(
604 'doing fill data based on: user_id:%s api_key:%s username:%s',
604 'doing fill data based on: user_id:%s api_key:%s username:%s',
605 user_id, api_key, username)
605 user_id, api_key, username)
606 try:
606 try:
607 dbuser = None
607 dbuser = None
608 if user_id:
608 if user_id:
609 dbuser = self.get(user_id)
609 dbuser = self.get(user_id)
610 elif api_key:
610 elif api_key:
611 dbuser = self.get_by_auth_token(api_key)
611 dbuser = self.get_by_auth_token(api_key)
612 elif username:
612 elif username:
613 dbuser = self.get_by_username(username)
613 dbuser = self.get_by_username(username)
614
614
615 if not dbuser:
615 if not dbuser:
616 log.warning(
616 log.warning(
617 'Unable to lookup user by id:%s api_key:%s username:%s',
617 'Unable to lookup user by id:%s api_key:%s username:%s',
618 user_id, api_key, username)
618 user_id, api_key, username)
619 return False
619 return False
620 if not dbuser.active:
620 if not dbuser.active:
621 log.debug('User `%s` is inactive, skipping fill data', username)
621 log.debug('User `%s` is inactive, skipping fill data', username)
622 return False
622 return False
623
623
624 log.debug('filling user:%s data', dbuser)
624 log.debug('filling user:%s data', dbuser)
625
625
626 # TODO: johbo: Think about this and find a clean solution
626 # TODO: johbo: Think about this and find a clean solution
627 user_data = dbuser.get_dict()
627 user_data = dbuser.get_dict()
628 user_data.update(dbuser.get_api_data(include_secrets=True))
628 user_data.update(dbuser.get_api_data(include_secrets=True))
629
629
630 for k, v in user_data.iteritems():
630 for k, v in user_data.iteritems():
631 # properties of auth user we dont update
631 # properties of auth user we dont update
632 if k not in ['auth_tokens', 'permissions']:
632 if k not in ['auth_tokens', 'permissions']:
633 setattr(auth_user, k, v)
633 setattr(auth_user, k, v)
634
634
635 # few extras
635 # few extras
636 setattr(auth_user, 'feed_token', dbuser.feed_token)
636 setattr(auth_user, 'feed_token', dbuser.feed_token)
637 except Exception:
637 except Exception:
638 log.error(traceback.format_exc())
638 log.error(traceback.format_exc())
639 auth_user.is_authenticated = False
639 auth_user.is_authenticated = False
640 return False
640 return False
641
641
642 return True
642 return True
643
643
644 def has_perm(self, user, perm):
644 def has_perm(self, user, perm):
645 perm = self._get_perm(perm)
645 perm = self._get_perm(perm)
646 user = self._get_user(user)
646 user = self._get_user(user)
647
647
648 return UserToPerm.query().filter(UserToPerm.user == user)\
648 return UserToPerm.query().filter(UserToPerm.user == user)\
649 .filter(UserToPerm.permission == perm).scalar() is not None
649 .filter(UserToPerm.permission == perm).scalar() is not None
650
650
651 def grant_perm(self, user, perm):
651 def grant_perm(self, user, perm):
652 """
652 """
653 Grant user global permissions
653 Grant user global permissions
654
654
655 :param user:
655 :param user:
656 :param perm:
656 :param perm:
657 """
657 """
658 user = self._get_user(user)
658 user = self._get_user(user)
659 perm = self._get_perm(perm)
659 perm = self._get_perm(perm)
660 # if this permission is already granted skip it
660 # if this permission is already granted skip it
661 _perm = UserToPerm.query()\
661 _perm = UserToPerm.query()\
662 .filter(UserToPerm.user == user)\
662 .filter(UserToPerm.user == user)\
663 .filter(UserToPerm.permission == perm)\
663 .filter(UserToPerm.permission == perm)\
664 .scalar()
664 .scalar()
665 if _perm:
665 if _perm:
666 return
666 return
667 new = UserToPerm()
667 new = UserToPerm()
668 new.user = user
668 new.user = user
669 new.permission = perm
669 new.permission = perm
670 self.sa.add(new)
670 self.sa.add(new)
671 return new
671 return new
672
672
673 def revoke_perm(self, user, perm):
673 def revoke_perm(self, user, perm):
674 """
674 """
675 Revoke users global permissions
675 Revoke users global permissions
676
676
677 :param user:
677 :param user:
678 :param perm:
678 :param perm:
679 """
679 """
680 user = self._get_user(user)
680 user = self._get_user(user)
681 perm = self._get_perm(perm)
681 perm = self._get_perm(perm)
682
682
683 obj = UserToPerm.query()\
683 obj = UserToPerm.query()\
684 .filter(UserToPerm.user == user)\
684 .filter(UserToPerm.user == user)\
685 .filter(UserToPerm.permission == perm)\
685 .filter(UserToPerm.permission == perm)\
686 .scalar()
686 .scalar()
687 if obj:
687 if obj:
688 self.sa.delete(obj)
688 self.sa.delete(obj)
689
689
690 def add_extra_email(self, user, email):
690 def add_extra_email(self, user, email):
691 """
691 """
692 Adds email address to UserEmailMap
692 Adds email address to UserEmailMap
693
693
694 :param user:
694 :param user:
695 :param email:
695 :param email:
696 """
696 """
697 from rhodecode.model import forms
697 from rhodecode.model import forms
698 form = forms.UserExtraEmailForm()()
698 form = forms.UserExtraEmailForm()()
699 data = form.to_python({'email': email})
699 data = form.to_python({'email': email})
700 user = self._get_user(user)
700 user = self._get_user(user)
701
701
702 obj = UserEmailMap()
702 obj = UserEmailMap()
703 obj.user = user
703 obj.user = user
704 obj.email = data['email']
704 obj.email = data['email']
705 self.sa.add(obj)
705 self.sa.add(obj)
706 return obj
706 return obj
707
707
708 def delete_extra_email(self, user, email_id):
708 def delete_extra_email(self, user, email_id):
709 """
709 """
710 Removes email address from UserEmailMap
710 Removes email address from UserEmailMap
711
711
712 :param user:
712 :param user:
713 :param email_id:
713 :param email_id:
714 """
714 """
715 user = self._get_user(user)
715 user = self._get_user(user)
716 obj = UserEmailMap.query().get(email_id)
716 obj = UserEmailMap.query().get(email_id)
717 if obj:
717 if obj:
718 self.sa.delete(obj)
718 self.sa.delete(obj)
719
719
720 def parse_ip_range(self, ip_range):
720 def parse_ip_range(self, ip_range):
721 ip_list = []
721 ip_list = []
722 def make_unique(value):
722 def make_unique(value):
723 seen = []
723 seen = []
724 return [c for c in value if not (c in seen or seen.append(c))]
724 return [c for c in value if not (c in seen or seen.append(c))]
725
725
726 # firsts split by commas
726 # firsts split by commas
727 for ip_range in ip_range.split(','):
727 for ip_range in ip_range.split(','):
728 if not ip_range:
728 if not ip_range:
729 continue
729 continue
730 ip_range = ip_range.strip()
730 ip_range = ip_range.strip()
731 if '-' in ip_range:
731 if '-' in ip_range:
732 start_ip, end_ip = ip_range.split('-', 1)
732 start_ip, end_ip = ip_range.split('-', 1)
733 start_ip = ipaddress.ip_address(start_ip.strip())
733 start_ip = ipaddress.ip_address(start_ip.strip())
734 end_ip = ipaddress.ip_address(end_ip.strip())
734 end_ip = ipaddress.ip_address(end_ip.strip())
735 parsed_ip_range = []
735 parsed_ip_range = []
736
736
737 for index in xrange(int(start_ip), int(end_ip) + 1):
737 for index in xrange(int(start_ip), int(end_ip) + 1):
738 new_ip = ipaddress.ip_address(index)
738 new_ip = ipaddress.ip_address(index)
739 parsed_ip_range.append(str(new_ip))
739 parsed_ip_range.append(str(new_ip))
740 ip_list.extend(parsed_ip_range)
740 ip_list.extend(parsed_ip_range)
741 else:
741 else:
742 ip_list.append(ip_range)
742 ip_list.append(ip_range)
743
743
744 return make_unique(ip_list)
744 return make_unique(ip_list)
745
745
746 def add_extra_ip(self, user, ip, description=None):
746 def add_extra_ip(self, user, ip, description=None):
747 """
747 """
748 Adds ip address to UserIpMap
748 Adds ip address to UserIpMap
749
749
750 :param user:
750 :param user:
751 :param ip:
751 :param ip:
752 """
752 """
753 from rhodecode.model import forms
753 from rhodecode.model import forms
754 form = forms.UserExtraIpForm()()
754 form = forms.UserExtraIpForm()()
755 data = form.to_python({'ip': ip})
755 data = form.to_python({'ip': ip})
756 user = self._get_user(user)
756 user = self._get_user(user)
757
757
758 obj = UserIpMap()
758 obj = UserIpMap()
759 obj.user = user
759 obj.user = user
760 obj.ip_addr = data['ip']
760 obj.ip_addr = data['ip']
761 obj.description = description
761 obj.description = description
762 self.sa.add(obj)
762 self.sa.add(obj)
763 return obj
763 return obj
764
764
765 def delete_extra_ip(self, user, ip_id):
765 def delete_extra_ip(self, user, ip_id):
766 """
766 """
767 Removes ip address from UserIpMap
767 Removes ip address from UserIpMap
768
768
769 :param user:
769 :param user:
770 :param ip_id:
770 :param ip_id:
771 """
771 """
772 user = self._get_user(user)
772 user = self._get_user(user)
773 obj = UserIpMap.query().get(ip_id)
773 obj = UserIpMap.query().get(ip_id)
774 if obj:
774 if obj:
775 self.sa.delete(obj)
775 self.sa.delete(obj)
776
776
777 def get_accounts_in_creation_order(self, current_user=None):
777 def get_accounts_in_creation_order(self, current_user=None):
778 """
778 """
779 Get accounts in order of creation for deactivation for license limits
779 Get accounts in order of creation for deactivation for license limits
780
780
781 pick currently logged in user, and append to the list in position 0
781 pick currently logged in user, and append to the list in position 0
782 pick all super-admins in order of creation date and add it to the list
782 pick all super-admins in order of creation date and add it to the list
783 pick all other accounts in order of creation and add it to the list.
783 pick all other accounts in order of creation and add it to the list.
784
784
785 Based on that list, the last accounts can be disabled as they are
785 Based on that list, the last accounts can be disabled as they are
786 created at the end and don't include any of the super admins as well
786 created at the end and don't include any of the super admins as well
787 as the current user.
787 as the current user.
788
788
789 :param current_user: optionally current user running this operation
789 :param current_user: optionally current user running this operation
790 """
790 """
791
791
792 if not current_user:
792 if not current_user:
793 current_user = get_current_rhodecode_user()
793 current_user = get_current_rhodecode_user()
794 active_super_admins = [
794 active_super_admins = [
795 x.user_id for x in User.query()
795 x.user_id for x in User.query()
796 .filter(User.user_id != current_user.user_id)
796 .filter(User.user_id != current_user.user_id)
797 .filter(User.active == true())
797 .filter(User.active == true())
798 .filter(User.admin == true())
798 .filter(User.admin == true())
799 .order_by(User.created_on.asc())]
799 .order_by(User.created_on.asc())]
800
800
801 active_regular_users = [
801 active_regular_users = [
802 x.user_id for x in User.query()
802 x.user_id for x in User.query()
803 .filter(User.user_id != current_user.user_id)
803 .filter(User.user_id != current_user.user_id)
804 .filter(User.active == true())
804 .filter(User.active == true())
805 .filter(User.admin == false())
805 .filter(User.admin == false())
806 .order_by(User.created_on.asc())]
806 .order_by(User.created_on.asc())]
807
807
808 list_of_accounts = [current_user.user_id]
808 list_of_accounts = [current_user.user_id]
809 list_of_accounts += active_super_admins
809 list_of_accounts += active_super_admins
810 list_of_accounts += active_regular_users
810 list_of_accounts += active_regular_users
811
811
812 return list_of_accounts
812 return list_of_accounts
813
813
814 def deactivate_last_users(self, expected_users):
814 def deactivate_last_users(self, expected_users):
815 """
815 """
816 Deactivate accounts that are over the license limits.
816 Deactivate accounts that are over the license limits.
817 Algorithm of which accounts to disabled is based on the formula:
817 Algorithm of which accounts to disabled is based on the formula:
818
818
819 Get current user, then super admins in creation order, then regular
819 Get current user, then super admins in creation order, then regular
820 active users in creation order.
820 active users in creation order.
821
821
822 Using that list we mark all accounts from the end of it as inactive.
822 Using that list we mark all accounts from the end of it as inactive.
823 This way we block only latest created accounts.
823 This way we block only latest created accounts.
824
824
825 :param expected_users: list of users in special order, we deactivate
825 :param expected_users: list of users in special order, we deactivate
826 the end N ammoun of users from that list
826 the end N ammoun of users from that list
827 """
827 """
828
828
829 list_of_accounts = self.get_accounts_in_creation_order()
829 list_of_accounts = self.get_accounts_in_creation_order()
830
830
831 for acc_id in list_of_accounts[expected_users + 1:]:
831 for acc_id in list_of_accounts[expected_users + 1:]:
832 user = User.get(acc_id)
832 user = User.get(acc_id)
833 log.info('Deactivating account %s for license unlock', user)
833 log.info('Deactivating account %s for license unlock', user)
834 user.active = False
834 user.active = False
835 Session().add(user)
835 Session().add(user)
836 Session().commit()
836 Session().commit()
837
837
838 return
838 return
@@ -1,517 +1,517 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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 user group model for RhodeCode
23 user group model for RhodeCode
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 RepoGroupAssignmentError
36 RepoGroupAssignmentError
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class UserGroupModel(BaseModel):
42 class UserGroupModel(BaseModel):
43
43
44 cls = UserGroup
44 cls = UserGroup
45
45
46 def _get_user_group(self, user_group):
46 def _get_user_group(self, user_group):
47 return self._get_instance(UserGroup, user_group,
47 return self._get_instance(UserGroup, user_group,
48 callback=UserGroup.get_by_group_name)
48 callback=UserGroup.get_by_group_name)
49
49
50 def _create_default_perms(self, user_group):
50 def _create_default_perms(self, user_group):
51 # create default permission
51 # create default permission
52 default_perm = 'usergroup.read'
52 default_perm = 'usergroup.read'
53 def_user = User.get_default_user()
53 def_user = User.get_default_user()
54 for p in def_user.user_perms:
54 for p in def_user.user_perms:
55 if p.permission.permission_name.startswith('usergroup.'):
55 if p.permission.permission_name.startswith('usergroup.'):
56 default_perm = p.permission.permission_name
56 default_perm = p.permission.permission_name
57 break
57 break
58
58
59 user_group_to_perm = UserUserGroupToPerm()
59 user_group_to_perm = UserUserGroupToPerm()
60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
61
61
62 user_group_to_perm.user_group = user_group
62 user_group_to_perm.user_group = user_group
63 user_group_to_perm.user_id = def_user.user_id
63 user_group_to_perm.user_id = def_user.user_id
64 return user_group_to_perm
64 return user_group_to_perm
65
65
66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
67 perm_deletions=None, check_perms=True, cur_user=None):
67 perm_deletions=None, check_perms=True, cur_user=None):
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 if not perm_additions:
69 if not perm_additions:
70 perm_additions = []
70 perm_additions = []
71 if not perm_updates:
71 if not perm_updates:
72 perm_updates = []
72 perm_updates = []
73 if not perm_deletions:
73 if not perm_deletions:
74 perm_deletions = []
74 perm_deletions = []
75
75
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77
77
78 # update permissions
78 # update permissions
79 for member_id, perm, member_type in perm_updates:
79 for member_id, perm, member_type in perm_updates:
80 member_id = int(member_id)
80 member_id = int(member_id)
81 if member_type == 'user':
81 if member_type == 'user':
82 # this updates existing one
82 # this updates existing one
83 self.grant_user_permission(
83 self.grant_user_permission(
84 user_group=user_group, user=member_id, perm=perm
84 user_group=user_group, user=member_id, perm=perm
85 )
85 )
86 else:
86 else:
87 # check if we have permissions to alter this usergroup
87 # check if we have permissions to alter this usergroup
88 member_name = UserGroup.get(member_id).users_group_name
88 member_name = UserGroup.get(member_id).users_group_name
89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
90 self.grant_user_group_permission(
90 self.grant_user_group_permission(
91 target_user_group=user_group, user_group=member_id, perm=perm
91 target_user_group=user_group, user_group=member_id, perm=perm
92 )
92 )
93
93
94 # set new permissions
94 # set new permissions
95 for member_id, perm, member_type in perm_additions:
95 for member_id, perm, member_type in perm_additions:
96 member_id = int(member_id)
96 member_id = int(member_id)
97 if member_type == 'user':
97 if member_type == 'user':
98 self.grant_user_permission(
98 self.grant_user_permission(
99 user_group=user_group, user=member_id, perm=perm
99 user_group=user_group, user=member_id, perm=perm
100 )
100 )
101 else:
101 else:
102 # check if we have permissions to alter this usergroup
102 # check if we have permissions to alter this usergroup
103 member_name = UserGroup.get(member_id).users_group_name
103 member_name = UserGroup.get(member_id).users_group_name
104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
105 self.grant_user_group_permission(
105 self.grant_user_group_permission(
106 target_user_group=user_group, user_group=member_id, perm=perm
106 target_user_group=user_group, user_group=member_id, perm=perm
107 )
107 )
108
108
109 # delete permissions
109 # delete permissions
110 for member_id, perm, member_type in perm_deletions:
110 for member_id, perm, member_type in perm_deletions:
111 member_id = int(member_id)
111 member_id = int(member_id)
112 if member_type == 'user':
112 if member_type == 'user':
113 self.revoke_user_permission(user_group=user_group, user=member_id)
113 self.revoke_user_permission(user_group=user_group, user=member_id)
114 else:
114 else:
115 #check if we have permissions to alter this usergroup
115 #check if we have permissions to alter this usergroup
116 member_name = UserGroup.get(member_id).users_group_name
116 member_name = UserGroup.get(member_id).users_group_name
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 self.revoke_user_group_permission(
118 self.revoke_user_group_permission(
119 target_user_group=user_group, user_group=member_id
119 target_user_group=user_group, user_group=member_id
120 )
120 )
121
121
122 def get(self, user_group_id, cache=False):
122 def get(self, user_group_id, cache=False):
123 return UserGroup.get(user_group_id)
123 return UserGroup.get(user_group_id)
124
124
125 def get_group(self, user_group):
125 def get_group(self, user_group):
126 return self._get_user_group(user_group)
126 return self._get_user_group(user_group)
127
127
128 def get_by_name(self, name, cache=False, case_insensitive=False):
128 def get_by_name(self, name, cache=False, case_insensitive=False):
129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
130
130
131 def create(self, name, description, owner, active=True, group_data=None):
131 def create(self, name, description, owner, active=True, group_data=None):
132 try:
132 try:
133 new_user_group = UserGroup()
133 new_user_group = UserGroup()
134 new_user_group.user = self._get_user(owner)
134 new_user_group.user = self._get_user(owner)
135 new_user_group.users_group_name = name
135 new_user_group.users_group_name = name
136 new_user_group.user_group_description = description
136 new_user_group.user_group_description = description
137 new_user_group.users_group_active = active
137 new_user_group.users_group_active = active
138 if group_data:
138 if group_data:
139 new_user_group.group_data = group_data
139 new_user_group.group_data = group_data
140 self.sa.add(new_user_group)
140 self.sa.add(new_user_group)
141 perm_obj = self._create_default_perms(new_user_group)
141 perm_obj = self._create_default_perms(new_user_group)
142 self.sa.add(perm_obj)
142 self.sa.add(perm_obj)
143
143
144 self.grant_user_permission(user_group=new_user_group,
144 self.grant_user_permission(user_group=new_user_group,
145 user=owner, perm='usergroup.admin')
145 user=owner, perm='usergroup.admin')
146
146
147 return new_user_group
147 return new_user_group
148 except Exception:
148 except Exception:
149 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
150 raise
150 raise
151
151
152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
153 members = []
153 members = []
154 for user_id in user_id_list:
154 for user_id in user_id_list:
155 member = self._get_membership(user_group.users_group_id, user_id)
155 member = self._get_membership(user_group.users_group_id, user_id)
156 members.append(member)
156 members.append(member)
157 return members
157 return members
158
158
159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
160 current_members = user_group.members or []
160 current_members = user_group.members or []
161 current_members_ids = [m.user.user_id for m in current_members]
161 current_members_ids = [m.user.user_id for m in current_members]
162
162
163 added_members = [
163 added_members = [
164 user_id for user_id in user_id_list
164 user_id for user_id in user_id_list
165 if user_id not in current_members_ids]
165 if user_id not in current_members_ids]
166 if user_id_list == []:
166 if user_id_list == []:
167 # all members were deleted
167 # all members were deleted
168 deleted_members = current_members_ids
168 deleted_members = current_members_ids
169 else:
169 else:
170 deleted_members = [
170 deleted_members = [
171 user_id for user_id in current_members_ids
171 user_id for user_id in current_members_ids
172 if user_id not in user_id_list]
172 if user_id not in user_id_list]
173
173
174 return (added_members, deleted_members)
174 return (added_members, deleted_members)
175
175
176 def _set_users_as_members(self, user_group, user_ids):
176 def _set_users_as_members(self, user_group, user_ids):
177 user_group.members = []
177 user_group.members = []
178 self.sa.flush()
178 self.sa.flush()
179 members = self._get_memberships_for_user_ids(
179 members = self._get_memberships_for_user_ids(
180 user_group, user_ids)
180 user_group, user_ids)
181 user_group.members = members
181 user_group.members = members
182 self.sa.add(user_group)
182 self.sa.add(user_group)
183
183
184 def _update_members_from_user_ids(self, user_group, user_ids):
184 def _update_members_from_user_ids(self, user_group, user_ids):
185 added, removed = self._get_added_and_removed_user_ids(
185 added, removed = self._get_added_and_removed_user_ids(
186 user_group, user_ids)
186 user_group, user_ids)
187 self._set_users_as_members(user_group, user_ids)
187 self._set_users_as_members(user_group, user_ids)
188 self._log_user_changes('added to', user_group, added)
188 self._log_user_changes('added to', user_group, added)
189 self._log_user_changes('removed from', user_group, removed)
189 self._log_user_changes('removed from', user_group, removed)
190
190
191 def _clean_members_data(self, members_data):
191 def _clean_members_data(self, members_data):
192 # TODO: anderson: this should be in the form validation but I couldn't
192 # TODO: anderson: this should be in the form validation but I couldn't
193 # make it work there as it conflicts with the other validator
193 # make it work there as it conflicts with the other validator
194 if not members_data:
194 if not members_data:
195 members_data = []
195 members_data = []
196
196
197 if isinstance(members_data, basestring):
197 if isinstance(members_data, basestring):
198 new_members = [members_data]
198 new_members = [members_data]
199 else:
199 else:
200 new_members = members_data
200 new_members = members_data
201
201
202 new_members = [int(uid) for uid in new_members]
202 new_members = [int(uid) for uid in new_members]
203 return new_members
203 return new_members
204
204
205 def update(self, user_group, form_data):
205 def update(self, user_group, form_data):
206 user_group = self._get_user_group(user_group)
206 user_group = self._get_user_group(user_group)
207 if 'users_group_name' in form_data:
207 if 'users_group_name' in form_data:
208 user_group.users_group_name = form_data['users_group_name']
208 user_group.users_group_name = form_data['users_group_name']
209 if 'users_group_active' in form_data:
209 if 'users_group_active' in form_data:
210 user_group.users_group_active = form_data['users_group_active']
210 user_group.users_group_active = form_data['users_group_active']
211 if 'user_group_description' in form_data:
211 if 'user_group_description' in form_data:
212 user_group.user_group_description = form_data[
212 user_group.user_group_description = form_data[
213 'user_group_description']
213 'user_group_description']
214
214
215 # handle owner change
215 # handle owner change
216 if 'user' in form_data:
216 if 'user' in form_data:
217 owner = form_data['user']
217 owner = form_data['user']
218 if isinstance(owner, basestring):
218 if isinstance(owner, basestring):
219 owner = User.get_by_username(form_data['user'])
219 owner = User.get_by_username(form_data['user'])
220
220
221 if not isinstance(owner, User):
221 if not isinstance(owner, User):
222 raise ValueError(
222 raise ValueError(
223 'invalid owner for user group: %s' % form_data['user'])
223 'invalid owner for user group: %s' % form_data['user'])
224
224
225 user_group.user = owner
225 user_group.user = owner
226
226
227 if 'users_group_members' in form_data:
227 if 'users_group_members' in form_data:
228 members_id_list = self._clean_members_data(
228 members_id_list = self._clean_members_data(
229 form_data['users_group_members'])
229 form_data['users_group_members'])
230 self._update_members_from_user_ids(user_group, members_id_list)
230 self._update_members_from_user_ids(user_group, members_id_list)
231
231
232 self.sa.add(user_group)
232 self.sa.add(user_group)
233
233
234 def delete(self, user_group, force=False):
234 def delete(self, user_group, force=False):
235 """
235 """
236 Deletes repository group, unless force flag is used
236 Deletes repository group, unless force flag is used
237 raises exception if there are members in that group, else deletes
237 raises exception if there are members in that group, else deletes
238 group and users
238 group and users
239
239
240 :param user_group:
240 :param user_group:
241 :param force:
241 :param force:
242 """
242 """
243 user_group = self._get_user_group(user_group)
243 user_group = self._get_user_group(user_group)
244 try:
244 try:
245 # check if this group is not assigned to repo
245 # check if this group is not assigned to repo
246 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
246 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
247 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
247 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
248 # check if this group is not assigned to repo
248 # check if this group is not assigned to repo
249 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
249 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
250 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
250 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
251
251
252 if (assigned_to_repo or assigned_to_repo_group) and not force:
252 if (assigned_to_repo or assigned_to_repo_group) and not force:
253 assigned = ','.join(map(safe_str,
253 assigned = ','.join(map(safe_str,
254 assigned_to_repo+assigned_to_repo_group))
254 assigned_to_repo+assigned_to_repo_group))
255
255
256 raise UserGroupAssignedException(
256 raise UserGroupAssignedException(
257 'UserGroup assigned to %s' % (assigned,))
257 'UserGroup assigned to %s' % (assigned,))
258 self.sa.delete(user_group)
258 self.sa.delete(user_group)
259 except Exception:
259 except Exception:
260 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
261 raise
261 raise
262
262
263 def _log_user_changes(self, action, user_group, user_or_users):
263 def _log_user_changes(self, action, user_group, user_or_users):
264 users = user_or_users
264 users = user_or_users
265 if not isinstance(users, (list, tuple)):
265 if not isinstance(users, (list, tuple)):
266 users = [users]
266 users = [users]
267 rhodecode_user = get_current_rhodecode_user()
267 rhodecode_user = get_current_rhodecode_user()
268 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
268 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
269 group_name = user_group.users_group_name
269 group_name = user_group.users_group_name
270
270
271 for user_or_user_id in users:
271 for user_or_user_id in users:
272 user = self._get_user(user_or_user_id)
272 user = self._get_user(user_or_user_id)
273 log_text = 'User {user} {action} {group}'.format(
273 log_text = 'User {user} {action} {group}'.format(
274 action=action, user=user.username, group=group_name)
274 action=action, user=user.username, group=group_name)
275 log.info('Logging action: {0} by {1} ip:{2}'.format(
275 log.info('Logging action: {0} by {1} ip:{2}'.format(
276 log_text, rhodecode_user, ipaddr))
276 log_text, rhodecode_user, ipaddr))
277
277
278 def _find_user_in_group(self, user, user_group):
278 def _find_user_in_group(self, user, user_group):
279 user_group_member = None
279 user_group_member = None
280 for m in user_group.members:
280 for m in user_group.members:
281 if m.user_id == user.user_id:
281 if m.user_id == user.user_id:
282 # Found this user's membership row
282 # Found this user's membership row
283 user_group_member = m
283 user_group_member = m
284 break
284 break
285
285
286 return user_group_member
286 return user_group_member
287
287
288 def _get_membership(self, user_group_id, user_id):
288 def _get_membership(self, user_group_id, user_id):
289 user_group_member = UserGroupMember(user_group_id, user_id)
289 user_group_member = UserGroupMember(user_group_id, user_id)
290 return user_group_member
290 return user_group_member
291
291
292 def add_user_to_group(self, user_group, user):
292 def add_user_to_group(self, user_group, user):
293 user_group = self._get_user_group(user_group)
293 user_group = self._get_user_group(user_group)
294 user = self._get_user(user)
294 user = self._get_user(user)
295 user_member = self._find_user_in_group(user, user_group)
295 user_member = self._find_user_in_group(user, user_group)
296 if user_member:
296 if user_member:
297 # user already in the group, skip
297 # user already in the group, skip
298 return True
298 return True
299
299
300 member = self._get_membership(
300 member = self._get_membership(
301 user_group.users_group_id, user.user_id)
301 user_group.users_group_id, user.user_id)
302 user_group.members.append(member)
302 user_group.members.append(member)
303
303
304 try:
304 try:
305 self.sa.add(member)
305 self.sa.add(member)
306 except Exception:
306 except Exception:
307 # what could go wrong here?
307 # what could go wrong here?
308 log.error(traceback.format_exc())
308 log.error(traceback.format_exc())
309 raise
309 raise
310
310
311 self._log_user_changes('added to', user_group, user)
311 self._log_user_changes('added to', user_group, user)
312 return member
312 return member
313
313
314 def remove_user_from_group(self, user_group, user):
314 def remove_user_from_group(self, user_group, user):
315 user_group = self._get_user_group(user_group)
315 user_group = self._get_user_group(user_group)
316 user = self._get_user(user)
316 user = self._get_user(user)
317 user_group_member = self._find_user_in_group(user, user_group)
317 user_group_member = self._find_user_in_group(user, user_group)
318
318
319 if not user_group_member:
319 if not user_group_member:
320 # User isn't in that group
320 # User isn't in that group
321 return False
321 return False
322
322
323 try:
323 try:
324 self.sa.delete(user_group_member)
324 self.sa.delete(user_group_member)
325 except Exception:
325 except Exception:
326 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
327 raise
327 raise
328
328
329 self._log_user_changes('removed from', user_group, user)
329 self._log_user_changes('removed from', user_group, user)
330 return True
330 return True
331
331
332 def has_perm(self, user_group, perm):
332 def has_perm(self, user_group, perm):
333 user_group = self._get_user_group(user_group)
333 user_group = self._get_user_group(user_group)
334 perm = self._get_perm(perm)
334 perm = self._get_perm(perm)
335
335
336 return UserGroupToPerm.query()\
336 return UserGroupToPerm.query()\
337 .filter(UserGroupToPerm.users_group == user_group)\
337 .filter(UserGroupToPerm.users_group == user_group)\
338 .filter(UserGroupToPerm.permission == perm).scalar() is not None
338 .filter(UserGroupToPerm.permission == perm).scalar() is not None
339
339
340 def grant_perm(self, user_group, perm):
340 def grant_perm(self, user_group, perm):
341 user_group = self._get_user_group(user_group)
341 user_group = self._get_user_group(user_group)
342 perm = self._get_perm(perm)
342 perm = self._get_perm(perm)
343
343
344 # if this permission is already granted skip it
344 # if this permission is already granted skip it
345 _perm = UserGroupToPerm.query()\
345 _perm = UserGroupToPerm.query()\
346 .filter(UserGroupToPerm.users_group == user_group)\
346 .filter(UserGroupToPerm.users_group == user_group)\
347 .filter(UserGroupToPerm.permission == perm)\
347 .filter(UserGroupToPerm.permission == perm)\
348 .scalar()
348 .scalar()
349 if _perm:
349 if _perm:
350 return
350 return
351
351
352 new = UserGroupToPerm()
352 new = UserGroupToPerm()
353 new.users_group = user_group
353 new.users_group = user_group
354 new.permission = perm
354 new.permission = perm
355 self.sa.add(new)
355 self.sa.add(new)
356 return new
356 return new
357
357
358 def revoke_perm(self, user_group, perm):
358 def revoke_perm(self, user_group, perm):
359 user_group = self._get_user_group(user_group)
359 user_group = self._get_user_group(user_group)
360 perm = self._get_perm(perm)
360 perm = self._get_perm(perm)
361
361
362 obj = UserGroupToPerm.query()\
362 obj = UserGroupToPerm.query()\
363 .filter(UserGroupToPerm.users_group == user_group)\
363 .filter(UserGroupToPerm.users_group == user_group)\
364 .filter(UserGroupToPerm.permission == perm).scalar()
364 .filter(UserGroupToPerm.permission == perm).scalar()
365 if obj:
365 if obj:
366 self.sa.delete(obj)
366 self.sa.delete(obj)
367
367
368 def grant_user_permission(self, user_group, user, perm):
368 def grant_user_permission(self, user_group, user, perm):
369 """
369 """
370 Grant permission for user on given user group, or update
370 Grant permission for user on given user group, or update
371 existing one if found
371 existing one if found
372
372
373 :param user_group: Instance of UserGroup, users_group_id,
373 :param user_group: Instance of UserGroup, users_group_id,
374 or users_group_name
374 or users_group_name
375 :param user: Instance of User, user_id or username
375 :param user: Instance of User, user_id or username
376 :param perm: Instance of Permission, or permission_name
376 :param perm: Instance of Permission, or permission_name
377 """
377 """
378
378
379 user_group = self._get_user_group(user_group)
379 user_group = self._get_user_group(user_group)
380 user = self._get_user(user)
380 user = self._get_user(user)
381 permission = self._get_perm(perm)
381 permission = self._get_perm(perm)
382
382
383 # check if we have that permission already
383 # check if we have that permission already
384 obj = self.sa.query(UserUserGroupToPerm)\
384 obj = self.sa.query(UserUserGroupToPerm)\
385 .filter(UserUserGroupToPerm.user == user)\
385 .filter(UserUserGroupToPerm.user == user)\
386 .filter(UserUserGroupToPerm.user_group == user_group)\
386 .filter(UserUserGroupToPerm.user_group == user_group)\
387 .scalar()
387 .scalar()
388 if obj is None:
388 if obj is None:
389 # create new !
389 # create new !
390 obj = UserUserGroupToPerm()
390 obj = UserUserGroupToPerm()
391 obj.user_group = user_group
391 obj.user_group = user_group
392 obj.user = user
392 obj.user = user
393 obj.permission = permission
393 obj.permission = permission
394 self.sa.add(obj)
394 self.sa.add(obj)
395 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
395 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
396 action_logger_generic(
396 action_logger_generic(
397 'granted permission: {} to user: {} on usergroup: {}'.format(
397 'granted permission: {} to user: {} on usergroup: {}'.format(
398 perm, user, user_group), namespace='security.usergroup')
398 perm, user, user_group), namespace='security.usergroup')
399
399
400 return obj
400 return obj
401
401
402 def revoke_user_permission(self, user_group, user):
402 def revoke_user_permission(self, user_group, user):
403 """
403 """
404 Revoke permission for user on given user group
404 Revoke permission for user on given user group
405
405
406 :param user_group: Instance of UserGroup, users_group_id,
406 :param user_group: Instance of UserGroup, users_group_id,
407 or users_group name
407 or users_group name
408 :param user: Instance of User, user_id or username
408 :param user: Instance of User, user_id or username
409 """
409 """
410
410
411 user_group = self._get_user_group(user_group)
411 user_group = self._get_user_group(user_group)
412 user = self._get_user(user)
412 user = self._get_user(user)
413
413
414 obj = self.sa.query(UserUserGroupToPerm)\
414 obj = self.sa.query(UserUserGroupToPerm)\
415 .filter(UserUserGroupToPerm.user == user)\
415 .filter(UserUserGroupToPerm.user == user)\
416 .filter(UserUserGroupToPerm.user_group == user_group)\
416 .filter(UserUserGroupToPerm.user_group == user_group)\
417 .scalar()
417 .scalar()
418 if obj:
418 if obj:
419 self.sa.delete(obj)
419 self.sa.delete(obj)
420 log.debug('Revoked perm on %s on %s', user_group, user)
420 log.debug('Revoked perm on %s on %s', user_group, user)
421 action_logger_generic(
421 action_logger_generic(
422 'revoked permission from user: {} on usergroup: {}'.format(
422 'revoked permission from user: {} on usergroup: {}'.format(
423 user, user_group), namespace='security.usergroup')
423 user, user_group), namespace='security.usergroup')
424
424
425 def grant_user_group_permission(self, target_user_group, user_group, perm):
425 def grant_user_group_permission(self, target_user_group, user_group, perm):
426 """
426 """
427 Grant user group permission for given target_user_group
427 Grant user group permission for given target_user_group
428
428
429 :param target_user_group:
429 :param target_user_group:
430 :param user_group:
430 :param user_group:
431 :param perm:
431 :param perm:
432 """
432 """
433 target_user_group = self._get_user_group(target_user_group)
433 target_user_group = self._get_user_group(target_user_group)
434 user_group = self._get_user_group(user_group)
434 user_group = self._get_user_group(user_group)
435 permission = self._get_perm(perm)
435 permission = self._get_perm(perm)
436 # forbid assigning same user group to itself
436 # forbid assigning same user group to itself
437 if target_user_group == user_group:
437 if target_user_group == user_group:
438 raise RepoGroupAssignmentError('target repo:%s cannot be '
438 raise RepoGroupAssignmentError('target repo:%s cannot be '
439 'assigned to itself' % target_user_group)
439 'assigned to itself' % target_user_group)
440
440
441 # check if we have that permission already
441 # check if we have that permission already
442 obj = self.sa.query(UserGroupUserGroupToPerm)\
442 obj = self.sa.query(UserGroupUserGroupToPerm)\
443 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
443 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
444 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
444 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
445 .scalar()
445 .scalar()
446 if obj is None:
446 if obj is None:
447 # create new !
447 # create new !
448 obj = UserGroupUserGroupToPerm()
448 obj = UserGroupUserGroupToPerm()
449 obj.user_group = user_group
449 obj.user_group = user_group
450 obj.target_user_group = target_user_group
450 obj.target_user_group = target_user_group
451 obj.permission = permission
451 obj.permission = permission
452 self.sa.add(obj)
452 self.sa.add(obj)
453 log.debug(
453 log.debug(
454 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
454 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
455 action_logger_generic(
455 action_logger_generic(
456 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
456 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
457 perm, user_group, target_user_group),
457 perm, user_group, target_user_group),
458 namespace='security.usergroup')
458 namespace='security.usergroup')
459
459
460 return obj
460 return obj
461
461
462 def revoke_user_group_permission(self, target_user_group, user_group):
462 def revoke_user_group_permission(self, target_user_group, user_group):
463 """
463 """
464 Revoke user group permission for given target_user_group
464 Revoke user group permission for given target_user_group
465
465
466 :param target_user_group:
466 :param target_user_group:
467 :param user_group:
467 :param user_group:
468 """
468 """
469 target_user_group = self._get_user_group(target_user_group)
469 target_user_group = self._get_user_group(target_user_group)
470 user_group = self._get_user_group(user_group)
470 user_group = self._get_user_group(user_group)
471
471
472 obj = self.sa.query(UserGroupUserGroupToPerm)\
472 obj = self.sa.query(UserGroupUserGroupToPerm)\
473 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
473 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
474 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
474 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
475 .scalar()
475 .scalar()
476 if obj:
476 if obj:
477 self.sa.delete(obj)
477 self.sa.delete(obj)
478 log.debug(
478 log.debug(
479 'Revoked perm on %s on %s', target_user_group, user_group)
479 'Revoked perm on %s on %s', target_user_group, user_group)
480 action_logger_generic(
480 action_logger_generic(
481 'revoked permission from usergroup: {} on usergroup: {}'.format(
481 'revoked permission from usergroup: {} on usergroup: {}'.format(
482 user_group, target_user_group),
482 user_group, target_user_group),
483 namespace='security.repogroup')
483 namespace='security.repogroup')
484
484
485 def enforce_groups(self, user, groups, extern_type=None):
485 def enforce_groups(self, user, groups, extern_type=None):
486 user = self._get_user(user)
486 user = self._get_user(user)
487 log.debug('Enforcing groups %s on user %s', groups, user)
487 log.debug('Enforcing groups %s on user %s', groups, user)
488 current_groups = user.group_member
488 current_groups = user.group_member
489 # find the external created groups
489 # find the external created groups
490 externals = [x.users_group for x in current_groups
490 externals = [x.users_group for x in current_groups
491 if 'extern_type' in x.users_group.group_data]
491 if 'extern_type' in x.users_group.group_data]
492
492
493 # calculate from what groups user should be removed
493 # calculate from what groups user should be removed
494 # externals that are not in groups
494 # externals that are not in groups
495 for gr in externals:
495 for gr in externals:
496 if gr.users_group_name not in groups:
496 if gr.users_group_name not in groups:
497 log.debug('Removing user %s from user group %s', user, gr)
497 log.debug('Removing user %s from user group %s', user, gr)
498 self.remove_user_from_group(gr, user)
498 self.remove_user_from_group(gr, user)
499
499
500 # now we calculate in which groups user should be == groups params
500 # now we calculate in which groups user should be == groups params
501 owner = User.get_first_admin().username
501 owner = User.get_first_super_admin().username
502 for gr in set(groups):
502 for gr in set(groups):
503 existing_group = UserGroup.get_by_group_name(gr)
503 existing_group = UserGroup.get_by_group_name(gr)
504 if not existing_group:
504 if not existing_group:
505 desc = 'Automatically created from plugin:%s' % extern_type
505 desc = 'Automatically created from plugin:%s' % extern_type
506 # we use first admin account to set the owner of the group
506 # we use first admin account to set the owner of the group
507 existing_group = UserGroupModel().create(gr, desc, owner,
507 existing_group = UserGroupModel().create(gr, desc, owner,
508 group_data={'extern_type': extern_type})
508 group_data={'extern_type': extern_type})
509
509
510 # we can only add users to special groups created via plugins
510 # we can only add users to special groups created via plugins
511 managed = 'extern_type' in existing_group.group_data
511 managed = 'extern_type' in existing_group.group_data
512 if managed:
512 if managed:
513 log.debug('Adding user %s to user group %s', user, gr)
513 log.debug('Adding user %s to user group %s', user, gr)
514 UserGroupModel().add_user_to_group(existing_group, user)
514 UserGroupModel().add_user_to_group(existing_group, user)
515 else:
515 else:
516 log.debug('Skipping addition to group %s since it is '
516 log.debug('Skipping addition to group %s since it is '
517 'not managed by auth plugins' % gr)
517 'not managed by auth plugins' % gr)
@@ -1,54 +1,54 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 from rhodecode.model.db import User
21 from rhodecode.model.db import User
22 from rhodecode.tests import *
22 from rhodecode.tests import *
23
23
24
24
25 class TestFeedController(TestController):
25 class TestFeedController(TestController):
26
26
27 def test_rss(self, backend):
27 def test_rss(self, backend):
28 self.log_user()
28 self.log_user()
29 response = self.app.get(url(controller='feed', action='rss',
29 response = self.app.get(url(controller='feed', action='rss',
30 repo_name=backend.repo_name))
30 repo_name=backend.repo_name))
31
31
32 assert response.content_type == "application/rss+xml"
32 assert response.content_type == "application/rss+xml"
33 assert """<rss version="2.0">""" in response
33 assert """<rss version="2.0">""" in response
34
34
35 def test_rss_with_auth_token(self, backend):
35 def test_rss_with_auth_token(self, backend):
36 auth_token = User.get_first_admin().feed_token
36 auth_token = User.get_first_super_admin().feed_token
37 assert auth_token != ''
37 assert auth_token != ''
38 response = self.app.get(url(controller='feed', action='rss',
38 response = self.app.get(url(controller='feed', action='rss',
39 repo_name=backend.repo_name, auth_token=auth_token))
39 repo_name=backend.repo_name, auth_token=auth_token))
40
40
41 assert response.content_type == "application/rss+xml"
41 assert response.content_type == "application/rss+xml"
42 assert """<rss version="2.0">""" in response
42 assert """<rss version="2.0">""" in response
43
43
44 def test_atom(self, backend):
44 def test_atom(self, backend):
45 self.log_user()
45 self.log_user()
46 response = self.app.get(url(controller='feed', action='atom',
46 response = self.app.get(url(controller='feed', action='atom',
47 repo_name=backend.repo_name))
47 repo_name=backend.repo_name))
48
48
49 assert response.content_type == """application/atom+xml"""
49 assert response.content_type == """application/atom+xml"""
50 assert """<?xml version="1.0" encoding="utf-8"?>""" in response
50 assert """<?xml version="1.0" encoding="utf-8"?>""" in response
51
51
52 tag1 = '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">'
52 tag1 = '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">'
53 tag2 = '<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">'
53 tag2 = '<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">'
54 assert tag1 in response or tag2 in response
54 assert tag1 in response or tag2 in response
@@ -1,519 +1,519 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 from rhodecode.lib.auth import check_password, generate_auth_token
31 from rhodecode.lib.auth import check_password, generate_auth_token
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model import validators
34 from rhodecode.model import validators
35 from rhodecode.model.db import User, Notification
35 from rhodecode.model.db import User, Notification
36 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
37
37
38 fixture = Fixture()
38 fixture = Fixture()
39
39
40 # Hardcode URLs because we don't have a request object to use
40 # Hardcode URLs because we don't have a request object to use
41 # pyramids URL generation methods.
41 # pyramids URL generation methods.
42 login_url = ADMIN_PREFIX + '/login'
42 login_url = ADMIN_PREFIX + '/login'
43 logut_url = ADMIN_PREFIX + '/logout'
43 logut_url = ADMIN_PREFIX + '/logout'
44 register_url = ADMIN_PREFIX + '/register'
44 register_url = ADMIN_PREFIX + '/register'
45 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
45 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
46 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
46 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
47
47
48
48
49 @pytest.mark.usefixtures('app')
49 @pytest.mark.usefixtures('app')
50 class TestLoginController:
50 class TestLoginController:
51 destroy_users = set()
51 destroy_users = set()
52
52
53 @classmethod
53 @classmethod
54 def teardown_class(cls):
54 def teardown_class(cls):
55 fixture.destroy_users(cls.destroy_users)
55 fixture.destroy_users(cls.destroy_users)
56
56
57 def teardown_method(self, method):
57 def teardown_method(self, method):
58 for n in Notification.query().all():
58 for n in Notification.query().all():
59 Session().delete(n)
59 Session().delete(n)
60
60
61 Session().commit()
61 Session().commit()
62 assert Notification.query().all() == []
62 assert Notification.query().all() == []
63
63
64 def test_index(self):
64 def test_index(self):
65 response = self.app.get(login_url)
65 response = self.app.get(login_url)
66 assert response.status == '200 OK'
66 assert response.status == '200 OK'
67 # Test response...
67 # Test response...
68
68
69 def test_login_admin_ok(self):
69 def test_login_admin_ok(self):
70 response = self.app.post(login_url,
70 response = self.app.post(login_url,
71 {'username': 'test_admin',
71 {'username': 'test_admin',
72 'password': 'test12'})
72 'password': 'test12'})
73 assert response.status == '302 Found'
73 assert response.status == '302 Found'
74 session = get_session_from_response(response)
74 session = get_session_from_response(response)
75 username = session['rhodecode_user'].get('username')
75 username = session['rhodecode_user'].get('username')
76 assert username == 'test_admin'
76 assert username == 'test_admin'
77 response = response.follow()
77 response = response.follow()
78 response.mustcontain('/%s' % HG_REPO)
78 response.mustcontain('/%s' % HG_REPO)
79
79
80 def test_login_regular_ok(self):
80 def test_login_regular_ok(self):
81 response = self.app.post(login_url,
81 response = self.app.post(login_url,
82 {'username': 'test_regular',
82 {'username': 'test_regular',
83 'password': 'test12'})
83 'password': 'test12'})
84
84
85 assert response.status == '302 Found'
85 assert response.status == '302 Found'
86 session = get_session_from_response(response)
86 session = get_session_from_response(response)
87 username = session['rhodecode_user'].get('username')
87 username = session['rhodecode_user'].get('username')
88 assert username == 'test_regular'
88 assert username == 'test_regular'
89 response = response.follow()
89 response = response.follow()
90 response.mustcontain('/%s' % HG_REPO)
90 response.mustcontain('/%s' % HG_REPO)
91
91
92 def test_login_ok_came_from(self):
92 def test_login_ok_came_from(self):
93 test_came_from = '/_admin/users?branch=stable'
93 test_came_from = '/_admin/users?branch=stable'
94 _url = '{}?came_from={}'.format(login_url, test_came_from)
94 _url = '{}?came_from={}'.format(login_url, test_came_from)
95 response = self.app.post(
95 response = self.app.post(
96 _url, {'username': 'test_admin', 'password': 'test12'})
96 _url, {'username': 'test_admin', 'password': 'test12'})
97 assert response.status == '302 Found'
97 assert response.status == '302 Found'
98 assert 'branch=stable' in response.location
98 assert 'branch=stable' in response.location
99 response = response.follow()
99 response = response.follow()
100
100
101 assert response.status == '200 OK'
101 assert response.status == '200 OK'
102 response.mustcontain('Users administration')
102 response.mustcontain('Users administration')
103
103
104 def test_redirect_to_login_with_get_args(self):
104 def test_redirect_to_login_with_get_args(self):
105 with fixture.anon_access(False):
105 with fixture.anon_access(False):
106 kwargs = {'branch': 'stable'}
106 kwargs = {'branch': 'stable'}
107 response = self.app.get(
107 response = self.app.get(
108 url('summary_home', repo_name=HG_REPO, **kwargs))
108 url('summary_home', repo_name=HG_REPO, **kwargs))
109 assert response.status == '302 Found'
109 assert response.status == '302 Found'
110 response_query = urlparse.parse_qsl(response.location)
110 response_query = urlparse.parse_qsl(response.location)
111 assert 'branch=stable' in response_query[0][1]
111 assert 'branch=stable' in response_query[0][1]
112
112
113 def test_login_form_with_get_args(self):
113 def test_login_form_with_get_args(self):
114 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
114 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
115 response = self.app.get(_url)
115 response = self.app.get(_url)
116 assert 'branch%3Dstable' in response.form.action
116 assert 'branch%3Dstable' in response.form.action
117
117
118 @pytest.mark.parametrize("url_came_from", [
118 @pytest.mark.parametrize("url_came_from", [
119 'data:text/html,<script>window.alert("xss")</script>',
119 'data:text/html,<script>window.alert("xss")</script>',
120 'mailto:test@rhodecode.org',
120 'mailto:test@rhodecode.org',
121 'file:///etc/passwd',
121 'file:///etc/passwd',
122 'ftp://some.ftp.server',
122 'ftp://some.ftp.server',
123 'http://other.domain',
123 'http://other.domain',
124 '/\r\nX-Forwarded-Host: http://example.org',
124 '/\r\nX-Forwarded-Host: http://example.org',
125 ])
125 ])
126 def test_login_bad_came_froms(self, url_came_from):
126 def test_login_bad_came_froms(self, url_came_from):
127 _url = '{}?came_from={}'.format(login_url, url_came_from)
127 _url = '{}?came_from={}'.format(login_url, url_came_from)
128 response = self.app.post(
128 response = self.app.post(
129 _url,
129 _url,
130 {'username': 'test_admin', 'password': 'test12'})
130 {'username': 'test_admin', 'password': 'test12'})
131 assert response.status == '302 Found'
131 assert response.status == '302 Found'
132 response = response.follow()
132 response = response.follow()
133 assert response.status == '200 OK'
133 assert response.status == '200 OK'
134 assert response.request.path == '/'
134 assert response.request.path == '/'
135
135
136 def test_login_short_password(self):
136 def test_login_short_password(self):
137 response = self.app.post(login_url,
137 response = self.app.post(login_url,
138 {'username': 'test_admin',
138 {'username': 'test_admin',
139 'password': 'as'})
139 'password': 'as'})
140 assert response.status == '200 OK'
140 assert response.status == '200 OK'
141
141
142 response.mustcontain('Enter 3 characters or more')
142 response.mustcontain('Enter 3 characters or more')
143
143
144 def test_login_wrong_non_ascii_password(self, user_regular):
144 def test_login_wrong_non_ascii_password(self, user_regular):
145 response = self.app.post(
145 response = self.app.post(
146 login_url,
146 login_url,
147 {'username': user_regular.username,
147 {'username': user_regular.username,
148 'password': u'invalid-non-asci\xe4'.encode('utf8')})
148 'password': u'invalid-non-asci\xe4'.encode('utf8')})
149
149
150 response.mustcontain('invalid user name')
150 response.mustcontain('invalid user name')
151 response.mustcontain('invalid password')
151 response.mustcontain('invalid password')
152
152
153 def test_login_with_non_ascii_password(self, user_util):
153 def test_login_with_non_ascii_password(self, user_util):
154 password = u'valid-non-ascii\xe4'
154 password = u'valid-non-ascii\xe4'
155 user = user_util.create_user(password=password)
155 user = user_util.create_user(password=password)
156 response = self.app.post(
156 response = self.app.post(
157 login_url,
157 login_url,
158 {'username': user.username,
158 {'username': user.username,
159 'password': password.encode('utf-8')})
159 'password': password.encode('utf-8')})
160 assert response.status_code == 302
160 assert response.status_code == 302
161
161
162 def test_login_wrong_username_password(self):
162 def test_login_wrong_username_password(self):
163 response = self.app.post(login_url,
163 response = self.app.post(login_url,
164 {'username': 'error',
164 {'username': 'error',
165 'password': 'test12'})
165 'password': 'test12'})
166
166
167 response.mustcontain('invalid user name')
167 response.mustcontain('invalid user name')
168 response.mustcontain('invalid password')
168 response.mustcontain('invalid password')
169
169
170 def test_login_admin_ok_password_migration(self, real_crypto_backend):
170 def test_login_admin_ok_password_migration(self, real_crypto_backend):
171 from rhodecode.lib import auth
171 from rhodecode.lib import auth
172
172
173 # create new user, with sha256 password
173 # create new user, with sha256 password
174 temp_user = 'test_admin_sha256'
174 temp_user = 'test_admin_sha256'
175 user = fixture.create_user(temp_user)
175 user = fixture.create_user(temp_user)
176 user.password = auth._RhodeCodeCryptoSha256().hash_create(
176 user.password = auth._RhodeCodeCryptoSha256().hash_create(
177 b'test123')
177 b'test123')
178 Session().add(user)
178 Session().add(user)
179 Session().commit()
179 Session().commit()
180 self.destroy_users.add(temp_user)
180 self.destroy_users.add(temp_user)
181 response = self.app.post(login_url,
181 response = self.app.post(login_url,
182 {'username': temp_user,
182 {'username': temp_user,
183 'password': 'test123'})
183 'password': 'test123'})
184
184
185 assert response.status == '302 Found'
185 assert response.status == '302 Found'
186 session = get_session_from_response(response)
186 session = get_session_from_response(response)
187 username = session['rhodecode_user'].get('username')
187 username = session['rhodecode_user'].get('username')
188 assert username == temp_user
188 assert username == temp_user
189 response = response.follow()
189 response = response.follow()
190 response.mustcontain('/%s' % HG_REPO)
190 response.mustcontain('/%s' % HG_REPO)
191
191
192 # new password should be bcrypted, after log-in and transfer
192 # new password should be bcrypted, after log-in and transfer
193 user = User.get_by_username(temp_user)
193 user = User.get_by_username(temp_user)
194 assert user.password.startswith('$')
194 assert user.password.startswith('$')
195
195
196 # REGISTRATIONS
196 # REGISTRATIONS
197 def test_register(self):
197 def test_register(self):
198 response = self.app.get(register_url)
198 response = self.app.get(register_url)
199 response.mustcontain('Create an Account')
199 response.mustcontain('Create an Account')
200
200
201 def test_register_err_same_username(self):
201 def test_register_err_same_username(self):
202 uname = 'test_admin'
202 uname = 'test_admin'
203 response = self.app.post(
203 response = self.app.post(
204 register_url,
204 register_url,
205 {
205 {
206 'username': uname,
206 'username': uname,
207 'password': 'test12',
207 'password': 'test12',
208 'password_confirmation': 'test12',
208 'password_confirmation': 'test12',
209 'email': 'goodmail@domain.com',
209 'email': 'goodmail@domain.com',
210 'firstname': 'test',
210 'firstname': 'test',
211 'lastname': 'test'
211 'lastname': 'test'
212 }
212 }
213 )
213 )
214
214
215 assertr = AssertResponse(response)
215 assertr = AssertResponse(response)
216 msg = validators.ValidUsername()._messages['username_exists']
216 msg = validators.ValidUsername()._messages['username_exists']
217 msg = msg % {'username': uname}
217 msg = msg % {'username': uname}
218 assertr.element_contains('#username+.error-message', msg)
218 assertr.element_contains('#username+.error-message', msg)
219
219
220 def test_register_err_same_email(self):
220 def test_register_err_same_email(self):
221 response = self.app.post(
221 response = self.app.post(
222 register_url,
222 register_url,
223 {
223 {
224 'username': 'test_admin_0',
224 'username': 'test_admin_0',
225 'password': 'test12',
225 'password': 'test12',
226 'password_confirmation': 'test12',
226 'password_confirmation': 'test12',
227 'email': 'test_admin@mail.com',
227 'email': 'test_admin@mail.com',
228 'firstname': 'test',
228 'firstname': 'test',
229 'lastname': 'test'
229 'lastname': 'test'
230 }
230 }
231 )
231 )
232
232
233 assertr = AssertResponse(response)
233 assertr = AssertResponse(response)
234 msg = validators.UniqSystemEmail()()._messages['email_taken']
234 msg = validators.UniqSystemEmail()()._messages['email_taken']
235 assertr.element_contains('#email+.error-message', msg)
235 assertr.element_contains('#email+.error-message', msg)
236
236
237 def test_register_err_same_email_case_sensitive(self):
237 def test_register_err_same_email_case_sensitive(self):
238 response = self.app.post(
238 response = self.app.post(
239 register_url,
239 register_url,
240 {
240 {
241 'username': 'test_admin_1',
241 'username': 'test_admin_1',
242 'password': 'test12',
242 'password': 'test12',
243 'password_confirmation': 'test12',
243 'password_confirmation': 'test12',
244 'email': 'TesT_Admin@mail.COM',
244 'email': 'TesT_Admin@mail.COM',
245 'firstname': 'test',
245 'firstname': 'test',
246 'lastname': 'test'
246 'lastname': 'test'
247 }
247 }
248 )
248 )
249 assertr = AssertResponse(response)
249 assertr = AssertResponse(response)
250 msg = validators.UniqSystemEmail()()._messages['email_taken']
250 msg = validators.UniqSystemEmail()()._messages['email_taken']
251 assertr.element_contains('#email+.error-message', msg)
251 assertr.element_contains('#email+.error-message', msg)
252
252
253 def test_register_err_wrong_data(self):
253 def test_register_err_wrong_data(self):
254 response = self.app.post(
254 response = self.app.post(
255 register_url,
255 register_url,
256 {
256 {
257 'username': 'xs',
257 'username': 'xs',
258 'password': 'test',
258 'password': 'test',
259 'password_confirmation': 'test',
259 'password_confirmation': 'test',
260 'email': 'goodmailm',
260 'email': 'goodmailm',
261 'firstname': 'test',
261 'firstname': 'test',
262 'lastname': 'test'
262 'lastname': 'test'
263 }
263 }
264 )
264 )
265 assert response.status == '200 OK'
265 assert response.status == '200 OK'
266 response.mustcontain('An email address must contain a single @')
266 response.mustcontain('An email address must contain a single @')
267 response.mustcontain('Enter a value 6 characters long or more')
267 response.mustcontain('Enter a value 6 characters long or more')
268
268
269 def test_register_err_username(self):
269 def test_register_err_username(self):
270 response = self.app.post(
270 response = self.app.post(
271 register_url,
271 register_url,
272 {
272 {
273 'username': 'error user',
273 'username': 'error user',
274 'password': 'test12',
274 'password': 'test12',
275 'password_confirmation': 'test12',
275 'password_confirmation': 'test12',
276 'email': 'goodmailm',
276 'email': 'goodmailm',
277 'firstname': 'test',
277 'firstname': 'test',
278 'lastname': 'test'
278 'lastname': 'test'
279 }
279 }
280 )
280 )
281
281
282 response.mustcontain('An email address must contain a single @')
282 response.mustcontain('An email address must contain a single @')
283 response.mustcontain(
283 response.mustcontain(
284 'Username may only contain '
284 'Username may only contain '
285 'alphanumeric characters underscores, '
285 'alphanumeric characters underscores, '
286 'periods or dashes and must begin with '
286 'periods or dashes and must begin with '
287 'alphanumeric character')
287 'alphanumeric character')
288
288
289 def test_register_err_case_sensitive(self):
289 def test_register_err_case_sensitive(self):
290 usr = 'Test_Admin'
290 usr = 'Test_Admin'
291 response = self.app.post(
291 response = self.app.post(
292 register_url,
292 register_url,
293 {
293 {
294 'username': usr,
294 'username': usr,
295 'password': 'test12',
295 'password': 'test12',
296 'password_confirmation': 'test12',
296 'password_confirmation': 'test12',
297 'email': 'goodmailm',
297 'email': 'goodmailm',
298 'firstname': 'test',
298 'firstname': 'test',
299 'lastname': 'test'
299 'lastname': 'test'
300 }
300 }
301 )
301 )
302
302
303 assertr = AssertResponse(response)
303 assertr = AssertResponse(response)
304 msg = validators.ValidUsername()._messages['username_exists']
304 msg = validators.ValidUsername()._messages['username_exists']
305 msg = msg % {'username': usr}
305 msg = msg % {'username': usr}
306 assertr.element_contains('#username+.error-message', msg)
306 assertr.element_contains('#username+.error-message', msg)
307
307
308 def test_register_special_chars(self):
308 def test_register_special_chars(self):
309 response = self.app.post(
309 response = self.app.post(
310 register_url,
310 register_url,
311 {
311 {
312 'username': 'xxxaxn',
312 'username': 'xxxaxn',
313 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
313 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
314 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
314 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
315 'email': 'goodmailm@test.plx',
315 'email': 'goodmailm@test.plx',
316 'firstname': 'test',
316 'firstname': 'test',
317 'lastname': 'test'
317 'lastname': 'test'
318 }
318 }
319 )
319 )
320
320
321 msg = validators.ValidPassword()._messages['invalid_password']
321 msg = validators.ValidPassword()._messages['invalid_password']
322 response.mustcontain(msg)
322 response.mustcontain(msg)
323
323
324 def test_register_password_mismatch(self):
324 def test_register_password_mismatch(self):
325 response = self.app.post(
325 response = self.app.post(
326 register_url,
326 register_url,
327 {
327 {
328 'username': 'xs',
328 'username': 'xs',
329 'password': '123qwe',
329 'password': '123qwe',
330 'password_confirmation': 'qwe123',
330 'password_confirmation': 'qwe123',
331 'email': 'goodmailm@test.plxa',
331 'email': 'goodmailm@test.plxa',
332 'firstname': 'test',
332 'firstname': 'test',
333 'lastname': 'test'
333 'lastname': 'test'
334 }
334 }
335 )
335 )
336 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
336 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
337 response.mustcontain(msg)
337 response.mustcontain(msg)
338
338
339 def test_register_ok(self):
339 def test_register_ok(self):
340 username = 'test_regular4'
340 username = 'test_regular4'
341 password = 'qweqwe'
341 password = 'qweqwe'
342 email = 'marcin@test.com'
342 email = 'marcin@test.com'
343 name = 'testname'
343 name = 'testname'
344 lastname = 'testlastname'
344 lastname = 'testlastname'
345
345
346 response = self.app.post(
346 response = self.app.post(
347 register_url,
347 register_url,
348 {
348 {
349 'username': username,
349 'username': username,
350 'password': password,
350 'password': password,
351 'password_confirmation': password,
351 'password_confirmation': password,
352 'email': email,
352 'email': email,
353 'firstname': name,
353 'firstname': name,
354 'lastname': lastname,
354 'lastname': lastname,
355 'admin': True
355 'admin': True
356 }
356 }
357 ) # This should be overriden
357 ) # This should be overriden
358 assert response.status == '302 Found'
358 assert response.status == '302 Found'
359 assert_session_flash(
359 assert_session_flash(
360 response, 'You have successfully registered with RhodeCode')
360 response, 'You have successfully registered with RhodeCode')
361
361
362 ret = Session().query(User).filter(
362 ret = Session().query(User).filter(
363 User.username == 'test_regular4').one()
363 User.username == 'test_regular4').one()
364 assert ret.username == username
364 assert ret.username == username
365 assert check_password(password, ret.password)
365 assert check_password(password, ret.password)
366 assert ret.email == email
366 assert ret.email == email
367 assert ret.name == name
367 assert ret.name == name
368 assert ret.lastname == lastname
368 assert ret.lastname == lastname
369 assert ret.api_key is not None
369 assert ret.api_key is not None
370 assert not ret.admin
370 assert not ret.admin
371
371
372 def test_forgot_password_wrong_mail(self):
372 def test_forgot_password_wrong_mail(self):
373 bad_email = 'marcin@wrongmail.org'
373 bad_email = 'marcin@wrongmail.org'
374 response = self.app.post(
374 response = self.app.post(
375 pwd_reset_url,
375 pwd_reset_url,
376 {'email': bad_email, }
376 {'email': bad_email, }
377 )
377 )
378
378
379 msg = validators.ValidSystemEmail()._messages['non_existing_email']
379 msg = validators.ValidSystemEmail()._messages['non_existing_email']
380 msg = h.html_escape(msg % {'email': bad_email})
380 msg = h.html_escape(msg % {'email': bad_email})
381 response.mustcontain()
381 response.mustcontain()
382
382
383 def test_forgot_password(self):
383 def test_forgot_password(self):
384 response = self.app.get(pwd_reset_url)
384 response = self.app.get(pwd_reset_url)
385 assert response.status == '200 OK'
385 assert response.status == '200 OK'
386
386
387 username = 'test_password_reset_1'
387 username = 'test_password_reset_1'
388 password = 'qweqwe'
388 password = 'qweqwe'
389 email = 'marcin@python-works.com'
389 email = 'marcin@python-works.com'
390 name = 'passwd'
390 name = 'passwd'
391 lastname = 'reset'
391 lastname = 'reset'
392
392
393 new = User()
393 new = User()
394 new.username = username
394 new.username = username
395 new.password = password
395 new.password = password
396 new.email = email
396 new.email = email
397 new.name = name
397 new.name = name
398 new.lastname = lastname
398 new.lastname = lastname
399 new.api_key = generate_auth_token(username)
399 new.api_key = generate_auth_token(username)
400 Session().add(new)
400 Session().add(new)
401 Session().commit()
401 Session().commit()
402
402
403 response = self.app.post(pwd_reset_url,
403 response = self.app.post(pwd_reset_url,
404 {'email': email, })
404 {'email': email, })
405
405
406 assert_session_flash(
406 assert_session_flash(
407 response, 'Your password reset link was sent')
407 response, 'Your password reset link was sent')
408
408
409 response = response.follow()
409 response = response.follow()
410
410
411 # BAD KEY
411 # BAD KEY
412
412
413 key = "bad"
413 key = "bad"
414 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
414 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
415 response = self.app.get(confirm_url)
415 response = self.app.get(confirm_url)
416 assert response.status == '302 Found'
416 assert response.status == '302 Found'
417 assert response.location.endswith(pwd_reset_url)
417 assert response.location.endswith(pwd_reset_url)
418
418
419 # GOOD KEY
419 # GOOD KEY
420
420
421 key = User.get_by_username(username).api_key
421 key = User.get_by_username(username).api_key
422 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
422 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
423 response = self.app.get(confirm_url)
423 response = self.app.get(confirm_url)
424 assert response.status == '302 Found'
424 assert response.status == '302 Found'
425 assert response.location.endswith(login_url)
425 assert response.location.endswith(login_url)
426
426
427 assert_session_flash(
427 assert_session_flash(
428 response,
428 response,
429 'Your password reset was successful, '
429 'Your password reset was successful, '
430 'a new password has been sent to your email')
430 'a new password has been sent to your email')
431
431
432 response = response.follow()
432 response = response.follow()
433
433
434 def _get_api_whitelist(self, values=None):
434 def _get_api_whitelist(self, values=None):
435 config = {'api_access_controllers_whitelist': values or []}
435 config = {'api_access_controllers_whitelist': values or []}
436 return config
436 return config
437
437
438 @pytest.mark.parametrize("test_name, auth_token", [
438 @pytest.mark.parametrize("test_name, auth_token", [
439 ('none', None),
439 ('none', None),
440 ('empty_string', ''),
440 ('empty_string', ''),
441 ('fake_number', '123456'),
441 ('fake_number', '123456'),
442 ('proper_auth_token', None)
442 ('proper_auth_token', None)
443 ])
443 ])
444 def test_access_not_whitelisted_page_via_auth_token(self, test_name,
444 def test_access_not_whitelisted_page_via_auth_token(self, test_name,
445 auth_token):
445 auth_token):
446 whitelist = self._get_api_whitelist([])
446 whitelist = self._get_api_whitelist([])
447 with mock.patch.dict('rhodecode.CONFIG', whitelist):
447 with mock.patch.dict('rhodecode.CONFIG', whitelist):
448 assert [] == whitelist['api_access_controllers_whitelist']
448 assert [] == whitelist['api_access_controllers_whitelist']
449 if test_name == 'proper_auth_token':
449 if test_name == 'proper_auth_token':
450 # use builtin if api_key is None
450 # use builtin if api_key is None
451 auth_token = User.get_first_admin().api_key
451 auth_token = User.get_first_super_admin().api_key
452
452
453 with fixture.anon_access(False):
453 with fixture.anon_access(False):
454 self.app.get(url(controller='changeset',
454 self.app.get(url(controller='changeset',
455 action='changeset_raw',
455 action='changeset_raw',
456 repo_name=HG_REPO, revision='tip',
456 repo_name=HG_REPO, revision='tip',
457 api_key=auth_token),
457 api_key=auth_token),
458 status=302)
458 status=302)
459
459
460 @pytest.mark.parametrize("test_name, auth_token, code", [
460 @pytest.mark.parametrize("test_name, auth_token, code", [
461 ('none', None, 302),
461 ('none', None, 302),
462 ('empty_string', '', 302),
462 ('empty_string', '', 302),
463 ('fake_number', '123456', 302),
463 ('fake_number', '123456', 302),
464 ('proper_auth_token', None, 200)
464 ('proper_auth_token', None, 200)
465 ])
465 ])
466 def test_access_whitelisted_page_via_auth_token(self, test_name,
466 def test_access_whitelisted_page_via_auth_token(self, test_name,
467 auth_token, code):
467 auth_token, code):
468 whitelist = self._get_api_whitelist(
468 whitelist = self._get_api_whitelist(
469 ['ChangesetController:changeset_raw'])
469 ['ChangesetController:changeset_raw'])
470 with mock.patch.dict('rhodecode.CONFIG', whitelist):
470 with mock.patch.dict('rhodecode.CONFIG', whitelist):
471 assert ['ChangesetController:changeset_raw'] == \
471 assert ['ChangesetController:changeset_raw'] == \
472 whitelist['api_access_controllers_whitelist']
472 whitelist['api_access_controllers_whitelist']
473 if test_name == 'proper_auth_token':
473 if test_name == 'proper_auth_token':
474 auth_token = User.get_first_admin().api_key
474 auth_token = User.get_first_super_admin().api_key
475
475
476 with fixture.anon_access(False):
476 with fixture.anon_access(False):
477 self.app.get(url(controller='changeset',
477 self.app.get(url(controller='changeset',
478 action='changeset_raw',
478 action='changeset_raw',
479 repo_name=HG_REPO, revision='tip',
479 repo_name=HG_REPO, revision='tip',
480 api_key=auth_token),
480 api_key=auth_token),
481 status=code)
481 status=code)
482
482
483 def test_access_page_via_extra_auth_token(self):
483 def test_access_page_via_extra_auth_token(self):
484 whitelist = self._get_api_whitelist(
484 whitelist = self._get_api_whitelist(
485 ['ChangesetController:changeset_raw'])
485 ['ChangesetController:changeset_raw'])
486 with mock.patch.dict('rhodecode.CONFIG', whitelist):
486 with mock.patch.dict('rhodecode.CONFIG', whitelist):
487 assert ['ChangesetController:changeset_raw'] == \
487 assert ['ChangesetController:changeset_raw'] == \
488 whitelist['api_access_controllers_whitelist']
488 whitelist['api_access_controllers_whitelist']
489
489
490 new_auth_token = AuthTokenModel().create(
490 new_auth_token = AuthTokenModel().create(
491 TEST_USER_ADMIN_LOGIN, 'test')
491 TEST_USER_ADMIN_LOGIN, 'test')
492 Session().commit()
492 Session().commit()
493 with fixture.anon_access(False):
493 with fixture.anon_access(False):
494 self.app.get(url(controller='changeset',
494 self.app.get(url(controller='changeset',
495 action='changeset_raw',
495 action='changeset_raw',
496 repo_name=HG_REPO, revision='tip',
496 repo_name=HG_REPO, revision='tip',
497 api_key=new_auth_token.api_key),
497 api_key=new_auth_token.api_key),
498 status=200)
498 status=200)
499
499
500 def test_access_page_via_expired_auth_token(self):
500 def test_access_page_via_expired_auth_token(self):
501 whitelist = self._get_api_whitelist(
501 whitelist = self._get_api_whitelist(
502 ['ChangesetController:changeset_raw'])
502 ['ChangesetController:changeset_raw'])
503 with mock.patch.dict('rhodecode.CONFIG', whitelist):
503 with mock.patch.dict('rhodecode.CONFIG', whitelist):
504 assert ['ChangesetController:changeset_raw'] == \
504 assert ['ChangesetController:changeset_raw'] == \
505 whitelist['api_access_controllers_whitelist']
505 whitelist['api_access_controllers_whitelist']
506
506
507 new_auth_token = AuthTokenModel().create(
507 new_auth_token = AuthTokenModel().create(
508 TEST_USER_ADMIN_LOGIN, 'test')
508 TEST_USER_ADMIN_LOGIN, 'test')
509 Session().commit()
509 Session().commit()
510 # patch the api key and make it expired
510 # patch the api key and make it expired
511 new_auth_token.expires = 0
511 new_auth_token.expires = 0
512 Session().add(new_auth_token)
512 Session().add(new_auth_token)
513 Session().commit()
513 Session().commit()
514 with fixture.anon_access(False):
514 with fixture.anon_access(False):
515 self.app.get(url(controller='changeset',
515 self.app.get(url(controller='changeset',
516 action='changeset_raw',
516 action='changeset_raw',
517 repo_name=HG_REPO, revision='tip',
517 repo_name=HG_REPO, revision='tip',
518 api_key=new_auth_token.api_key),
518 api_key=new_auth_token.api_key),
519 status=302)
519 status=302)
General Comments 0
You need to be logged in to leave comments. Login now