##// END OF EJS Templates
auth: disable password change form for accounts that are not managed by RhodeCode....
marcink -
r1275:b650c2de default
parent child Browse files
Show More
@@ -1,467 +1,468 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27 import datetime
27 import datetime
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
31 from pyramid.threadlocal import get_current_registry
32 from pylons import request, tmpl_context as c, url, session
32 from pylons import request, tmpl_context as c, url, session
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36 from webob.exc import HTTPBadGateway
36 from webob.exc import HTTPBadGateway
37
37
38 from rhodecode import forms
38 from rhodecode import forms
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib import auth
40 from rhodecode.lib import auth
41 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
44 from rhodecode.lib.utils import jsonify
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
46 from rhodecode.lib.ext_json import json
46 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
47 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
48 ChannelstreamException
49
49
50 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 Repository, PullRequest, UserEmailMap, User, UserFollowing)
53 from rhodecode.model.forms import UserForm
53 from rhodecode.model.forms import UserForm
54 from rhodecode.model.scm import RepoList
54 from rhodecode.model.scm import RepoList
55 from rhodecode.model.user import UserModel
55 from rhodecode.model.user import UserModel
56 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.auth_token import AuthTokenModel
57 from rhodecode.model.auth_token import AuthTokenModel
58 from rhodecode.model.meta import Session
58 from rhodecode.model.meta import Session
59 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.comment import ChangesetCommentsModel
60 from rhodecode.model.comment import ChangesetCommentsModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 class MyAccountController(BaseController):
65 class MyAccountController(BaseController):
66 """REST Controller styled on the Atom Publishing Protocol"""
66 """REST Controller styled on the Atom Publishing Protocol"""
67 # To properly map this controller, ensure your config/routing.py
67 # To properly map this controller, ensure your config/routing.py
68 # file has a resource setup:
68 # file has a resource setup:
69 # map.resource('setting', 'settings', controller='admin/settings',
69 # map.resource('setting', 'settings', controller='admin/settings',
70 # path_prefix='/admin', name_prefix='admin_')
70 # path_prefix='/admin', name_prefix='admin_')
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 def __before__(self):
74 def __before__(self):
75 super(MyAccountController, self).__before__()
75 super(MyAccountController, self).__before__()
76
76
77 def __load_data(self):
77 def __load_data(self):
78 c.user = User.get(c.rhodecode_user.user_id)
78 c.user = User.get(c.rhodecode_user.user_id)
79 if c.user.username == User.DEFAULT_USER:
79 if c.user.username == User.DEFAULT_USER:
80 h.flash(_("You can't edit this user since it's"
80 h.flash(_("You can't edit this user since it's"
81 " crucial for entire application"), category='warning')
81 " crucial for entire application"), category='warning')
82 return redirect(url('users'))
82 return redirect(url('users'))
83
83
84 c.auth_user = AuthUser(
85 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
86
84 def _load_my_repos_data(self, watched=False):
87 def _load_my_repos_data(self, watched=False):
85 if watched:
88 if watched:
86 admin = False
89 admin = False
87 follows_repos = Session().query(UserFollowing)\
90 follows_repos = Session().query(UserFollowing)\
88 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
91 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
89 .options(joinedload(UserFollowing.follows_repository))\
92 .options(joinedload(UserFollowing.follows_repository))\
90 .all()
93 .all()
91 repo_list = [x.follows_repository for x in follows_repos]
94 repo_list = [x.follows_repository for x in follows_repos]
92 else:
95 else:
93 admin = True
96 admin = True
94 repo_list = Repository.get_all_repos(
97 repo_list = Repository.get_all_repos(
95 user_id=c.rhodecode_user.user_id)
98 user_id=c.rhodecode_user.user_id)
96 repo_list = RepoList(repo_list, perm_set=[
99 repo_list = RepoList(repo_list, perm_set=[
97 'repository.read', 'repository.write', 'repository.admin'])
100 'repository.read', 'repository.write', 'repository.admin'])
98
101
99 repos_data = RepoModel().get_repos_as_dict(
102 repos_data = RepoModel().get_repos_as_dict(
100 repo_list=repo_list, admin=admin)
103 repo_list=repo_list, admin=admin)
101 # json used to render the grid
104 # json used to render the grid
102 return json.dumps(repos_data)
105 return json.dumps(repos_data)
103
106
104 @auth.CSRFRequired()
107 @auth.CSRFRequired()
105 def my_account_update(self):
108 def my_account_update(self):
106 """
109 """
107 POST /_admin/my_account Updates info of my account
110 POST /_admin/my_account Updates info of my account
108 """
111 """
109 # url('my_account')
112 # url('my_account')
110 c.active = 'profile_edit'
113 c.active = 'profile_edit'
111 self.__load_data()
114 self.__load_data()
112 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
115 c.perm_user = c.auth_user
113 ip_addr=self.ip_addr)
114 c.extern_type = c.user.extern_type
116 c.extern_type = c.user.extern_type
115 c.extern_name = c.user.extern_name
117 c.extern_name = c.user.extern_name
116
118
117 defaults = c.user.get_dict()
119 defaults = c.user.get_dict()
118 update = False
120 update = False
119 _form = UserForm(edit=True,
121 _form = UserForm(edit=True,
120 old_data={'user_id': c.rhodecode_user.user_id,
122 old_data={'user_id': c.rhodecode_user.user_id,
121 'email': c.rhodecode_user.email})()
123 'email': c.rhodecode_user.email})()
122 form_result = {}
124 form_result = {}
123 try:
125 try:
124 post_data = dict(request.POST)
126 post_data = dict(request.POST)
125 post_data['new_password'] = ''
127 post_data['new_password'] = ''
126 post_data['password_confirmation'] = ''
128 post_data['password_confirmation'] = ''
127 form_result = _form.to_python(post_data)
129 form_result = _form.to_python(post_data)
128 # skip updating those attrs for my account
130 # skip updating those attrs for my account
129 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
131 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
130 'new_password', 'password_confirmation']
132 'new_password', 'password_confirmation']
131 # TODO: plugin should define if username can be updated
133 # TODO: plugin should define if username can be updated
132 if c.extern_type != "rhodecode":
134 if c.extern_type != "rhodecode":
133 # forbid updating username for external accounts
135 # forbid updating username for external accounts
134 skip_attrs.append('username')
136 skip_attrs.append('username')
135
137
136 UserModel().update_user(
138 UserModel().update_user(
137 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
139 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
138 h.flash(_('Your account was updated successfully'),
140 h.flash(_('Your account was updated successfully'),
139 category='success')
141 category='success')
140 Session().commit()
142 Session().commit()
141 update = True
143 update = True
142
144
143 except formencode.Invalid as errors:
145 except formencode.Invalid as errors:
144 return htmlfill.render(
146 return htmlfill.render(
145 render('admin/my_account/my_account.html'),
147 render('admin/my_account/my_account.html'),
146 defaults=errors.value,
148 defaults=errors.value,
147 errors=errors.error_dict or {},
149 errors=errors.error_dict or {},
148 prefix_error=False,
150 prefix_error=False,
149 encoding="UTF-8",
151 encoding="UTF-8",
150 force_defaults=False)
152 force_defaults=False)
151 except Exception:
153 except Exception:
152 log.exception("Exception updating user")
154 log.exception("Exception updating user")
153 h.flash(_('Error occurred during update of user %s')
155 h.flash(_('Error occurred during update of user %s')
154 % form_result.get('username'), category='error')
156 % form_result.get('username'), category='error')
155
157
156 if update:
158 if update:
157 return redirect('my_account')
159 return redirect('my_account')
158
160
159 return htmlfill.render(
161 return htmlfill.render(
160 render('admin/my_account/my_account.html'),
162 render('admin/my_account/my_account.html'),
161 defaults=defaults,
163 defaults=defaults,
162 encoding="UTF-8",
164 encoding="UTF-8",
163 force_defaults=False
165 force_defaults=False
164 )
166 )
165
167
166 def my_account(self):
168 def my_account(self):
167 """
169 """
168 GET /_admin/my_account Displays info about my account
170 GET /_admin/my_account Displays info about my account
169 """
171 """
170 # url('my_account')
172 # url('my_account')
171 c.active = 'profile'
173 c.active = 'profile'
172 self.__load_data()
174 self.__load_data()
173
175
174 defaults = c.user.get_dict()
176 defaults = c.user.get_dict()
175 return htmlfill.render(
177 return htmlfill.render(
176 render('admin/my_account/my_account.html'),
178 render('admin/my_account/my_account.html'),
177 defaults=defaults, encoding="UTF-8", force_defaults=False)
179 defaults=defaults, encoding="UTF-8", force_defaults=False)
178
180
179 def my_account_edit(self):
181 def my_account_edit(self):
180 """
182 """
181 GET /_admin/my_account/edit Displays edit form of my account
183 GET /_admin/my_account/edit Displays edit form of my account
182 """
184 """
183 c.active = 'profile_edit'
185 c.active = 'profile_edit'
184 self.__load_data()
186 self.__load_data()
185 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
187 c.perm_user = c.auth_user
186 ip_addr=self.ip_addr)
187 c.extern_type = c.user.extern_type
188 c.extern_type = c.user.extern_type
188 c.extern_name = c.user.extern_name
189 c.extern_name = c.user.extern_name
189
190
190 defaults = c.user.get_dict()
191 defaults = c.user.get_dict()
191 return htmlfill.render(
192 return htmlfill.render(
192 render('admin/my_account/my_account.html'),
193 render('admin/my_account/my_account.html'),
193 defaults=defaults,
194 defaults=defaults,
194 encoding="UTF-8",
195 encoding="UTF-8",
195 force_defaults=False
196 force_defaults=False
196 )
197 )
197
198
198 @auth.CSRFRequired(except_methods=['GET'])
199 @auth.CSRFRequired(except_methods=['GET'])
199 def my_account_password(self):
200 def my_account_password(self):
200 c.active = 'password'
201 c.active = 'password'
201 self.__load_data()
202 self.__load_data()
203 c.extern_type = c.user.extern_type
202
204
203 schema = user_schema.ChangePasswordSchema().bind(
205 schema = user_schema.ChangePasswordSchema().bind(
204 username=c.rhodecode_user.username)
206 username=c.rhodecode_user.username)
205
207
206 form = forms.Form(schema,
208 form = forms.Form(schema,
207 buttons=(forms.buttons.save, forms.buttons.reset))
209 buttons=(forms.buttons.save, forms.buttons.reset))
208
210
209 if request.method == 'POST':
211 if request.method == 'POST' and c.extern_type == 'rhodecode':
210 controls = request.POST.items()
212 controls = request.POST.items()
211 try:
213 try:
212 valid_data = form.validate(controls)
214 valid_data = form.validate(controls)
213 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
215 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
214 instance = c.rhodecode_user.get_instance()
216 instance = c.rhodecode_user.get_instance()
215 instance.update_userdata(force_password_change=False)
217 instance.update_userdata(force_password_change=False)
216 Session().commit()
218 Session().commit()
217 except forms.ValidationFailure as e:
219 except forms.ValidationFailure as e:
218 request.session.flash(
220 request.session.flash(
219 _('Error occurred during update of user password'),
221 _('Error occurred during update of user password'),
220 queue='error')
222 queue='error')
221 form = e
223 form = e
222 except Exception:
224 except Exception:
223 log.exception("Exception updating password")
225 log.exception("Exception updating password")
224 request.session.flash(
226 request.session.flash(
225 _('Error occurred during update of user password'),
227 _('Error occurred during update of user password'),
226 queue='error')
228 queue='error')
227 else:
229 else:
228 session.setdefault('rhodecode_user', {}).update(
230 session.setdefault('rhodecode_user', {}).update(
229 {'password': md5(instance.password)})
231 {'password': md5(instance.password)})
230 session.save()
232 session.save()
231 request.session.flash(
233 request.session.flash(
232 _("Successfully updated password"), queue='success')
234 _("Successfully updated password"), queue='success')
233 return redirect(url('my_account_password'))
235 return redirect(url('my_account_password'))
234
236
235 c.form = form
237 c.form = form
236 return render('admin/my_account/my_account.html')
238 return render('admin/my_account/my_account.html')
237
239
238 def my_account_repos(self):
240 def my_account_repos(self):
239 c.active = 'repos'
241 c.active = 'repos'
240 self.__load_data()
242 self.__load_data()
241
243
242 # json used to render the grid
244 # json used to render the grid
243 c.data = self._load_my_repos_data()
245 c.data = self._load_my_repos_data()
244 return render('admin/my_account/my_account.html')
246 return render('admin/my_account/my_account.html')
245
247
246 def my_account_watched(self):
248 def my_account_watched(self):
247 c.active = 'watched'
249 c.active = 'watched'
248 self.__load_data()
250 self.__load_data()
249
251
250 # json used to render the grid
252 # json used to render the grid
251 c.data = self._load_my_repos_data(watched=True)
253 c.data = self._load_my_repos_data(watched=True)
252 return render('admin/my_account/my_account.html')
254 return render('admin/my_account/my_account.html')
253
255
254 def my_account_perms(self):
256 def my_account_perms(self):
255 c.active = 'perms'
257 c.active = 'perms'
256 self.__load_data()
258 self.__load_data()
257 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
259 c.perm_user = c.auth_user
258 ip_addr=self.ip_addr)
259
260
260 return render('admin/my_account/my_account.html')
261 return render('admin/my_account/my_account.html')
261
262
262 def my_account_emails(self):
263 def my_account_emails(self):
263 c.active = 'emails'
264 c.active = 'emails'
264 self.__load_data()
265 self.__load_data()
265
266
266 c.user_email_map = UserEmailMap.query()\
267 c.user_email_map = UserEmailMap.query()\
267 .filter(UserEmailMap.user == c.user).all()
268 .filter(UserEmailMap.user == c.user).all()
268 return render('admin/my_account/my_account.html')
269 return render('admin/my_account/my_account.html')
269
270
270 @auth.CSRFRequired()
271 @auth.CSRFRequired()
271 def my_account_emails_add(self):
272 def my_account_emails_add(self):
272 email = request.POST.get('new_email')
273 email = request.POST.get('new_email')
273
274
274 try:
275 try:
275 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
276 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
276 Session().commit()
277 Session().commit()
277 h.flash(_("Added new email address `%s` for user account") % email,
278 h.flash(_("Added new email address `%s` for user account") % email,
278 category='success')
279 category='success')
279 except formencode.Invalid as error:
280 except formencode.Invalid as error:
280 msg = error.error_dict['email']
281 msg = error.error_dict['email']
281 h.flash(msg, category='error')
282 h.flash(msg, category='error')
282 except Exception:
283 except Exception:
283 log.exception("Exception in my_account_emails")
284 log.exception("Exception in my_account_emails")
284 h.flash(_('An error occurred during email saving'),
285 h.flash(_('An error occurred during email saving'),
285 category='error')
286 category='error')
286 return redirect(url('my_account_emails'))
287 return redirect(url('my_account_emails'))
287
288
288 @auth.CSRFRequired()
289 @auth.CSRFRequired()
289 def my_account_emails_delete(self):
290 def my_account_emails_delete(self):
290 email_id = request.POST.get('del_email_id')
291 email_id = request.POST.get('del_email_id')
291 user_model = UserModel()
292 user_model = UserModel()
292 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
293 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
293 Session().commit()
294 Session().commit()
294 h.flash(_("Removed email address from user account"),
295 h.flash(_("Removed email address from user account"),
295 category='success')
296 category='success')
296 return redirect(url('my_account_emails'))
297 return redirect(url('my_account_emails'))
297
298
298 def _extract_ordering(self, request):
299 def _extract_ordering(self, request):
299 column_index = safe_int(request.GET.get('order[0][column]'))
300 column_index = safe_int(request.GET.get('order[0][column]'))
300 order_dir = request.GET.get('order[0][dir]', 'desc')
301 order_dir = request.GET.get('order[0][dir]', 'desc')
301 order_by = request.GET.get(
302 order_by = request.GET.get(
302 'columns[%s][data][sort]' % column_index, 'name_raw')
303 'columns[%s][data][sort]' % column_index, 'name_raw')
303 return order_by, order_dir
304 return order_by, order_dir
304
305
305 def _get_pull_requests_list(self, statuses):
306 def _get_pull_requests_list(self, statuses):
306 start = safe_int(request.GET.get('start'), 0)
307 start = safe_int(request.GET.get('start'), 0)
307 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
308 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
308 order_by, order_dir = self._extract_ordering(request)
309 order_by, order_dir = self._extract_ordering(request)
309
310
310 pull_requests = PullRequestModel().get_im_participating_in(
311 pull_requests = PullRequestModel().get_im_participating_in(
311 user_id=c.rhodecode_user.user_id,
312 user_id=c.rhodecode_user.user_id,
312 statuses=statuses,
313 statuses=statuses,
313 offset=start, length=length, order_by=order_by,
314 offset=start, length=length, order_by=order_by,
314 order_dir=order_dir)
315 order_dir=order_dir)
315
316
316 pull_requests_total_count = PullRequestModel().count_im_participating_in(
317 pull_requests_total_count = PullRequestModel().count_im_participating_in(
317 user_id=c.rhodecode_user.user_id, statuses=statuses)
318 user_id=c.rhodecode_user.user_id, statuses=statuses)
318
319
319 from rhodecode.lib.utils import PartialRenderer
320 from rhodecode.lib.utils import PartialRenderer
320 _render = PartialRenderer('data_table/_dt_elements.html')
321 _render = PartialRenderer('data_table/_dt_elements.html')
321 data = []
322 data = []
322 for pr in pull_requests:
323 for pr in pull_requests:
323 repo_id = pr.target_repo_id
324 repo_id = pr.target_repo_id
324 comments = ChangesetCommentsModel().get_all_comments(
325 comments = ChangesetCommentsModel().get_all_comments(
325 repo_id, pull_request=pr)
326 repo_id, pull_request=pr)
326 owned = pr.user_id == c.rhodecode_user.user_id
327 owned = pr.user_id == c.rhodecode_user.user_id
327 status = pr.calculated_review_status()
328 status = pr.calculated_review_status()
328
329
329 data.append({
330 data.append({
330 'target_repo': _render('pullrequest_target_repo',
331 'target_repo': _render('pullrequest_target_repo',
331 pr.target_repo.repo_name),
332 pr.target_repo.repo_name),
332 'name': _render('pullrequest_name',
333 'name': _render('pullrequest_name',
333 pr.pull_request_id, pr.target_repo.repo_name,
334 pr.pull_request_id, pr.target_repo.repo_name,
334 short=True),
335 short=True),
335 'name_raw': pr.pull_request_id,
336 'name_raw': pr.pull_request_id,
336 'status': _render('pullrequest_status', status),
337 'status': _render('pullrequest_status', status),
337 'title': _render(
338 'title': _render(
338 'pullrequest_title', pr.title, pr.description),
339 'pullrequest_title', pr.title, pr.description),
339 'description': h.escape(pr.description),
340 'description': h.escape(pr.description),
340 'updated_on': _render('pullrequest_updated_on',
341 'updated_on': _render('pullrequest_updated_on',
341 h.datetime_to_time(pr.updated_on)),
342 h.datetime_to_time(pr.updated_on)),
342 'updated_on_raw': h.datetime_to_time(pr.updated_on),
343 'updated_on_raw': h.datetime_to_time(pr.updated_on),
343 'created_on': _render('pullrequest_updated_on',
344 'created_on': _render('pullrequest_updated_on',
344 h.datetime_to_time(pr.created_on)),
345 h.datetime_to_time(pr.created_on)),
345 'created_on_raw': h.datetime_to_time(pr.created_on),
346 'created_on_raw': h.datetime_to_time(pr.created_on),
346 'author': _render('pullrequest_author',
347 'author': _render('pullrequest_author',
347 pr.author.full_contact, ),
348 pr.author.full_contact, ),
348 'author_raw': pr.author.full_name,
349 'author_raw': pr.author.full_name,
349 'comments': _render('pullrequest_comments', len(comments)),
350 'comments': _render('pullrequest_comments', len(comments)),
350 'comments_raw': len(comments),
351 'comments_raw': len(comments),
351 'closed': pr.is_closed(),
352 'closed': pr.is_closed(),
352 'owned': owned
353 'owned': owned
353 })
354 })
354 # json used to render the grid
355 # json used to render the grid
355 data = ({
356 data = ({
356 'data': data,
357 'data': data,
357 'recordsTotal': pull_requests_total_count,
358 'recordsTotal': pull_requests_total_count,
358 'recordsFiltered': pull_requests_total_count,
359 'recordsFiltered': pull_requests_total_count,
359 })
360 })
360 return data
361 return data
361
362
362 def my_account_pullrequests(self):
363 def my_account_pullrequests(self):
363 c.active = 'pullrequests'
364 c.active = 'pullrequests'
364 self.__load_data()
365 self.__load_data()
365 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
366 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
366
367
367 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
368 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
368 if c.show_closed:
369 if c.show_closed:
369 statuses += [PullRequest.STATUS_CLOSED]
370 statuses += [PullRequest.STATUS_CLOSED]
370 data = self._get_pull_requests_list(statuses)
371 data = self._get_pull_requests_list(statuses)
371 if not request.is_xhr:
372 if not request.is_xhr:
372 c.data_participate = json.dumps(data['data'])
373 c.data_participate = json.dumps(data['data'])
373 c.records_total_participate = data['recordsTotal']
374 c.records_total_participate = data['recordsTotal']
374 return render('admin/my_account/my_account.html')
375 return render('admin/my_account/my_account.html')
375 else:
376 else:
376 return json.dumps(data)
377 return json.dumps(data)
377
378
378 def my_account_auth_tokens(self):
379 def my_account_auth_tokens(self):
379 c.active = 'auth_tokens'
380 c.active = 'auth_tokens'
380 self.__load_data()
381 self.__load_data()
381 show_expired = True
382 show_expired = True
382 c.lifetime_values = [
383 c.lifetime_values = [
383 (str(-1), _('forever')),
384 (str(-1), _('forever')),
384 (str(5), _('5 minutes')),
385 (str(5), _('5 minutes')),
385 (str(60), _('1 hour')),
386 (str(60), _('1 hour')),
386 (str(60 * 24), _('1 day')),
387 (str(60 * 24), _('1 day')),
387 (str(60 * 24 * 30), _('1 month')),
388 (str(60 * 24 * 30), _('1 month')),
388 ]
389 ]
389 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
390 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
390 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
391 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
391 for x in AuthTokenModel.cls.ROLES]
392 for x in AuthTokenModel.cls.ROLES]
392 c.role_options = [(c.role_values, _("Role"))]
393 c.role_options = [(c.role_values, _("Role"))]
393 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
394 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
394 c.rhodecode_user.user_id, show_expired=show_expired)
395 c.rhodecode_user.user_id, show_expired=show_expired)
395 return render('admin/my_account/my_account.html')
396 return render('admin/my_account/my_account.html')
396
397
397 @auth.CSRFRequired()
398 @auth.CSRFRequired()
398 def my_account_auth_tokens_add(self):
399 def my_account_auth_tokens_add(self):
399 lifetime = safe_int(request.POST.get('lifetime'), -1)
400 lifetime = safe_int(request.POST.get('lifetime'), -1)
400 description = request.POST.get('description')
401 description = request.POST.get('description')
401 role = request.POST.get('role')
402 role = request.POST.get('role')
402 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
403 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
403 role)
404 role)
404 Session().commit()
405 Session().commit()
405 h.flash(_("Auth token successfully created"), category='success')
406 h.flash(_("Auth token successfully created"), category='success')
406 return redirect(url('my_account_auth_tokens'))
407 return redirect(url('my_account_auth_tokens'))
407
408
408 @auth.CSRFRequired()
409 @auth.CSRFRequired()
409 def my_account_auth_tokens_delete(self):
410 def my_account_auth_tokens_delete(self):
410 auth_token = request.POST.get('del_auth_token')
411 auth_token = request.POST.get('del_auth_token')
411 user_id = c.rhodecode_user.user_id
412 user_id = c.rhodecode_user.user_id
412 if request.POST.get('del_auth_token_builtin'):
413 if request.POST.get('del_auth_token_builtin'):
413 user = User.get(user_id)
414 user = User.get(user_id)
414 if user:
415 if user:
415 user.api_key = generate_auth_token(user.username)
416 user.api_key = generate_auth_token(user.username)
416 Session().add(user)
417 Session().add(user)
417 Session().commit()
418 Session().commit()
418 h.flash(_("Auth token successfully reset"), category='success')
419 h.flash(_("Auth token successfully reset"), category='success')
419 elif auth_token:
420 elif auth_token:
420 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
421 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
421 Session().commit()
422 Session().commit()
422 h.flash(_("Auth token successfully deleted"), category='success')
423 h.flash(_("Auth token successfully deleted"), category='success')
423
424
424 return redirect(url('my_account_auth_tokens'))
425 return redirect(url('my_account_auth_tokens'))
425
426
426 def my_notifications(self):
427 def my_notifications(self):
427 c.active = 'notifications'
428 c.active = 'notifications'
428 return render('admin/my_account/my_account.html')
429 return render('admin/my_account/my_account.html')
429
430
430 @auth.CSRFRequired()
431 @auth.CSRFRequired()
431 @jsonify
432 @jsonify
432 def my_notifications_toggle_visibility(self):
433 def my_notifications_toggle_visibility(self):
433 user = c.rhodecode_user.get_instance()
434 user = c.rhodecode_user.get_instance()
434 new_status = not user.user_data.get('notification_status', True)
435 new_status = not user.user_data.get('notification_status', True)
435 user.update_userdata(notification_status=new_status)
436 user.update_userdata(notification_status=new_status)
436 Session().commit()
437 Session().commit()
437 return user.user_data['notification_status']
438 return user.user_data['notification_status']
438
439
439 @auth.CSRFRequired()
440 @auth.CSRFRequired()
440 @jsonify
441 @jsonify
441 def my_account_notifications_test_channelstream(self):
442 def my_account_notifications_test_channelstream(self):
442 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
443 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
443 c.rhodecode_user.username, datetime.datetime.now())
444 c.rhodecode_user.username, datetime.datetime.now())
444 payload = {
445 payload = {
445 'type': 'message',
446 'type': 'message',
446 'timestamp': datetime.datetime.utcnow(),
447 'timestamp': datetime.datetime.utcnow(),
447 'user': 'system',
448 'user': 'system',
448 #'channel': 'broadcast',
449 #'channel': 'broadcast',
449 'pm_users': [c.rhodecode_user.username],
450 'pm_users': [c.rhodecode_user.username],
450 'message': {
451 'message': {
451 'message': message,
452 'message': message,
452 'level': 'info',
453 'level': 'info',
453 'topic': '/notifications'
454 'topic': '/notifications'
454 }
455 }
455 }
456 }
456
457
457 registry = get_current_registry()
458 registry = get_current_registry()
458 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
459 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
459 channelstream_config = rhodecode_plugins.get('channelstream', {})
460 channelstream_config = rhodecode_plugins.get('channelstream', {})
460
461
461 try:
462 try:
462 channelstream_request(channelstream_config, [payload], '/message')
463 channelstream_request(channelstream_config, [payload], '/message')
463 except ChannelstreamException as e:
464 except ChannelstreamException as e:
464 log.exception('Failed to send channelstream data')
465 log.exception('Failed to send channelstream data')
465 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
466 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
466 return {"response": 'Channelstream data sent. '
467 return {"response": 'Channelstream data sent. '
467 'You should see a new live message now.'}
468 'You should see a new live message now.'}
@@ -1,5 +1,13 b''
1 <%namespace name="widgets" file="/widgets.html"/>
1 <%namespace name="widgets" file="/widgets.html"/>
2
2
3 <%widgets:panel title="${_('Change Your Account Password')}">
3 <%widgets:panel title="${_('Change Your Account Password')}">
4
5 % if c.extern_type != 'rhodecode':
6 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
7 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
8 </p>
9 % else:
4 ${c.form.render() | n}
10 ${c.form.render() | n}
11 % endif
12
5 </%widgets:panel>
13 </%widgets:panel>
@@ -1,110 +1,113 b''
1 <%namespace name="base" file="/base/base.html"/>
1 <%namespace name="base" file="/base/base.html"/>
2 <div class="panel panel-default user-profile">
2 <div class="panel panel-default user-profile">
3 <div class="panel-heading">
3 <div class="panel-heading">
4 <h3 class="panel-title">${_('My Profile')}</h3>
4 <h3 class="panel-title">${_('My Profile')}</h3>
5 <a href="${url('my_account')}" class="panel-edit">Close</a>
5 <a href="${url('my_account')}" class="panel-edit">Close</a>
6 </div>
6 </div>
7
7
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(url('my_account'), method='post', class_='form')}
9 ${h.secure_form(url('my_account'), method='post', class_='form')}
10 <% readonly = None %>
10 <% readonly = None %>
11 <% disabled = "" %>
11 <% disabled = "" %>
12
12
13 % if c.extern_type != 'rhodecode':
13 % if c.extern_type != 'rhodecode':
14 <% readonly = "readonly" %>
14 <% readonly = "readonly" %>
15 <% disabled = "disabled" %>
15 <% disabled = "disabled" %>
16 <div class="infoform">
16 <div class="infoform">
17 <div class="fields">
17 <div class="fields">
18 <p>${_('Your user account details are managed by an external source, i.e. LDAP. Details cannot be managed here.')}.</p>
18 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
19 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
20 </p>
21
19 <div class="field">
22 <div class="field">
20 <div class="label">
23 <div class="label">
21 <label for="username">${_('Username')}:</label>
24 <label for="username">${_('Username')}:</label>
22 </div>
25 </div>
23 <div class="input">
26 <div class="input">
24 ${h.text('username', class_='input-valuedisplay', readonly=readonly)}
27 ${h.text('username', class_='input-valuedisplay', readonly=readonly)}
25 </div>
28 </div>
26 </div>
29 </div>
27
30
28 <div class="field">
31 <div class="field">
29 <div class="label">
32 <div class="label">
30 <label for="name">${_('First Name')}:</label>
33 <label for="name">${_('First Name')}:</label>
31 </div>
34 </div>
32 <div class="input">
35 <div class="input">
33 ${h.text('firstname', class_='input-valuedisplay', readonly=readonly)}
36 ${h.text('firstname', class_='input-valuedisplay', readonly=readonly)}
34 </div>
37 </div>
35 </div>
38 </div>
36
39
37 <div class="field">
40 <div class="field">
38 <div class="label">
41 <div class="label">
39 <label for="lastname">${_('Last Name')}:</label>
42 <label for="lastname">${_('Last Name')}:</label>
40 </div>
43 </div>
41 <div class="input-valuedisplay">
44 <div class="input-valuedisplay">
42 ${h.text('lastname', class_='input-valuedisplay', readonly=readonly)}
45 ${h.text('lastname', class_='input-valuedisplay', readonly=readonly)}
43 </div>
46 </div>
44 </div>
47 </div>
45 </div>
48 </div>
46 </div>
49 </div>
47 % else:
50 % else:
48 <div class="form">
51 <div class="form">
49 <div class="fields">
52 <div class="fields">
50 <div class="field">
53 <div class="field">
51 <div class="label photo">
54 <div class="label photo">
52 ${_('Photo')}:
55 ${_('Photo')}:
53 </div>
56 </div>
54 <div class="input profile">
57 <div class="input profile">
55 %if c.visual.use_gravatar:
58 %if c.visual.use_gravatar:
56 ${base.gravatar(c.user.email, 100)}
59 ${base.gravatar(c.user.email, 100)}
57 <p class="help-block">${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
60 <p class="help-block">${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
58 %else:
61 %else:
59 ${base.gravatar(c.user.email, 20)}
62 ${base.gravatar(c.user.email, 20)}
60 ${_('Avatars are disabled')}
63 ${_('Avatars are disabled')}
61 %endif
64 %endif
62 </div>
65 </div>
63 </div>
66 </div>
64 <div class="field">
67 <div class="field">
65 <div class="label">
68 <div class="label">
66 <label for="username">${_('Username')}:</label>
69 <label for="username">${_('Username')}:</label>
67 </div>
70 </div>
68 <div class="input">
71 <div class="input">
69 ${h.text('username', class_='medium%s' % disabled, readonly=readonly)}
72 ${h.text('username', class_='medium%s' % disabled, readonly=readonly)}
70 ${h.hidden('extern_name', c.extern_name)}
73 ${h.hidden('extern_name', c.extern_name)}
71 ${h.hidden('extern_type', c.extern_type)}
74 ${h.hidden('extern_type', c.extern_type)}
72 </div>
75 </div>
73 </div>
76 </div>
74 <div class="field">
77 <div class="field">
75 <div class="label">
78 <div class="label">
76 <label for="name">${_('First Name')}:</label>
79 <label for="name">${_('First Name')}:</label>
77 </div>
80 </div>
78 <div class="input">
81 <div class="input">
79 ${h.text('firstname', class_="medium")}
82 ${h.text('firstname', class_="medium")}
80 </div>
83 </div>
81 </div>
84 </div>
82
85
83 <div class="field">
86 <div class="field">
84 <div class="label">
87 <div class="label">
85 <label for="lastname">${_('Last Name')}:</label>
88 <label for="lastname">${_('Last Name')}:</label>
86 </div>
89 </div>
87 <div class="input">
90 <div class="input">
88 ${h.text('lastname', class_="medium")}
91 ${h.text('lastname', class_="medium")}
89 </div>
92 </div>
90 </div>
93 </div>
91
94
92 <div class="field">
95 <div class="field">
93 <div class="label">
96 <div class="label">
94 <label for="email">${_('Email')}:</label>
97 <label for="email">${_('Email')}:</label>
95 </div>
98 </div>
96 <div class="input">
99 <div class="input">
97 ## we should be able to edit email !
100 ## we should be able to edit email !
98 ${h.text('email', class_="medium")}
101 ${h.text('email', class_="medium")}
99 </div>
102 </div>
100 </div>
103 </div>
101
104
102 <div class="buttons">
105 <div class="buttons">
103 ${h.submit('save', _('Save'), class_="btn")}
106 ${h.submit('save', _('Save'), class_="btn")}
104 ${h.reset('reset', _('Reset'), class_="btn")}
107 ${h.reset('reset', _('Reset'), class_="btn")}
105 </div>
108 </div>
106 </div>
109 </div>
107 </div>
110 </div>
108 % endif
111 % endif
109 </div>
112 </div>
110 </div> No newline at end of file
113 </div>
General Comments 0
You need to be logged in to leave comments. Login now