##// END OF EJS Templates
notifications: store notification status in channelstream
ergo -
r734:1eb83256 default
parent child Browse files
Show More
@@ -1,373 +1,371 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2016 RhodeCode GmbH
3 # Copyright (C) 2013-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 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27
27
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, session
30 from pylons import request, tmpl_context as c, url, session
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from rhodecode import forms
35 from rhodecode import forms
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import auth
37 from rhodecode.lib import auth
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
39 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.utils import jsonify
41 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils2 import safe_int, md5
42 from rhodecode.lib.utils2 import safe_int, md5
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44
44
45 from rhodecode.model.validation_schema.schemas import user_schema
45 from rhodecode.model.validation_schema.schemas import user_schema
46 from rhodecode.model.db import (
46 from rhodecode.model.db import (
47 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
47 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
48 UserFollowing)
48 UserFollowing)
49 from rhodecode.model.forms import UserForm
49 from rhodecode.model.forms import UserForm
50 from rhodecode.model.scm import RepoList
50 from rhodecode.model.scm import RepoList
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.auth_token import AuthTokenModel
53 from rhodecode.model.auth_token import AuthTokenModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class MyAccountController(BaseController):
59 class MyAccountController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
60 """REST Controller styled on the Atom Publishing Protocol"""
61 # To properly map this controller, ensure your config/routing.py
61 # To properly map this controller, ensure your config/routing.py
62 # file has a resource setup:
62 # file has a resource setup:
63 # map.resource('setting', 'settings', controller='admin/settings',
63 # map.resource('setting', 'settings', controller='admin/settings',
64 # path_prefix='/admin', name_prefix='admin_')
64 # path_prefix='/admin', name_prefix='admin_')
65
65
66 @LoginRequired()
66 @LoginRequired()
67 @NotAnonymous()
67 @NotAnonymous()
68 def __before__(self):
68 def __before__(self):
69 super(MyAccountController, self).__before__()
69 super(MyAccountController, self).__before__()
70
70
71 def __load_data(self):
71 def __load_data(self):
72 c.user = User.get(c.rhodecode_user.user_id)
72 c.user = User.get(c.rhodecode_user.user_id)
73 if c.user.username == User.DEFAULT_USER:
73 if c.user.username == User.DEFAULT_USER:
74 h.flash(_("You can't edit this user since it's"
74 h.flash(_("You can't edit this user since it's"
75 " crucial for entire application"), category='warning')
75 " crucial for entire application"), category='warning')
76 return redirect(url('users'))
76 return redirect(url('users'))
77
77
78 def _load_my_repos_data(self, watched=False):
78 def _load_my_repos_data(self, watched=False):
79 if watched:
79 if watched:
80 admin = False
80 admin = False
81 follows_repos = Session().query(UserFollowing)\
81 follows_repos = Session().query(UserFollowing)\
82 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
82 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
83 .options(joinedload(UserFollowing.follows_repository))\
83 .options(joinedload(UserFollowing.follows_repository))\
84 .all()
84 .all()
85 repo_list = [x.follows_repository for x in follows_repos]
85 repo_list = [x.follows_repository for x in follows_repos]
86 else:
86 else:
87 admin = True
87 admin = True
88 repo_list = Repository.get_all_repos(
88 repo_list = Repository.get_all_repos(
89 user_id=c.rhodecode_user.user_id)
89 user_id=c.rhodecode_user.user_id)
90 repo_list = RepoList(repo_list, perm_set=[
90 repo_list = RepoList(repo_list, perm_set=[
91 'repository.read', 'repository.write', 'repository.admin'])
91 'repository.read', 'repository.write', 'repository.admin'])
92
92
93 repos_data = RepoModel().get_repos_as_dict(
93 repos_data = RepoModel().get_repos_as_dict(
94 repo_list=repo_list, admin=admin)
94 repo_list=repo_list, admin=admin)
95 # json used to render the grid
95 # json used to render the grid
96 return json.dumps(repos_data)
96 return json.dumps(repos_data)
97
97
98 @auth.CSRFRequired()
98 @auth.CSRFRequired()
99 def my_account_update(self):
99 def my_account_update(self):
100 """
100 """
101 POST /_admin/my_account Updates info of my account
101 POST /_admin/my_account Updates info of my account
102 """
102 """
103 # url('my_account')
103 # url('my_account')
104 c.active = 'profile_edit'
104 c.active = 'profile_edit'
105 self.__load_data()
105 self.__load_data()
106 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
106 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
107 ip_addr=self.ip_addr)
107 ip_addr=self.ip_addr)
108 c.extern_type = c.user.extern_type
108 c.extern_type = c.user.extern_type
109 c.extern_name = c.user.extern_name
109 c.extern_name = c.user.extern_name
110
110
111 defaults = c.user.get_dict()
111 defaults = c.user.get_dict()
112 update = False
112 update = False
113 _form = UserForm(edit=True,
113 _form = UserForm(edit=True,
114 old_data={'user_id': c.rhodecode_user.user_id,
114 old_data={'user_id': c.rhodecode_user.user_id,
115 'email': c.rhodecode_user.email})()
115 'email': c.rhodecode_user.email})()
116 form_result = {}
116 form_result = {}
117 try:
117 try:
118 post_data = dict(request.POST)
118 post_data = dict(request.POST)
119 post_data['new_password'] = ''
119 post_data['new_password'] = ''
120 post_data['password_confirmation'] = ''
120 post_data['password_confirmation'] = ''
121 form_result = _form.to_python(post_data)
121 form_result = _form.to_python(post_data)
122 # skip updating those attrs for my account
122 # skip updating those attrs for my account
123 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
123 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
124 'new_password', 'password_confirmation']
124 'new_password', 'password_confirmation']
125 # TODO: plugin should define if username can be updated
125 # TODO: plugin should define if username can be updated
126 if c.extern_type != "rhodecode":
126 if c.extern_type != "rhodecode":
127 # forbid updating username for external accounts
127 # forbid updating username for external accounts
128 skip_attrs.append('username')
128 skip_attrs.append('username')
129
129
130 UserModel().update_user(
130 UserModel().update_user(
131 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
131 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
132 h.flash(_('Your account was updated successfully'),
132 h.flash(_('Your account was updated successfully'),
133 category='success')
133 category='success')
134 Session().commit()
134 Session().commit()
135 update = True
135 update = True
136
136
137 except formencode.Invalid as errors:
137 except formencode.Invalid as errors:
138 return htmlfill.render(
138 return htmlfill.render(
139 render('admin/my_account/my_account.html'),
139 render('admin/my_account/my_account.html'),
140 defaults=errors.value,
140 defaults=errors.value,
141 errors=errors.error_dict or {},
141 errors=errors.error_dict or {},
142 prefix_error=False,
142 prefix_error=False,
143 encoding="UTF-8",
143 encoding="UTF-8",
144 force_defaults=False)
144 force_defaults=False)
145 except Exception:
145 except Exception:
146 log.exception("Exception updating user")
146 log.exception("Exception updating user")
147 h.flash(_('Error occurred during update of user %s')
147 h.flash(_('Error occurred during update of user %s')
148 % form_result.get('username'), category='error')
148 % form_result.get('username'), category='error')
149
149
150 if update:
150 if update:
151 return redirect('my_account')
151 return redirect('my_account')
152
152
153 return htmlfill.render(
153 return htmlfill.render(
154 render('admin/my_account/my_account.html'),
154 render('admin/my_account/my_account.html'),
155 defaults=defaults,
155 defaults=defaults,
156 encoding="UTF-8",
156 encoding="UTF-8",
157 force_defaults=False
157 force_defaults=False
158 )
158 )
159
159
160 def my_account(self):
160 def my_account(self):
161 """
161 """
162 GET /_admin/my_account Displays info about my account
162 GET /_admin/my_account Displays info about my account
163 """
163 """
164 # url('my_account')
164 # url('my_account')
165 c.active = 'profile'
165 c.active = 'profile'
166 self.__load_data()
166 self.__load_data()
167
167
168 defaults = c.user.get_dict()
168 defaults = c.user.get_dict()
169 return htmlfill.render(
169 return htmlfill.render(
170 render('admin/my_account/my_account.html'),
170 render('admin/my_account/my_account.html'),
171 defaults=defaults, encoding="UTF-8", force_defaults=False)
171 defaults=defaults, encoding="UTF-8", force_defaults=False)
172
172
173 def my_account_edit(self):
173 def my_account_edit(self):
174 """
174 """
175 GET /_admin/my_account/edit Displays edit form of my account
175 GET /_admin/my_account/edit Displays edit form of my account
176 """
176 """
177 c.active = 'profile_edit'
177 c.active = 'profile_edit'
178 self.__load_data()
178 self.__load_data()
179 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
179 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
180 ip_addr=self.ip_addr)
180 ip_addr=self.ip_addr)
181 c.extern_type = c.user.extern_type
181 c.extern_type = c.user.extern_type
182 c.extern_name = c.user.extern_name
182 c.extern_name = c.user.extern_name
183
183
184 defaults = c.user.get_dict()
184 defaults = c.user.get_dict()
185 return htmlfill.render(
185 return htmlfill.render(
186 render('admin/my_account/my_account.html'),
186 render('admin/my_account/my_account.html'),
187 defaults=defaults,
187 defaults=defaults,
188 encoding="UTF-8",
188 encoding="UTF-8",
189 force_defaults=False
189 force_defaults=False
190 )
190 )
191
191
192 @auth.CSRFRequired(except_methods=['GET'])
192 @auth.CSRFRequired(except_methods=['GET'])
193 def my_account_password(self):
193 def my_account_password(self):
194 c.active = 'password'
194 c.active = 'password'
195 self.__load_data()
195 self.__load_data()
196
196
197 schema = user_schema.ChangePasswordSchema().bind(
197 schema = user_schema.ChangePasswordSchema().bind(
198 username=c.rhodecode_user.username)
198 username=c.rhodecode_user.username)
199
199
200 form = forms.Form(schema,
200 form = forms.Form(schema,
201 buttons=(forms.buttons.save, forms.buttons.reset))
201 buttons=(forms.buttons.save, forms.buttons.reset))
202
202
203 if request.method == 'POST':
203 if request.method == 'POST':
204 controls = request.POST.items()
204 controls = request.POST.items()
205 try:
205 try:
206 valid_data = form.validate(controls)
206 valid_data = form.validate(controls)
207 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
207 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
208 instance = c.rhodecode_user.get_instance()
208 instance = c.rhodecode_user.get_instance()
209 instance.update_userdata(force_password_change=False)
209 instance.update_userdata(force_password_change=False)
210 Session().commit()
210 Session().commit()
211 except forms.ValidationFailure as e:
211 except forms.ValidationFailure as e:
212 request.session.flash(
212 request.session.flash(
213 _('Error occurred during update of user password'),
213 _('Error occurred during update of user password'),
214 queue='error')
214 queue='error')
215 form = e
215 form = e
216 except Exception:
216 except Exception:
217 log.exception("Exception updating password")
217 log.exception("Exception updating password")
218 request.session.flash(
218 request.session.flash(
219 _('Error occurred during update of user password'),
219 _('Error occurred during update of user password'),
220 queue='error')
220 queue='error')
221 else:
221 else:
222 session.setdefault('rhodecode_user', {}).update(
222 session.setdefault('rhodecode_user', {}).update(
223 {'password': md5(instance.password)})
223 {'password': md5(instance.password)})
224 session.save()
224 session.save()
225 request.session.flash(
225 request.session.flash(
226 _("Successfully updated password"), queue='success')
226 _("Successfully updated password"), queue='success')
227 return redirect(url('my_account_password'))
227 return redirect(url('my_account_password'))
228
228
229 c.form = form
229 c.form = form
230 return render('admin/my_account/my_account.html')
230 return render('admin/my_account/my_account.html')
231
231
232 def my_account_repos(self):
232 def my_account_repos(self):
233 c.active = 'repos'
233 c.active = 'repos'
234 self.__load_data()
234 self.__load_data()
235
235
236 # json used to render the grid
236 # json used to render the grid
237 c.data = self._load_my_repos_data()
237 c.data = self._load_my_repos_data()
238 return render('admin/my_account/my_account.html')
238 return render('admin/my_account/my_account.html')
239
239
240 def my_account_watched(self):
240 def my_account_watched(self):
241 c.active = 'watched'
241 c.active = 'watched'
242 self.__load_data()
242 self.__load_data()
243
243
244 # json used to render the grid
244 # json used to render the grid
245 c.data = self._load_my_repos_data(watched=True)
245 c.data = self._load_my_repos_data(watched=True)
246 return render('admin/my_account/my_account.html')
246 return render('admin/my_account/my_account.html')
247
247
248 def my_account_perms(self):
248 def my_account_perms(self):
249 c.active = 'perms'
249 c.active = 'perms'
250 self.__load_data()
250 self.__load_data()
251 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
251 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
252 ip_addr=self.ip_addr)
252 ip_addr=self.ip_addr)
253
253
254 return render('admin/my_account/my_account.html')
254 return render('admin/my_account/my_account.html')
255
255
256 def my_account_emails(self):
256 def my_account_emails(self):
257 c.active = 'emails'
257 c.active = 'emails'
258 self.__load_data()
258 self.__load_data()
259
259
260 c.user_email_map = UserEmailMap.query()\
260 c.user_email_map = UserEmailMap.query()\
261 .filter(UserEmailMap.user == c.user).all()
261 .filter(UserEmailMap.user == c.user).all()
262 return render('admin/my_account/my_account.html')
262 return render('admin/my_account/my_account.html')
263
263
264 @auth.CSRFRequired()
264 @auth.CSRFRequired()
265 def my_account_emails_add(self):
265 def my_account_emails_add(self):
266 email = request.POST.get('new_email')
266 email = request.POST.get('new_email')
267
267
268 try:
268 try:
269 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
269 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
270 Session().commit()
270 Session().commit()
271 h.flash(_("Added new email address `%s` for user account") % email,
271 h.flash(_("Added new email address `%s` for user account") % email,
272 category='success')
272 category='success')
273 except formencode.Invalid as error:
273 except formencode.Invalid as error:
274 msg = error.error_dict['email']
274 msg = error.error_dict['email']
275 h.flash(msg, category='error')
275 h.flash(msg, category='error')
276 except Exception:
276 except Exception:
277 log.exception("Exception in my_account_emails")
277 log.exception("Exception in my_account_emails")
278 h.flash(_('An error occurred during email saving'),
278 h.flash(_('An error occurred during email saving'),
279 category='error')
279 category='error')
280 return redirect(url('my_account_emails'))
280 return redirect(url('my_account_emails'))
281
281
282 @auth.CSRFRequired()
282 @auth.CSRFRequired()
283 def my_account_emails_delete(self):
283 def my_account_emails_delete(self):
284 email_id = request.POST.get('del_email_id')
284 email_id = request.POST.get('del_email_id')
285 user_model = UserModel()
285 user_model = UserModel()
286 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
286 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
287 Session().commit()
287 Session().commit()
288 h.flash(_("Removed email address from user account"),
288 h.flash(_("Removed email address from user account"),
289 category='success')
289 category='success')
290 return redirect(url('my_account_emails'))
290 return redirect(url('my_account_emails'))
291
291
292 def my_account_pullrequests(self):
292 def my_account_pullrequests(self):
293 c.active = 'pullrequests'
293 c.active = 'pullrequests'
294 self.__load_data()
294 self.__load_data()
295 c.show_closed = request.GET.get('pr_show_closed')
295 c.show_closed = request.GET.get('pr_show_closed')
296
296
297 def _filter(pr):
297 def _filter(pr):
298 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
298 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
299 if not c.show_closed:
299 if not c.show_closed:
300 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
300 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
301 return s
301 return s
302
302
303 c.my_pull_requests = _filter(
303 c.my_pull_requests = _filter(
304 PullRequest.query().filter(
304 PullRequest.query().filter(
305 PullRequest.user_id == c.rhodecode_user.user_id).all())
305 PullRequest.user_id == c.rhodecode_user.user_id).all())
306 my_prs = [
306 my_prs = [
307 x.pull_request for x in PullRequestReviewers.query().filter(
307 x.pull_request for x in PullRequestReviewers.query().filter(
308 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
308 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
309 c.participate_in_pull_requests = _filter(my_prs)
309 c.participate_in_pull_requests = _filter(my_prs)
310 return render('admin/my_account/my_account.html')
310 return render('admin/my_account/my_account.html')
311
311
312 def my_account_auth_tokens(self):
312 def my_account_auth_tokens(self):
313 c.active = 'auth_tokens'
313 c.active = 'auth_tokens'
314 self.__load_data()
314 self.__load_data()
315 show_expired = True
315 show_expired = True
316 c.lifetime_values = [
316 c.lifetime_values = [
317 (str(-1), _('forever')),
317 (str(-1), _('forever')),
318 (str(5), _('5 minutes')),
318 (str(5), _('5 minutes')),
319 (str(60), _('1 hour')),
319 (str(60), _('1 hour')),
320 (str(60 * 24), _('1 day')),
320 (str(60 * 24), _('1 day')),
321 (str(60 * 24 * 30), _('1 month')),
321 (str(60 * 24 * 30), _('1 month')),
322 ]
322 ]
323 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
323 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
324 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
324 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
325 for x in AuthTokenModel.cls.ROLES]
325 for x in AuthTokenModel.cls.ROLES]
326 c.role_options = [(c.role_values, _("Role"))]
326 c.role_options = [(c.role_values, _("Role"))]
327 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
327 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
328 c.rhodecode_user.user_id, show_expired=show_expired)
328 c.rhodecode_user.user_id, show_expired=show_expired)
329 return render('admin/my_account/my_account.html')
329 return render('admin/my_account/my_account.html')
330
330
331 @auth.CSRFRequired()
331 @auth.CSRFRequired()
332 def my_account_auth_tokens_add(self):
332 def my_account_auth_tokens_add(self):
333 lifetime = safe_int(request.POST.get('lifetime'), -1)
333 lifetime = safe_int(request.POST.get('lifetime'), -1)
334 description = request.POST.get('description')
334 description = request.POST.get('description')
335 role = request.POST.get('role')
335 role = request.POST.get('role')
336 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
336 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
337 role)
337 role)
338 Session().commit()
338 Session().commit()
339 h.flash(_("Auth token successfully created"), category='success')
339 h.flash(_("Auth token successfully created"), category='success')
340 return redirect(url('my_account_auth_tokens'))
340 return redirect(url('my_account_auth_tokens'))
341
341
342 @auth.CSRFRequired()
342 @auth.CSRFRequired()
343 def my_account_auth_tokens_delete(self):
343 def my_account_auth_tokens_delete(self):
344 auth_token = request.POST.get('del_auth_token')
344 auth_token = request.POST.get('del_auth_token')
345 user_id = c.rhodecode_user.user_id
345 user_id = c.rhodecode_user.user_id
346 if request.POST.get('del_auth_token_builtin'):
346 if request.POST.get('del_auth_token_builtin'):
347 user = User.get(user_id)
347 user = User.get(user_id)
348 if user:
348 if user:
349 user.api_key = generate_auth_token(user.username)
349 user.api_key = generate_auth_token(user.username)
350 Session().add(user)
350 Session().add(user)
351 Session().commit()
351 Session().commit()
352 h.flash(_("Auth token successfully reset"), category='success')
352 h.flash(_("Auth token successfully reset"), category='success')
353 elif auth_token:
353 elif auth_token:
354 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
354 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
355 Session().commit()
355 Session().commit()
356 h.flash(_("Auth token successfully deleted"), category='success')
356 h.flash(_("Auth token successfully deleted"), category='success')
357
357
358 return redirect(url('my_account_auth_tokens'))
358 return redirect(url('my_account_auth_tokens'))
359
359
360 def my_notifications(self):
360 def my_notifications(self):
361 c.active = 'notifications'
361 c.active = 'notifications'
362 return render('admin/my_account/my_account.html')
362 return render('admin/my_account/my_account.html')
363
363
364 @auth.CSRFRequired()
364 @auth.CSRFRequired()
365 @jsonify
365 @jsonify
366 def my_notifications_toggle_visibility(self):
366 def my_notifications_toggle_visibility(self):
367 user = c.rhodecode_user.get_instance()
367 user = c.rhodecode_user.get_instance()
368 user_data = user.user_data
368 new_status = not user.user_data.get('notification_status', True)
369 status = user_data.get('notification_status', False)
369 user.update_userdata(notification_status=new_status)
370 user_data['notification_status'] = not status
371 user.user_data = user_data
372 Session().commit()
370 Session().commit()
373 return user_data['notification_status']
371 return user.user_data['notification_status']
@@ -1,219 +1,220 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-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 logging
21 import logging
22 import os
22 import os
23
23
24 import itsdangerous
24 import itsdangerous
25 import requests
25 import requests
26
26
27 from dogpile.core import ReadWriteMutex
27 from dogpile.core import ReadWriteMutex
28
28
29 import rhodecode.lib.helpers as h
29 import rhodecode.lib.helpers as h
30
30
31 from rhodecode.lib.auth import HasRepoPermissionAny
31 from rhodecode.lib.auth import HasRepoPermissionAny
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import User
33 from rhodecode.model.db import User
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37 LOCK = ReadWriteMutex()
37 LOCK = ReadWriteMutex()
38
38
39 STATE_PUBLIC_KEYS = ['id', 'username', 'first_name', 'last_name',
39 STATE_PUBLIC_KEYS = ['id', 'username', 'first_name', 'last_name',
40 'icon_link', 'display_name', 'display_link']
40 'icon_link', 'display_name', 'display_link']
41
41
42
42
43 class ChannelstreamException(Exception):
43 class ChannelstreamException(Exception):
44 pass
44 pass
45
45
46
46
47 class ChannelstreamConnectionException(Exception):
47 class ChannelstreamConnectionException(Exception):
48 pass
48 pass
49
49
50
50
51 class ChannelstreamPermissionException(Exception):
51 class ChannelstreamPermissionException(Exception):
52 pass
52 pass
53
53
54
54
55 def channelstream_request(config, payload, endpoint, raise_exc=True):
55 def channelstream_request(config, payload, endpoint, raise_exc=True):
56 signer = itsdangerous.TimestampSigner(config['secret'])
56 signer = itsdangerous.TimestampSigner(config['secret'])
57 sig_for_server = signer.sign(endpoint)
57 sig_for_server = signer.sign(endpoint)
58 secret_headers = {'x-channelstream-secret': sig_for_server,
58 secret_headers = {'x-channelstream-secret': sig_for_server,
59 'x-channelstream-endpoint': endpoint,
59 'x-channelstream-endpoint': endpoint,
60 'Content-Type': 'application/json'}
60 'Content-Type': 'application/json'}
61 req_url = 'http://{}{}'.format(config['server'], endpoint)
61 req_url = 'http://{}{}'.format(config['server'], endpoint)
62 response = None
62 response = None
63 try:
63 try:
64 response = requests.post(req_url, data=json.dumps(payload),
64 response = requests.post(req_url, data=json.dumps(payload),
65 headers=secret_headers).json()
65 headers=secret_headers).json()
66 except requests.ConnectionError:
66 except requests.ConnectionError:
67 log.exception('ConnectionError happened')
67 log.exception('ConnectionError happened')
68 if raise_exc:
68 if raise_exc:
69 raise ChannelstreamConnectionException()
69 raise ChannelstreamConnectionException()
70 except Exception:
70 except Exception:
71 log.exception('Exception related to channelstream happened')
71 log.exception('Exception related to channelstream happened')
72 if raise_exc:
72 if raise_exc:
73 raise ChannelstreamConnectionException()
73 raise ChannelstreamConnectionException()
74 return response
74 return response
75
75
76
76
77 def get_user_data(user_id):
77 def get_user_data(user_id):
78 user = User.get(user_id)
78 user = User.get(user_id)
79 return {
79 return {
80 'id': user.user_id,
80 'id': user.user_id,
81 'username': user.username,
81 'username': user.username,
82 'first_name': user.name,
82 'first_name': user.name,
83 'last_name': user.lastname,
83 'last_name': user.lastname,
84 'icon_link': h.gravatar_url(user.email, 14),
84 'icon_link': h.gravatar_url(user.email, 14),
85 'display_name': h.person(user, 'username_or_name_or_email'),
85 'display_name': h.person(user, 'username_or_name_or_email'),
86 'display_link': h.link_to_user(user),
86 'display_link': h.link_to_user(user),
87 'notifications': user.user_data.get('notification_status', True)
87 }
88 }
88
89
89
90
90 def broadcast_validator(channel_name):
91 def broadcast_validator(channel_name):
91 """ checks if user can access the broadcast channel """
92 """ checks if user can access the broadcast channel """
92 if channel_name == 'broadcast':
93 if channel_name == 'broadcast':
93 return True
94 return True
94
95
95
96
96 def repo_validator(channel_name):
97 def repo_validator(channel_name):
97 """ checks if user can access the broadcast channel """
98 """ checks if user can access the broadcast channel """
98 channel_prefix = '/repo$'
99 channel_prefix = '/repo$'
99 if channel_name.startswith(channel_prefix):
100 if channel_name.startswith(channel_prefix):
100 elements = channel_name[len(channel_prefix):].split('$')
101 elements = channel_name[len(channel_prefix):].split('$')
101 repo_name = elements[0]
102 repo_name = elements[0]
102 can_access = HasRepoPermissionAny(
103 can_access = HasRepoPermissionAny(
103 'repository.read',
104 'repository.read',
104 'repository.write',
105 'repository.write',
105 'repository.admin')(repo_name)
106 'repository.admin')(repo_name)
106 log.debug('permission check for {} channel '
107 log.debug('permission check for {} channel '
107 'resulted in {}'.format(repo_name, can_access))
108 'resulted in {}'.format(repo_name, can_access))
108 if can_access:
109 if can_access:
109 return True
110 return True
110 return False
111 return False
111
112
112
113
113 def check_channel_permissions(channels, plugin_validators, should_raise=True):
114 def check_channel_permissions(channels, plugin_validators, should_raise=True):
114 valid_channels = []
115 valid_channels = []
115
116
116 validators = [broadcast_validator, repo_validator]
117 validators = [broadcast_validator, repo_validator]
117 if plugin_validators:
118 if plugin_validators:
118 validators.extend(plugin_validators)
119 validators.extend(plugin_validators)
119 for channel_name in channels:
120 for channel_name in channels:
120 is_valid = False
121 is_valid = False
121 for validator in validators:
122 for validator in validators:
122 if validator(channel_name):
123 if validator(channel_name):
123 is_valid = True
124 is_valid = True
124 break
125 break
125 if is_valid:
126 if is_valid:
126 valid_channels.append(channel_name)
127 valid_channels.append(channel_name)
127 else:
128 else:
128 if should_raise:
129 if should_raise:
129 raise ChannelstreamPermissionException()
130 raise ChannelstreamPermissionException()
130 return valid_channels
131 return valid_channels
131
132
132
133
133 def get_channels_info(self, channels):
134 def get_channels_info(self, channels):
134 payload = {'channels': channels}
135 payload = {'channels': channels}
135 # gather persistence info
136 # gather persistence info
136 return channelstream_request(self._config(), payload, '/info')
137 return channelstream_request(self._config(), payload, '/info')
137
138
138
139
139 def parse_channels_info(info_result, include_channel_info=None):
140 def parse_channels_info(info_result, include_channel_info=None):
140 """
141 """
141 Returns data that contains only secure information that can be
142 Returns data that contains only secure information that can be
142 presented to clients
143 presented to clients
143 """
144 """
144 include_channel_info = include_channel_info or []
145 include_channel_info = include_channel_info or []
145
146
146 user_state_dict = {}
147 user_state_dict = {}
147 for userinfo in info_result['users']:
148 for userinfo in info_result['users']:
148 user_state_dict[userinfo['user']] = {
149 user_state_dict[userinfo['user']] = {
149 k: v for k, v in userinfo['state'].items()
150 k: v for k, v in userinfo['state'].items()
150 if k in STATE_PUBLIC_KEYS
151 if k in STATE_PUBLIC_KEYS
151 }
152 }
152
153
153 channels_info = {}
154 channels_info = {}
154
155
155 for c_name, c_info in info_result['channels'].items():
156 for c_name, c_info in info_result['channels'].items():
156 if c_name not in include_channel_info:
157 if c_name not in include_channel_info:
157 continue
158 continue
158 connected_list = []
159 connected_list = []
159 for userinfo in c_info['users']:
160 for userinfo in c_info['users']:
160 connected_list.append({
161 connected_list.append({
161 'user': userinfo['user'],
162 'user': userinfo['user'],
162 'state': user_state_dict[userinfo['user']]
163 'state': user_state_dict[userinfo['user']]
163 })
164 })
164 channels_info[c_name] = {'users': connected_list,
165 channels_info[c_name] = {'users': connected_list,
165 'history': c_info['history']}
166 'history': c_info['history']}
166
167
167 return channels_info
168 return channels_info
168
169
169
170
170 def log_filepath(history_location, channel_name):
171 def log_filepath(history_location, channel_name):
171 filename = '{}.log'.format(channel_name.encode('hex'))
172 filename = '{}.log'.format(channel_name.encode('hex'))
172 filepath = os.path.join(history_location, filename)
173 filepath = os.path.join(history_location, filename)
173 return filepath
174 return filepath
174
175
175
176
176 def read_history(history_location, channel_name):
177 def read_history(history_location, channel_name):
177 filepath = log_filepath(history_location, channel_name)
178 filepath = log_filepath(history_location, channel_name)
178 if not os.path.exists(filepath):
179 if not os.path.exists(filepath):
179 return []
180 return []
180 history_lines_limit = -100
181 history_lines_limit = -100
181 history = []
182 history = []
182 with open(filepath, 'rb') as f:
183 with open(filepath, 'rb') as f:
183 for line in f.readlines()[history_lines_limit:]:
184 for line in f.readlines()[history_lines_limit:]:
184 try:
185 try:
185 history.append(json.loads(line))
186 history.append(json.loads(line))
186 except Exception:
187 except Exception:
187 log.exception('Failed to load history')
188 log.exception('Failed to load history')
188 return history
189 return history
189
190
190
191
191 def update_history_from_logs(config, channels, payload):
192 def update_history_from_logs(config, channels, payload):
192 history_location = config.get('history.location')
193 history_location = config.get('history.location')
193 for channel in channels:
194 for channel in channels:
194 history = read_history(history_location, channel)
195 history = read_history(history_location, channel)
195 payload['channels_info'][channel]['history'] = history
196 payload['channels_info'][channel]['history'] = history
196
197
197
198
198 def write_history(config, message):
199 def write_history(config, message):
199 """ writes a messge to a base64encoded filename """
200 """ writes a messge to a base64encoded filename """
200 history_location = config.get('history.location')
201 history_location = config.get('history.location')
201 if not os.path.exists(history_location):
202 if not os.path.exists(history_location):
202 return
203 return
203 try:
204 try:
204 LOCK.acquire_write_lock()
205 LOCK.acquire_write_lock()
205 filepath = log_filepath(history_location, message['channel'])
206 filepath = log_filepath(history_location, message['channel'])
206 with open(filepath, 'ab') as f:
207 with open(filepath, 'ab') as f:
207 json.dump(message, f)
208 json.dump(message, f)
208 f.write('\n')
209 f.write('\n')
209 finally:
210 finally:
210 LOCK.release_write_lock()
211 LOCK.release_write_lock()
211
212
212
213
213 def get_connection_validators(registry):
214 def get_connection_validators(registry):
214 validators = []
215 validators = []
215 for k, config in registry.rhodecode_plugins.iteritems():
216 for k, config in registry.rhodecode_plugins.iteritems():
216 validator = config.get('channelstream', {}).get('connect_validator')
217 validator = config.get('channelstream', {}).get('connect_validator')
217 if validator:
218 if validator:
218 validators.append(validator)
219 validators.append(validator)
219 return validators
220 return validators
@@ -1,838 +1,839 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 import events
35 from rhodecode import events
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
150
151 # cleanups, user edit password change form
151 # cleanups, user edit password change form
152 kwargs.pop('password_confirmation', None)
152 kwargs.pop('password_confirmation', None)
153 kwargs.pop('password_change', None)
153 kwargs.pop('password_change', None)
154
154
155 # create repo group on user creation
155 # create repo group on user creation
156 kwargs.pop('create_repo_group', None)
156 kwargs.pop('create_repo_group', None)
157
157
158 # legacy forms send name, which is the firstname
158 # legacy forms send name, which is the firstname
159 firstname = kwargs.pop('name', None)
159 firstname = kwargs.pop('name', None)
160 if firstname:
160 if firstname:
161 kwargs['firstname'] = firstname
161 kwargs['firstname'] = firstname
162
162
163 for k, v in kwargs.items():
163 for k, v in kwargs.items():
164 # skip if we don't want to update this
164 # skip if we don't want to update this
165 if skip_attrs and k in skip_attrs:
165 if skip_attrs and k in skip_attrs:
166 continue
166 continue
167
167
168 user_attrs[k] = v
168 user_attrs[k] = v
169
169
170 try:
170 try:
171 return self.create_or_update(**user_attrs)
171 return self.create_or_update(**user_attrs)
172 except Exception:
172 except Exception:
173 log.error(traceback.format_exc())
173 log.error(traceback.format_exc())
174 raise
174 raise
175
175
176 def create_or_update(
176 def create_or_update(
177 self, username, password, email, firstname='', lastname='',
177 self, username, password, email, firstname='', lastname='',
178 active=True, admin=False, extern_type=None, extern_name=None,
178 active=True, admin=False, extern_type=None, extern_name=None,
179 cur_user=None, plugin=None, force_password_change=False,
179 cur_user=None, plugin=None, force_password_change=False,
180 allow_to_create_user=True, create_repo_group=False,
180 allow_to_create_user=True, create_repo_group=False,
181 updating_user_id=None, language=None, strict_creation_check=True):
181 updating_user_id=None, language=None, strict_creation_check=True):
182 """
182 """
183 Creates a new instance if not found, or updates current one
183 Creates a new instance if not found, or updates current one
184
184
185 :param username:
185 :param username:
186 :param password:
186 :param password:
187 :param email:
187 :param email:
188 :param firstname:
188 :param firstname:
189 :param lastname:
189 :param lastname:
190 :param active:
190 :param active:
191 :param admin:
191 :param admin:
192 :param extern_type:
192 :param extern_type:
193 :param extern_name:
193 :param extern_name:
194 :param cur_user:
194 :param cur_user:
195 :param plugin: optional plugin this method was called from
195 :param plugin: optional plugin this method was called from
196 :param force_password_change: toggles new or existing user flag
196 :param force_password_change: toggles new or existing user flag
197 for password change
197 for password change
198 :param allow_to_create_user: Defines if the method can actually create
198 :param allow_to_create_user: Defines if the method can actually create
199 new users
199 new users
200 :param create_repo_group: Defines if the method should also
200 :param create_repo_group: Defines if the method should also
201 create an repo group with user name, and owner
201 create an repo group with user name, and owner
202 :param updating_user_id: if we set it up this is the user we want to
202 :param updating_user_id: if we set it up this is the user we want to
203 update this allows to editing username.
203 update this allows to editing username.
204 :param language: language of user from interface.
204 :param language: language of user from interface.
205
205
206 :returns: new User object with injected `is_new_user` attribute.
206 :returns: new User object with injected `is_new_user` attribute.
207 """
207 """
208 if not cur_user:
208 if not cur_user:
209 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
209 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
210
210
211 from rhodecode.lib.auth import (
211 from rhodecode.lib.auth import (
212 get_crypt_password, check_password, generate_auth_token)
212 get_crypt_password, check_password, generate_auth_token)
213 from rhodecode.lib.hooks_base import (
213 from rhodecode.lib.hooks_base import (
214 log_create_user, check_allowed_create_user)
214 log_create_user, check_allowed_create_user)
215
215
216 def _password_change(new_user, password):
216 def _password_change(new_user, password):
217 # empty password
217 # empty password
218 if not new_user.password:
218 if not new_user.password:
219 return False
219 return False
220
220
221 # password check is only needed for RhodeCode internal auth calls
221 # password check is only needed for RhodeCode internal auth calls
222 # in case it's a plugin we don't care
222 # in case it's a plugin we don't care
223 if not plugin:
223 if not plugin:
224
224
225 # first check if we gave crypted password back, and if it matches
225 # first check if we gave crypted password back, and if it matches
226 # it's not password change
226 # it's not password change
227 if new_user.password == password:
227 if new_user.password == password:
228 return False
228 return False
229
229
230 password_match = check_password(password, new_user.password)
230 password_match = check_password(password, new_user.password)
231 if not password_match:
231 if not password_match:
232 return True
232 return True
233
233
234 return False
234 return False
235
235
236 user_data = {
236 user_data = {
237 'username': username,
237 'username': username,
238 'password': password,
238 'password': password,
239 'email': email,
239 'email': email,
240 'firstname': firstname,
240 'firstname': firstname,
241 'lastname': lastname,
241 'lastname': lastname,
242 'active': active,
242 'active': active,
243 'admin': admin
243 'admin': admin
244 }
244 }
245
245
246 if updating_user_id:
246 if updating_user_id:
247 log.debug('Checking for existing account in RhodeCode '
247 log.debug('Checking for existing account in RhodeCode '
248 'database with user_id `%s` ' % (updating_user_id,))
248 'database with user_id `%s` ' % (updating_user_id,))
249 user = User.get(updating_user_id)
249 user = User.get(updating_user_id)
250 else:
250 else:
251 log.debug('Checking for existing account in RhodeCode '
251 log.debug('Checking for existing account in RhodeCode '
252 'database with username `%s` ' % (username,))
252 'database with username `%s` ' % (username,))
253 user = User.get_by_username(username, case_insensitive=True)
253 user = User.get_by_username(username, case_insensitive=True)
254
254
255 if user is None:
255 if user is None:
256 # we check internal flag if this method is actually allowed to
256 # we check internal flag if this method is actually allowed to
257 # create new user
257 # create new user
258 if not allow_to_create_user:
258 if not allow_to_create_user:
259 msg = ('Method wants to create new user, but it is not '
259 msg = ('Method wants to create new user, but it is not '
260 'allowed to do so')
260 'allowed to do so')
261 log.warning(msg)
261 log.warning(msg)
262 raise NotAllowedToCreateUserError(msg)
262 raise NotAllowedToCreateUserError(msg)
263
263
264 log.debug('Creating new user %s', username)
264 log.debug('Creating new user %s', username)
265
265
266 # only if we create user that is active
266 # only if we create user that is active
267 new_active_user = active
267 new_active_user = active
268 if new_active_user and strict_creation_check:
268 if new_active_user and strict_creation_check:
269 # raises UserCreationError if it's not allowed for any reason to
269 # raises UserCreationError if it's not allowed for any reason to
270 # create new active user, this also executes pre-create hooks
270 # create new active user, this also executes pre-create hooks
271 check_allowed_create_user(user_data, cur_user, strict_check=True)
271 check_allowed_create_user(user_data, cur_user, strict_check=True)
272 events.trigger(events.UserPreCreate(user_data))
272 events.trigger(events.UserPreCreate(user_data))
273 new_user = User()
273 new_user = User()
274 edit = False
274 edit = False
275 else:
275 else:
276 log.debug('updating user %s', username)
276 log.debug('updating user %s', username)
277 events.trigger(events.UserPreUpdate(user, user_data))
277 events.trigger(events.UserPreUpdate(user, user_data))
278 new_user = user
278 new_user = user
279 edit = True
279 edit = True
280
280
281 # we're not allowed to edit default user
281 # we're not allowed to edit default user
282 if user.username == User.DEFAULT_USER:
282 if user.username == User.DEFAULT_USER:
283 raise DefaultUserException(
283 raise DefaultUserException(
284 _("You can't edit this user (`%(username)s`) since it's "
284 _("You can't edit this user (`%(username)s`) since it's "
285 "crucial for entire application") % {'username': user.username})
285 "crucial for entire application") % {'username': user.username})
286
286
287 # inject special attribute that will tell us if User is new or old
287 # inject special attribute that will tell us if User is new or old
288 new_user.is_new_user = not edit
288 new_user.is_new_user = not edit
289 # for users that didn's specify auth type, we use RhodeCode built in
289 # for users that didn's specify auth type, we use RhodeCode built in
290 from rhodecode.authentication.plugins import auth_rhodecode
290 from rhodecode.authentication.plugins import auth_rhodecode
291 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
291 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
292 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
292 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
293
293
294 try:
294 try:
295 new_user.username = username
295 new_user.username = username
296 new_user.admin = admin
296 new_user.admin = admin
297 new_user.email = email
297 new_user.email = email
298 new_user.active = active
298 new_user.active = active
299 new_user.extern_name = safe_unicode(extern_name)
299 new_user.extern_name = safe_unicode(extern_name)
300 new_user.extern_type = safe_unicode(extern_type)
300 new_user.extern_type = safe_unicode(extern_type)
301 new_user.name = firstname
301 new_user.name = firstname
302 new_user.lastname = lastname
302 new_user.lastname = lastname
303
303
304 if not edit:
304 if not edit:
305 new_user.api_key = generate_auth_token(username)
305 new_user.api_key = generate_auth_token(username)
306
306
307 # set password only if creating an user or password is changed
307 # set password only if creating an user or password is changed
308 if not edit or _password_change(new_user, password):
308 if not edit or _password_change(new_user, password):
309 reason = 'new password' if edit else 'new user'
309 reason = 'new password' if edit else 'new user'
310 log.debug('Updating password reason=>%s', reason)
310 log.debug('Updating password reason=>%s', reason)
311 new_user.password = get_crypt_password(password) if password else None
311 new_user.password = get_crypt_password(password) if password else None
312
312
313 if force_password_change:
313 if force_password_change:
314 new_user.update_userdata(force_password_change=True)
314 new_user.update_userdata(force_password_change=True)
315 if language:
315 if language:
316 new_user.update_userdata(language=language)
316 new_user.update_userdata(language=language)
317 new_user.update_userdata(notification_status=True)
317
318
318 self.sa.add(new_user)
319 self.sa.add(new_user)
319
320
320 if not edit and create_repo_group:
321 if not edit and create_repo_group:
321 # 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
322 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
323 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
323 RepoGroupModel().create(group_name=username,
324 RepoGroupModel().create(group_name=username,
324 group_description=desc,
325 group_description=desc,
325 owner=username, commit_early=False)
326 owner=username, commit_early=False)
326 if not edit:
327 if not edit:
327 # add the RSS token
328 # add the RSS token
328 AuthTokenModel().create(username,
329 AuthTokenModel().create(username,
329 description='Generated feed token',
330 description='Generated feed token',
330 role=AuthTokenModel.cls.ROLE_FEED)
331 role=AuthTokenModel.cls.ROLE_FEED)
331 log_create_user(created_by=cur_user, **new_user.get_dict())
332 log_create_user(created_by=cur_user, **new_user.get_dict())
332 return new_user
333 return new_user
333 except (DatabaseError,):
334 except (DatabaseError,):
334 log.error(traceback.format_exc())
335 log.error(traceback.format_exc())
335 raise
336 raise
336
337
337 def create_registration(self, form_data):
338 def create_registration(self, form_data):
338 from rhodecode.model.notification import NotificationModel
339 from rhodecode.model.notification import NotificationModel
339 from rhodecode.model.notification import EmailNotificationModel
340 from rhodecode.model.notification import EmailNotificationModel
340
341
341 try:
342 try:
342 form_data['admin'] = False
343 form_data['admin'] = False
343 form_data['extern_name'] = 'rhodecode'
344 form_data['extern_name'] = 'rhodecode'
344 form_data['extern_type'] = 'rhodecode'
345 form_data['extern_type'] = 'rhodecode'
345 new_user = self.create(form_data)
346 new_user = self.create(form_data)
346
347
347 self.sa.add(new_user)
348 self.sa.add(new_user)
348 self.sa.flush()
349 self.sa.flush()
349
350
350 user_data = new_user.get_dict()
351 user_data = new_user.get_dict()
351 kwargs = {
352 kwargs = {
352 # use SQLALCHEMY safe dump of user data
353 # use SQLALCHEMY safe dump of user data
353 'user': AttributeDict(user_data),
354 'user': AttributeDict(user_data),
354 'date': datetime.datetime.now()
355 'date': datetime.datetime.now()
355 }
356 }
356 notification_type = EmailNotificationModel.TYPE_REGISTRATION
357 notification_type = EmailNotificationModel.TYPE_REGISTRATION
357 # pre-generate the subject for notification itself
358 # pre-generate the subject for notification itself
358 (subject,
359 (subject,
359 _h, _e, # we don't care about those
360 _h, _e, # we don't care about those
360 body_plaintext) = EmailNotificationModel().render_email(
361 body_plaintext) = EmailNotificationModel().render_email(
361 notification_type, **kwargs)
362 notification_type, **kwargs)
362
363
363 # create notification objects, and emails
364 # create notification objects, and emails
364 NotificationModel().create(
365 NotificationModel().create(
365 created_by=new_user,
366 created_by=new_user,
366 notification_subject=subject,
367 notification_subject=subject,
367 notification_body=body_plaintext,
368 notification_body=body_plaintext,
368 notification_type=notification_type,
369 notification_type=notification_type,
369 recipients=None, # all admins
370 recipients=None, # all admins
370 email_kwargs=kwargs,
371 email_kwargs=kwargs,
371 )
372 )
372
373
373 return new_user
374 return new_user
374 except Exception:
375 except Exception:
375 log.error(traceback.format_exc())
376 log.error(traceback.format_exc())
376 raise
377 raise
377
378
378 def _handle_user_repos(self, username, repositories, handle_mode=None):
379 def _handle_user_repos(self, username, repositories, handle_mode=None):
379 _superadmin = self.cls.get_first_super_admin()
380 _superadmin = self.cls.get_first_super_admin()
380 left_overs = True
381 left_overs = True
381
382
382 from rhodecode.model.repo import RepoModel
383 from rhodecode.model.repo import RepoModel
383
384
384 if handle_mode == 'detach':
385 if handle_mode == 'detach':
385 for obj in repositories:
386 for obj in repositories:
386 obj.user = _superadmin
387 obj.user = _superadmin
387 # set description we know why we super admin now owns
388 # set description we know why we super admin now owns
388 # additional repositories that were orphaned !
389 # additional repositories that were orphaned !
389 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
390 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
390 self.sa.add(obj)
391 self.sa.add(obj)
391 left_overs = False
392 left_overs = False
392 elif handle_mode == 'delete':
393 elif handle_mode == 'delete':
393 for obj in repositories:
394 for obj in repositories:
394 RepoModel().delete(obj, forks='detach')
395 RepoModel().delete(obj, forks='detach')
395 left_overs = False
396 left_overs = False
396
397
397 # if nothing is done we have left overs left
398 # if nothing is done we have left overs left
398 return left_overs
399 return left_overs
399
400
400 def _handle_user_repo_groups(self, username, repository_groups,
401 def _handle_user_repo_groups(self, username, repository_groups,
401 handle_mode=None):
402 handle_mode=None):
402 _superadmin = self.cls.get_first_super_admin()
403 _superadmin = self.cls.get_first_super_admin()
403 left_overs = True
404 left_overs = True
404
405
405 from rhodecode.model.repo_group import RepoGroupModel
406 from rhodecode.model.repo_group import RepoGroupModel
406
407
407 if handle_mode == 'detach':
408 if handle_mode == 'detach':
408 for r in repository_groups:
409 for r in repository_groups:
409 r.user = _superadmin
410 r.user = _superadmin
410 # set description we know why we super admin now owns
411 # set description we know why we super admin now owns
411 # additional repositories that were orphaned !
412 # additional repositories that were orphaned !
412 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,)
413 self.sa.add(r)
414 self.sa.add(r)
414 left_overs = False
415 left_overs = False
415 elif handle_mode == 'delete':
416 elif handle_mode == 'delete':
416 for r in repository_groups:
417 for r in repository_groups:
417 RepoGroupModel().delete(r)
418 RepoGroupModel().delete(r)
418 left_overs = False
419 left_overs = False
419
420
420 # if nothing is done we have left overs left
421 # if nothing is done we have left overs left
421 return left_overs
422 return left_overs
422
423
423 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):
424 _superadmin = self.cls.get_first_super_admin()
425 _superadmin = self.cls.get_first_super_admin()
425 left_overs = True
426 left_overs = True
426
427
427 from rhodecode.model.user_group import UserGroupModel
428 from rhodecode.model.user_group import UserGroupModel
428
429
429 if handle_mode == 'detach':
430 if handle_mode == 'detach':
430 for r in user_groups:
431 for r in user_groups:
431 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:
432 if user_user_group_to_perm.user.username == username:
433 if user_user_group_to_perm.user.username == username:
433 user_user_group_to_perm.user = _superadmin
434 user_user_group_to_perm.user = _superadmin
434 r.user = _superadmin
435 r.user = _superadmin
435 # set description we know why we super admin now owns
436 # set description we know why we super admin now owns
436 # additional repositories that were orphaned !
437 # additional repositories that were orphaned !
437 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,)
438 self.sa.add(r)
439 self.sa.add(r)
439 left_overs = False
440 left_overs = False
440 elif handle_mode == 'delete':
441 elif handle_mode == 'delete':
441 for r in user_groups:
442 for r in user_groups:
442 UserGroupModel().delete(r)
443 UserGroupModel().delete(r)
443 left_overs = False
444 left_overs = False
444
445
445 # if nothing is done we have left overs left
446 # if nothing is done we have left overs left
446 return left_overs
447 return left_overs
447
448
448 def delete(self, user, cur_user=None, handle_repos=None,
449 def delete(self, user, cur_user=None, handle_repos=None,
449 handle_repo_groups=None, handle_user_groups=None):
450 handle_repo_groups=None, handle_user_groups=None):
450 if not cur_user:
451 if not cur_user:
451 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
452 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
452 user = self._get_user(user)
453 user = self._get_user(user)
453
454
454 try:
455 try:
455 if user.username == User.DEFAULT_USER:
456 if user.username == User.DEFAULT_USER:
456 raise DefaultUserException(
457 raise DefaultUserException(
457 _(u"You can't remove this user since it's"
458 _(u"You can't remove this user since it's"
458 u" crucial for entire application"))
459 u" crucial for entire application"))
459
460
460 left_overs = self._handle_user_repos(
461 left_overs = self._handle_user_repos(
461 user.username, user.repositories, handle_repos)
462 user.username, user.repositories, handle_repos)
462 if left_overs and user.repositories:
463 if left_overs and user.repositories:
463 repos = [x.repo_name for x in user.repositories]
464 repos = [x.repo_name for x in user.repositories]
464 raise UserOwnsReposException(
465 raise UserOwnsReposException(
465 _(u'user "%s" still owns %s repositories and cannot be '
466 _(u'user "%s" still owns %s repositories and cannot be '
466 u'removed. Switch owners or remove those repositories:%s')
467 u'removed. Switch owners or remove those repositories:%s')
467 % (user.username, len(repos), ', '.join(repos)))
468 % (user.username, len(repos), ', '.join(repos)))
468
469
469 left_overs = self._handle_user_repo_groups(
470 left_overs = self._handle_user_repo_groups(
470 user.username, user.repository_groups, handle_repo_groups)
471 user.username, user.repository_groups, handle_repo_groups)
471 if left_overs and user.repository_groups:
472 if left_overs and user.repository_groups:
472 repo_groups = [x.group_name for x in user.repository_groups]
473 repo_groups = [x.group_name for x in user.repository_groups]
473 raise UserOwnsRepoGroupsException(
474 raise UserOwnsRepoGroupsException(
474 _(u'user "%s" still owns %s repository groups and cannot be '
475 _(u'user "%s" still owns %s repository groups and cannot be '
475 u'removed. Switch owners or remove those repository groups:%s')
476 u'removed. Switch owners or remove those repository groups:%s')
476 % (user.username, len(repo_groups), ', '.join(repo_groups)))
477 % (user.username, len(repo_groups), ', '.join(repo_groups)))
477
478
478 left_overs = self._handle_user_user_groups(
479 left_overs = self._handle_user_user_groups(
479 user.username, user.user_groups, handle_user_groups)
480 user.username, user.user_groups, handle_user_groups)
480 if left_overs and user.user_groups:
481 if left_overs and user.user_groups:
481 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]
482 raise UserOwnsUserGroupsException(
483 raise UserOwnsUserGroupsException(
483 _(u'user "%s" still owns %s user groups and cannot be '
484 _(u'user "%s" still owns %s user groups and cannot be '
484 u'removed. Switch owners or remove those user groups:%s')
485 u'removed. Switch owners or remove those user groups:%s')
485 % (user.username, len(user_groups), ', '.join(user_groups)))
486 % (user.username, len(user_groups), ', '.join(user_groups)))
486
487
487 # we might change the user data with detach/delete, make sure
488 # we might change the user data with detach/delete, make sure
488 # the object is marked as expired before actually deleting !
489 # the object is marked as expired before actually deleting !
489 self.sa.expire(user)
490 self.sa.expire(user)
490 self.sa.delete(user)
491 self.sa.delete(user)
491 from rhodecode.lib.hooks_base import log_delete_user
492 from rhodecode.lib.hooks_base import log_delete_user
492 log_delete_user(deleted_by=cur_user, **user.get_dict())
493 log_delete_user(deleted_by=cur_user, **user.get_dict())
493 except Exception:
494 except Exception:
494 log.error(traceback.format_exc())
495 log.error(traceback.format_exc())
495 raise
496 raise
496
497
497 def reset_password_link(self, data, pwd_reset_url):
498 def reset_password_link(self, data, pwd_reset_url):
498 from rhodecode.lib.celerylib import tasks, run_task
499 from rhodecode.lib.celerylib import tasks, run_task
499 from rhodecode.model.notification import EmailNotificationModel
500 from rhodecode.model.notification import EmailNotificationModel
500 user_email = data['email']
501 user_email = data['email']
501 try:
502 try:
502 user = User.get_by_email(user_email)
503 user = User.get_by_email(user_email)
503 if user:
504 if user:
504 log.debug('password reset user found %s', user)
505 log.debug('password reset user found %s', user)
505
506
506 email_kwargs = {
507 email_kwargs = {
507 'password_reset_url': pwd_reset_url,
508 'password_reset_url': pwd_reset_url,
508 'user': user,
509 'user': user,
509 'email': user_email,
510 'email': user_email,
510 'date': datetime.datetime.now()
511 'date': datetime.datetime.now()
511 }
512 }
512
513
513 (subject, headers, email_body,
514 (subject, headers, email_body,
514 email_body_plaintext) = EmailNotificationModel().render_email(
515 email_body_plaintext) = EmailNotificationModel().render_email(
515 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
516 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
516
517
517 recipients = [user_email]
518 recipients = [user_email]
518
519
519 action_logger_generic(
520 action_logger_generic(
520 'sending password reset email to user: {}'.format(
521 'sending password reset email to user: {}'.format(
521 user), namespace='security.password_reset')
522 user), namespace='security.password_reset')
522
523
523 run_task(tasks.send_email, recipients, subject,
524 run_task(tasks.send_email, recipients, subject,
524 email_body_plaintext, email_body)
525 email_body_plaintext, email_body)
525
526
526 else:
527 else:
527 log.debug("password reset email %s not found", user_email)
528 log.debug("password reset email %s not found", user_email)
528 except Exception:
529 except Exception:
529 log.error(traceback.format_exc())
530 log.error(traceback.format_exc())
530 return False
531 return False
531
532
532 return True
533 return True
533
534
534 def reset_password(self, data, pwd_reset_url):
535 def reset_password(self, data, pwd_reset_url):
535 from rhodecode.lib.celerylib import tasks, run_task
536 from rhodecode.lib.celerylib import tasks, run_task
536 from rhodecode.model.notification import EmailNotificationModel
537 from rhodecode.model.notification import EmailNotificationModel
537 from rhodecode.lib import auth
538 from rhodecode.lib import auth
538 user_email = data['email']
539 user_email = data['email']
539 pre_db = True
540 pre_db = True
540 try:
541 try:
541 user = User.get_by_email(user_email)
542 user = User.get_by_email(user_email)
542 new_passwd = auth.PasswordGenerator().gen_password(
543 new_passwd = auth.PasswordGenerator().gen_password(
543 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
544 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
544 if user:
545 if user:
545 user.password = auth.get_crypt_password(new_passwd)
546 user.password = auth.get_crypt_password(new_passwd)
546 # also force this user to reset his password !
547 # also force this user to reset his password !
547 user.update_userdata(force_password_change=True)
548 user.update_userdata(force_password_change=True)
548
549
549 Session().add(user)
550 Session().add(user)
550 Session().commit()
551 Session().commit()
551 log.info('change password for %s', user_email)
552 log.info('change password for %s', user_email)
552 if new_passwd is None:
553 if new_passwd is None:
553 raise Exception('unable to generate new password')
554 raise Exception('unable to generate new password')
554
555
555 pre_db = False
556 pre_db = False
556
557
557 email_kwargs = {
558 email_kwargs = {
558 'new_password': new_passwd,
559 'new_password': new_passwd,
559 'password_reset_url': pwd_reset_url,
560 'password_reset_url': pwd_reset_url,
560 'user': user,
561 'user': user,
561 'email': user_email,
562 'email': user_email,
562 'date': datetime.datetime.now()
563 'date': datetime.datetime.now()
563 }
564 }
564
565
565 (subject, headers, email_body,
566 (subject, headers, email_body,
566 email_body_plaintext) = EmailNotificationModel().render_email(
567 email_body_plaintext) = EmailNotificationModel().render_email(
567 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
568 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
568
569
569 recipients = [user_email]
570 recipients = [user_email]
570
571
571 action_logger_generic(
572 action_logger_generic(
572 'sent new password to user: {} with email: {}'.format(
573 'sent new password to user: {} with email: {}'.format(
573 user, user_email), namespace='security.password_reset')
574 user, user_email), namespace='security.password_reset')
574
575
575 run_task(tasks.send_email, recipients, subject,
576 run_task(tasks.send_email, recipients, subject,
576 email_body_plaintext, email_body)
577 email_body_plaintext, email_body)
577
578
578 except Exception:
579 except Exception:
579 log.error('Failed to update user password')
580 log.error('Failed to update user password')
580 log.error(traceback.format_exc())
581 log.error(traceback.format_exc())
581 if pre_db:
582 if pre_db:
582 # we rollback only if local db stuff fails. If it goes into
583 # 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
584 # run_task, we're pass rollback state this wouldn't work then
584 Session().rollback()
585 Session().rollback()
585
586
586 return True
587 return True
587
588
588 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
589 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
589 """
590 """
590 Fetches auth_user by user_id,or api_key if present.
591 Fetches auth_user by user_id,or api_key if present.
591 Fills auth_user attributes with those taken from database.
592 Fills auth_user attributes with those taken from database.
592 Additionally set's is_authenitated if lookup fails
593 Additionally set's is_authenitated if lookup fails
593 present in database
594 present in database
594
595
595 :param auth_user: instance of user to set attributes
596 :param auth_user: instance of user to set attributes
596 :param user_id: user id to fetch by
597 :param user_id: user id to fetch by
597 :param api_key: api key to fetch by
598 :param api_key: api key to fetch by
598 :param username: username to fetch by
599 :param username: username to fetch by
599 """
600 """
600 if user_id is None and api_key is None and username is None:
601 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')
602 raise Exception('You need to pass user_id, api_key or username')
602
603
603 log.debug(
604 log.debug(
604 'doing fill data based on: user_id:%s api_key:%s username:%s',
605 'doing fill data based on: user_id:%s api_key:%s username:%s',
605 user_id, api_key, username)
606 user_id, api_key, username)
606 try:
607 try:
607 dbuser = None
608 dbuser = None
608 if user_id:
609 if user_id:
609 dbuser = self.get(user_id)
610 dbuser = self.get(user_id)
610 elif api_key:
611 elif api_key:
611 dbuser = self.get_by_auth_token(api_key)
612 dbuser = self.get_by_auth_token(api_key)
612 elif username:
613 elif username:
613 dbuser = self.get_by_username(username)
614 dbuser = self.get_by_username(username)
614
615
615 if not dbuser:
616 if not dbuser:
616 log.warning(
617 log.warning(
617 'Unable to lookup user by id:%s api_key:%s username:%s',
618 'Unable to lookup user by id:%s api_key:%s username:%s',
618 user_id, api_key, username)
619 user_id, api_key, username)
619 return False
620 return False
620 if not dbuser.active:
621 if not dbuser.active:
621 log.debug('User `%s` is inactive, skipping fill data', username)
622 log.debug('User `%s` is inactive, skipping fill data', username)
622 return False
623 return False
623
624
624 log.debug('filling user:%s data', dbuser)
625 log.debug('filling user:%s data', dbuser)
625
626
626 # TODO: johbo: Think about this and find a clean solution
627 # TODO: johbo: Think about this and find a clean solution
627 user_data = dbuser.get_dict()
628 user_data = dbuser.get_dict()
628 user_data.update(dbuser.get_api_data(include_secrets=True))
629 user_data.update(dbuser.get_api_data(include_secrets=True))
629
630
630 for k, v in user_data.iteritems():
631 for k, v in user_data.iteritems():
631 # properties of auth user we dont update
632 # properties of auth user we dont update
632 if k not in ['auth_tokens', 'permissions']:
633 if k not in ['auth_tokens', 'permissions']:
633 setattr(auth_user, k, v)
634 setattr(auth_user, k, v)
634
635
635 # few extras
636 # few extras
636 setattr(auth_user, 'feed_token', dbuser.feed_token)
637 setattr(auth_user, 'feed_token', dbuser.feed_token)
637 except Exception:
638 except Exception:
638 log.error(traceback.format_exc())
639 log.error(traceback.format_exc())
639 auth_user.is_authenticated = False
640 auth_user.is_authenticated = False
640 return False
641 return False
641
642
642 return True
643 return True
643
644
644 def has_perm(self, user, perm):
645 def has_perm(self, user, perm):
645 perm = self._get_perm(perm)
646 perm = self._get_perm(perm)
646 user = self._get_user(user)
647 user = self._get_user(user)
647
648
648 return UserToPerm.query().filter(UserToPerm.user == user)\
649 return UserToPerm.query().filter(UserToPerm.user == user)\
649 .filter(UserToPerm.permission == perm).scalar() is not None
650 .filter(UserToPerm.permission == perm).scalar() is not None
650
651
651 def grant_perm(self, user, perm):
652 def grant_perm(self, user, perm):
652 """
653 """
653 Grant user global permissions
654 Grant user global permissions
654
655
655 :param user:
656 :param user:
656 :param perm:
657 :param perm:
657 """
658 """
658 user = self._get_user(user)
659 user = self._get_user(user)
659 perm = self._get_perm(perm)
660 perm = self._get_perm(perm)
660 # if this permission is already granted skip it
661 # if this permission is already granted skip it
661 _perm = UserToPerm.query()\
662 _perm = UserToPerm.query()\
662 .filter(UserToPerm.user == user)\
663 .filter(UserToPerm.user == user)\
663 .filter(UserToPerm.permission == perm)\
664 .filter(UserToPerm.permission == perm)\
664 .scalar()
665 .scalar()
665 if _perm:
666 if _perm:
666 return
667 return
667 new = UserToPerm()
668 new = UserToPerm()
668 new.user = user
669 new.user = user
669 new.permission = perm
670 new.permission = perm
670 self.sa.add(new)
671 self.sa.add(new)
671 return new
672 return new
672
673
673 def revoke_perm(self, user, perm):
674 def revoke_perm(self, user, perm):
674 """
675 """
675 Revoke users global permissions
676 Revoke users global permissions
676
677
677 :param user:
678 :param user:
678 :param perm:
679 :param perm:
679 """
680 """
680 user = self._get_user(user)
681 user = self._get_user(user)
681 perm = self._get_perm(perm)
682 perm = self._get_perm(perm)
682
683
683 obj = UserToPerm.query()\
684 obj = UserToPerm.query()\
684 .filter(UserToPerm.user == user)\
685 .filter(UserToPerm.user == user)\
685 .filter(UserToPerm.permission == perm)\
686 .filter(UserToPerm.permission == perm)\
686 .scalar()
687 .scalar()
687 if obj:
688 if obj:
688 self.sa.delete(obj)
689 self.sa.delete(obj)
689
690
690 def add_extra_email(self, user, email):
691 def add_extra_email(self, user, email):
691 """
692 """
692 Adds email address to UserEmailMap
693 Adds email address to UserEmailMap
693
694
694 :param user:
695 :param user:
695 :param email:
696 :param email:
696 """
697 """
697 from rhodecode.model import forms
698 from rhodecode.model import forms
698 form = forms.UserExtraEmailForm()()
699 form = forms.UserExtraEmailForm()()
699 data = form.to_python({'email': email})
700 data = form.to_python({'email': email})
700 user = self._get_user(user)
701 user = self._get_user(user)
701
702
702 obj = UserEmailMap()
703 obj = UserEmailMap()
703 obj.user = user
704 obj.user = user
704 obj.email = data['email']
705 obj.email = data['email']
705 self.sa.add(obj)
706 self.sa.add(obj)
706 return obj
707 return obj
707
708
708 def delete_extra_email(self, user, email_id):
709 def delete_extra_email(self, user, email_id):
709 """
710 """
710 Removes email address from UserEmailMap
711 Removes email address from UserEmailMap
711
712
712 :param user:
713 :param user:
713 :param email_id:
714 :param email_id:
714 """
715 """
715 user = self._get_user(user)
716 user = self._get_user(user)
716 obj = UserEmailMap.query().get(email_id)
717 obj = UserEmailMap.query().get(email_id)
717 if obj:
718 if obj:
718 self.sa.delete(obj)
719 self.sa.delete(obj)
719
720
720 def parse_ip_range(self, ip_range):
721 def parse_ip_range(self, ip_range):
721 ip_list = []
722 ip_list = []
722 def make_unique(value):
723 def make_unique(value):
723 seen = []
724 seen = []
724 return [c for c in value if not (c in seen or seen.append(c))]
725 return [c for c in value if not (c in seen or seen.append(c))]
725
726
726 # firsts split by commas
727 # firsts split by commas
727 for ip_range in ip_range.split(','):
728 for ip_range in ip_range.split(','):
728 if not ip_range:
729 if not ip_range:
729 continue
730 continue
730 ip_range = ip_range.strip()
731 ip_range = ip_range.strip()
731 if '-' in ip_range:
732 if '-' in ip_range:
732 start_ip, end_ip = ip_range.split('-', 1)
733 start_ip, end_ip = ip_range.split('-', 1)
733 start_ip = ipaddress.ip_address(start_ip.strip())
734 start_ip = ipaddress.ip_address(start_ip.strip())
734 end_ip = ipaddress.ip_address(end_ip.strip())
735 end_ip = ipaddress.ip_address(end_ip.strip())
735 parsed_ip_range = []
736 parsed_ip_range = []
736
737
737 for index in xrange(int(start_ip), int(end_ip) + 1):
738 for index in xrange(int(start_ip), int(end_ip) + 1):
738 new_ip = ipaddress.ip_address(index)
739 new_ip = ipaddress.ip_address(index)
739 parsed_ip_range.append(str(new_ip))
740 parsed_ip_range.append(str(new_ip))
740 ip_list.extend(parsed_ip_range)
741 ip_list.extend(parsed_ip_range)
741 else:
742 else:
742 ip_list.append(ip_range)
743 ip_list.append(ip_range)
743
744
744 return make_unique(ip_list)
745 return make_unique(ip_list)
745
746
746 def add_extra_ip(self, user, ip, description=None):
747 def add_extra_ip(self, user, ip, description=None):
747 """
748 """
748 Adds ip address to UserIpMap
749 Adds ip address to UserIpMap
749
750
750 :param user:
751 :param user:
751 :param ip:
752 :param ip:
752 """
753 """
753 from rhodecode.model import forms
754 from rhodecode.model import forms
754 form = forms.UserExtraIpForm()()
755 form = forms.UserExtraIpForm()()
755 data = form.to_python({'ip': ip})
756 data = form.to_python({'ip': ip})
756 user = self._get_user(user)
757 user = self._get_user(user)
757
758
758 obj = UserIpMap()
759 obj = UserIpMap()
759 obj.user = user
760 obj.user = user
760 obj.ip_addr = data['ip']
761 obj.ip_addr = data['ip']
761 obj.description = description
762 obj.description = description
762 self.sa.add(obj)
763 self.sa.add(obj)
763 return obj
764 return obj
764
765
765 def delete_extra_ip(self, user, ip_id):
766 def delete_extra_ip(self, user, ip_id):
766 """
767 """
767 Removes ip address from UserIpMap
768 Removes ip address from UserIpMap
768
769
769 :param user:
770 :param user:
770 :param ip_id:
771 :param ip_id:
771 """
772 """
772 user = self._get_user(user)
773 user = self._get_user(user)
773 obj = UserIpMap.query().get(ip_id)
774 obj = UserIpMap.query().get(ip_id)
774 if obj:
775 if obj:
775 self.sa.delete(obj)
776 self.sa.delete(obj)
776
777
777 def get_accounts_in_creation_order(self, current_user=None):
778 def get_accounts_in_creation_order(self, current_user=None):
778 """
779 """
779 Get accounts in order of creation for deactivation for license limits
780 Get accounts in order of creation for deactivation for license limits
780
781
781 pick currently logged in user, and append to the list in position 0
782 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
783 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.
784 pick all other accounts in order of creation and add it to the list.
784
785
785 Based on that list, the last accounts can be disabled as they are
786 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
787 created at the end and don't include any of the super admins as well
787 as the current user.
788 as the current user.
788
789
789 :param current_user: optionally current user running this operation
790 :param current_user: optionally current user running this operation
790 """
791 """
791
792
792 if not current_user:
793 if not current_user:
793 current_user = get_current_rhodecode_user()
794 current_user = get_current_rhodecode_user()
794 active_super_admins = [
795 active_super_admins = [
795 x.user_id for x in User.query()
796 x.user_id for x in User.query()
796 .filter(User.user_id != current_user.user_id)
797 .filter(User.user_id != current_user.user_id)
797 .filter(User.active == true())
798 .filter(User.active == true())
798 .filter(User.admin == true())
799 .filter(User.admin == true())
799 .order_by(User.created_on.asc())]
800 .order_by(User.created_on.asc())]
800
801
801 active_regular_users = [
802 active_regular_users = [
802 x.user_id for x in User.query()
803 x.user_id for x in User.query()
803 .filter(User.user_id != current_user.user_id)
804 .filter(User.user_id != current_user.user_id)
804 .filter(User.active == true())
805 .filter(User.active == true())
805 .filter(User.admin == false())
806 .filter(User.admin == false())
806 .order_by(User.created_on.asc())]
807 .order_by(User.created_on.asc())]
807
808
808 list_of_accounts = [current_user.user_id]
809 list_of_accounts = [current_user.user_id]
809 list_of_accounts += active_super_admins
810 list_of_accounts += active_super_admins
810 list_of_accounts += active_regular_users
811 list_of_accounts += active_regular_users
811
812
812 return list_of_accounts
813 return list_of_accounts
813
814
814 def deactivate_last_users(self, expected_users):
815 def deactivate_last_users(self, expected_users):
815 """
816 """
816 Deactivate accounts that are over the license limits.
817 Deactivate accounts that are over the license limits.
817 Algorithm of which accounts to disabled is based on the formula:
818 Algorithm of which accounts to disabled is based on the formula:
818
819
819 Get current user, then super admins in creation order, then regular
820 Get current user, then super admins in creation order, then regular
820 active users in creation order.
821 active users in creation order.
821
822
822 Using that list we mark all accounts from the end of it as inactive.
823 Using that list we mark all accounts from the end of it as inactive.
823 This way we block only latest created accounts.
824 This way we block only latest created accounts.
824
825
825 :param expected_users: list of users in special order, we deactivate
826 :param expected_users: list of users in special order, we deactivate
826 the end N ammoun of users from that list
827 the end N ammoun of users from that list
827 """
828 """
828
829
829 list_of_accounts = self.get_accounts_in_creation_order()
830 list_of_accounts = self.get_accounts_in_creation_order()
830
831
831 for acc_id in list_of_accounts[expected_users + 1:]:
832 for acc_id in list_of_accounts[expected_users + 1:]:
832 user = User.get(acc_id)
833 user = User.get(acc_id)
833 log.info('Deactivating account %s for license unlock', user)
834 log.info('Deactivating account %s for license unlock', user)
834 user.active = False
835 user.active = False
835 Session().add(user)
836 Session().add(user)
836 Session().commit()
837 Session().commit()
837
838
838 return
839 return
General Comments 0
You need to be logged in to leave comments. Login now