##// END OF EJS Templates
pull-requests: added awaiting my review filter for users pull-requests....
super-admin -
r4690:2e951f8d stable
parent child
Show More
@@ -1,204 +1,208
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 # -*- coding: utf-8 -*-
20 # -*- coding: utf-8 -*-
21
21
22 # Copyright (C) 2016-2020 RhodeCode GmbH
22 # Copyright (C) 2016-2020 RhodeCode GmbH
23 #
23 #
24 # This program is free software: you can redistribute it and/or modify
24 # This program is free software: you can redistribute it and/or modify
25 # it under the terms of the GNU Affero General Public License, version 3
25 # it under the terms of the GNU Affero General Public License, version 3
26 # (only), as published by the Free Software Foundation.
26 # (only), as published by the Free Software Foundation.
27 #
27 #
28 # This program is distributed in the hope that it will be useful,
28 # This program is distributed in the hope that it will be useful,
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 # GNU General Public License for more details.
31 # GNU General Public License for more details.
32 #
32 #
33 # You should have received a copy of the GNU Affero General Public License
33 # You should have received a copy of the GNU Affero General Public License
34 # along with this program. If not, see <http://www.gnu.org/licenses/>.
34 # along with this program. If not, see <http://www.gnu.org/licenses/>.
35 #
35 #
36 # This program is dual-licensed. If you wish to learn more about the
36 # This program is dual-licensed. If you wish to learn more about the
37 # RhodeCode Enterprise Edition, including its added features, Support services,
37 # RhodeCode Enterprise Edition, including its added features, Support services,
38 # and proprietary license terms, please see https://rhodecode.com/licenses/
38 # and proprietary license terms, please see https://rhodecode.com/licenses/
39
39
40 import pytest
40 import pytest
41
41
42 from rhodecode.model.db import User
42 from rhodecode.model.db import User
43 from rhodecode.tests import TestController, assert_session_flash
43 from rhodecode.tests import TestController, assert_session_flash
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45
45
46
46
47 def route_path(name, params=None, **kwargs):
47 def route_path(name, params=None, **kwargs):
48 import urllib
48 import urllib
49 from rhodecode.apps._base import ADMIN_PREFIX
49 from rhodecode.apps._base import ADMIN_PREFIX
50
50
51 base_url = {
51 base_url = {
52 'my_account_edit': ADMIN_PREFIX + '/my_account/edit',
52 'my_account_edit': ADMIN_PREFIX + '/my_account/edit',
53 'my_account_update': ADMIN_PREFIX + '/my_account/update',
53 'my_account_update': ADMIN_PREFIX + '/my_account/update',
54 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests',
54 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests',
55 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data',
55 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data',
56 }[name].format(**kwargs)
56 }[name].format(**kwargs)
57
57
58 if params:
58 if params:
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 return base_url
60 return base_url
61
61
62
62
63 class TestMyAccountEdit(TestController):
63 class TestMyAccountEdit(TestController):
64
64
65 def test_my_account_edit(self):
65 def test_my_account_edit(self):
66 self.log_user()
66 self.log_user()
67 response = self.app.get(route_path('my_account_edit'))
67 response = self.app.get(route_path('my_account_edit'))
68
68
69 response.mustcontain('value="test_admin')
69 response.mustcontain('value="test_admin')
70
70
71 @pytest.mark.backends("git", "hg")
71 @pytest.mark.backends("git", "hg")
72 def test_my_account_my_pullrequests(self, pr_util):
72 def test_my_account_my_pullrequests(self, pr_util):
73 self.log_user()
73 self.log_user()
74 response = self.app.get(route_path('my_account_pullrequests'))
74 response = self.app.get(route_path('my_account_pullrequests'))
75 response.mustcontain('There are currently no open pull '
75 response.mustcontain('There are currently no open pull '
76 'requests requiring your participation.')
76 'requests requiring your participation.')
77
77
78 @pytest.mark.backends("git", "hg")
78 @pytest.mark.backends("git", "hg")
79 def test_my_account_my_pullrequests_data(self, pr_util, xhr_header):
79 @pytest.mark.parametrize('params, expected_title', [
80 ({'closed': 1}, 'Closed'),
81 ({'awaiting_my_review': 1}, 'Awaiting my review'),
82 ])
83 def test_my_account_my_pullrequests_data(self, pr_util, xhr_header, params, expected_title):
80 self.log_user()
84 self.log_user()
81 response = self.app.get(route_path('my_account_pullrequests_data'),
85 response = self.app.get(route_path('my_account_pullrequests_data'),
82 extra_environ=xhr_header)
86 extra_environ=xhr_header)
83 assert response.json == {
87 assert response.json == {
84 u'data': [], u'draw': None,
88 u'data': [], u'draw': None,
85 u'recordsFiltered': 0, u'recordsTotal': 0}
89 u'recordsFiltered': 0, u'recordsTotal': 0}
86
90
87 pr = pr_util.create_pull_request(title='TestMyAccountPR')
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
88 expected = {
92 expected = {
89 'author_raw': 'RhodeCode Admin',
93 'author_raw': 'RhodeCode Admin',
90 'name_raw': pr.pull_request_id
94 'name_raw': pr.pull_request_id
91 }
95 }
92 response = self.app.get(route_path('my_account_pullrequests_data'),
96 response = self.app.get(route_path('my_account_pullrequests_data'),
93 extra_environ=xhr_header)
97 extra_environ=xhr_header)
94 assert response.json['recordsTotal'] == 1
98 assert response.json['recordsTotal'] == 1
95 assert response.json['data'][0]['author_raw'] == expected['author_raw']
99 assert response.json['data'][0]['author_raw'] == expected['author_raw']
96
100
97 assert response.json['data'][0]['author_raw'] == expected['author_raw']
101 assert response.json['data'][0]['author_raw'] == expected['author_raw']
98 assert response.json['data'][0]['name_raw'] == expected['name_raw']
102 assert response.json['data'][0]['name_raw'] == expected['name_raw']
99
103
100 @pytest.mark.parametrize(
104 @pytest.mark.parametrize(
101 "name, attrs", [
105 "name, attrs", [
102 ('firstname', {'firstname': 'new_username'}),
106 ('firstname', {'firstname': 'new_username'}),
103 ('lastname', {'lastname': 'new_username'}),
107 ('lastname', {'lastname': 'new_username'}),
104 ('admin', {'admin': True}),
108 ('admin', {'admin': True}),
105 ('admin', {'admin': False}),
109 ('admin', {'admin': False}),
106 ('extern_type', {'extern_type': 'ldap'}),
110 ('extern_type', {'extern_type': 'ldap'}),
107 ('extern_type', {'extern_type': None}),
111 ('extern_type', {'extern_type': None}),
108 # ('extern_name', {'extern_name': 'test'}),
112 # ('extern_name', {'extern_name': 'test'}),
109 # ('extern_name', {'extern_name': None}),
113 # ('extern_name', {'extern_name': None}),
110 ('active', {'active': False}),
114 ('active', {'active': False}),
111 ('active', {'active': True}),
115 ('active', {'active': True}),
112 ('email', {'email': u'some@email.com'}),
116 ('email', {'email': u'some@email.com'}),
113 ])
117 ])
114 def test_my_account_update(self, name, attrs, user_util):
118 def test_my_account_update(self, name, attrs, user_util):
115 usr = user_util.create_user(password='qweqwe')
119 usr = user_util.create_user(password='qweqwe')
116 params = usr.get_api_data() # current user data
120 params = usr.get_api_data() # current user data
117 user_id = usr.user_id
121 user_id = usr.user_id
118 self.log_user(
122 self.log_user(
119 username=usr.username, password='qweqwe')
123 username=usr.username, password='qweqwe')
120
124
121 params.update({'password_confirmation': ''})
125 params.update({'password_confirmation': ''})
122 params.update({'new_password': ''})
126 params.update({'new_password': ''})
123 params.update({'extern_type': u'rhodecode'})
127 params.update({'extern_type': u'rhodecode'})
124 params.update({'extern_name': u'rhodecode'})
128 params.update({'extern_name': u'rhodecode'})
125 params.update({'csrf_token': self.csrf_token})
129 params.update({'csrf_token': self.csrf_token})
126
130
127 params.update(attrs)
131 params.update(attrs)
128 # my account page cannot set language param yet, only for admins
132 # my account page cannot set language param yet, only for admins
129 del params['language']
133 del params['language']
130 if name == 'email':
134 if name == 'email':
131 uem = user_util.create_additional_user_email(usr, attrs['email'])
135 uem = user_util.create_additional_user_email(usr, attrs['email'])
132 email_before = User.get(user_id).email
136 email_before = User.get(user_id).email
133
137
134 response = self.app.post(route_path('my_account_update'), params)
138 response = self.app.post(route_path('my_account_update'), params)
135
139
136 assert_session_flash(
140 assert_session_flash(
137 response, 'Your account was updated successfully')
141 response, 'Your account was updated successfully')
138
142
139 del params['csrf_token']
143 del params['csrf_token']
140
144
141 updated_user = User.get(user_id)
145 updated_user = User.get(user_id)
142 updated_params = updated_user.get_api_data()
146 updated_params = updated_user.get_api_data()
143 updated_params.update({'password_confirmation': ''})
147 updated_params.update({'password_confirmation': ''})
144 updated_params.update({'new_password': ''})
148 updated_params.update({'new_password': ''})
145
149
146 params['last_login'] = updated_params['last_login']
150 params['last_login'] = updated_params['last_login']
147 params['last_activity'] = updated_params['last_activity']
151 params['last_activity'] = updated_params['last_activity']
148 # my account page cannot set language param yet, only for admins
152 # my account page cannot set language param yet, only for admins
149 # but we get this info from API anyway
153 # but we get this info from API anyway
150 params['language'] = updated_params['language']
154 params['language'] = updated_params['language']
151
155
152 if name == 'email':
156 if name == 'email':
153 params['emails'] = [attrs['email'], email_before]
157 params['emails'] = [attrs['email'], email_before]
154 if name == 'extern_type':
158 if name == 'extern_type':
155 # cannot update this via form, expected value is original one
159 # cannot update this via form, expected value is original one
156 params['extern_type'] = "rhodecode"
160 params['extern_type'] = "rhodecode"
157 if name == 'extern_name':
161 if name == 'extern_name':
158 # cannot update this via form, expected value is original one
162 # cannot update this via form, expected value is original one
159 params['extern_name'] = str(user_id)
163 params['extern_name'] = str(user_id)
160 if name == 'active':
164 if name == 'active':
161 # my account cannot deactivate account
165 # my account cannot deactivate account
162 params['active'] = True
166 params['active'] = True
163 if name == 'admin':
167 if name == 'admin':
164 # my account cannot make you an admin !
168 # my account cannot make you an admin !
165 params['admin'] = False
169 params['admin'] = False
166
170
167 assert params == updated_params
171 assert params == updated_params
168
172
169 def test_my_account_update_err_email_not_exists_in_emails(self):
173 def test_my_account_update_err_email_not_exists_in_emails(self):
170 self.log_user()
174 self.log_user()
171
175
172 new_email = 'test_regular@mail.com' # not in emails
176 new_email = 'test_regular@mail.com' # not in emails
173 params = {
177 params = {
174 'username': 'test_admin',
178 'username': 'test_admin',
175 'new_password': 'test12',
179 'new_password': 'test12',
176 'password_confirmation': 'test122',
180 'password_confirmation': 'test122',
177 'firstname': 'NewName',
181 'firstname': 'NewName',
178 'lastname': 'NewLastname',
182 'lastname': 'NewLastname',
179 'email': new_email,
183 'email': new_email,
180 'csrf_token': self.csrf_token,
184 'csrf_token': self.csrf_token,
181 }
185 }
182
186
183 response = self.app.post(route_path('my_account_update'),
187 response = self.app.post(route_path('my_account_update'),
184 params=params)
188 params=params)
185
189
186 response.mustcontain('"test_regular@mail.com" is not one of test_admin@mail.com')
190 response.mustcontain('"test_regular@mail.com" is not one of test_admin@mail.com')
187
191
188 def test_my_account_update_bad_email_address(self):
192 def test_my_account_update_bad_email_address(self):
189 self.log_user('test_regular2', 'test12')
193 self.log_user('test_regular2', 'test12')
190
194
191 new_email = 'newmail.pl'
195 new_email = 'newmail.pl'
192 params = {
196 params = {
193 'username': 'test_admin',
197 'username': 'test_admin',
194 'new_password': 'test12',
198 'new_password': 'test12',
195 'password_confirmation': 'test122',
199 'password_confirmation': 'test122',
196 'firstname': 'NewName',
200 'firstname': 'NewName',
197 'lastname': 'NewLastname',
201 'lastname': 'NewLastname',
198 'email': new_email,
202 'email': new_email,
199 'csrf_token': self.csrf_token,
203 'csrf_token': self.csrf_token,
200 }
204 }
201 response = self.app.post(route_path('my_account_update'),
205 response = self.app.post(route_path('my_account_update'),
202 params=params)
206 params=params)
203
207
204 response.mustcontain('"newmail.pl" is not one of test_regular2@mail.com')
208 response.mustcontain('"newmail.pl" is not one of test_regular2@mail.com')
@@ -1,752 +1,783
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 datetime
22 import datetime
23 import string
23 import string
24
24
25 import formencode
25 import formencode
26 import formencode.htmlfill
26 import formencode.htmlfill
27 import peppercorn
27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode import forms
31 from rhodecode import forms
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, NotAnonymous, CSRFRequired,
36 LoginRequired, NotAnonymous, CSRFRequired,
37 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
37 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
38 from rhodecode.lib.channelstream import (
38 from rhodecode.lib.channelstream import (
39 channelstream_request, ChannelstreamException)
39 channelstream_request, ChannelstreamException)
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 IntegrityError, or_, in_filter_generator,
44 IntegrityError, or_, in_filter_generator,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 PullRequest, UserBookmark, RepoGroup)
46 PullRequest, UserBookmark, RepoGroup, ChangesetStatus)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.user_group import UserGroupModel
50 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.validation_schema.schemas import user_schema
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class MyAccountView(BaseAppView, DataGridAppView):
56 class MyAccountView(BaseAppView, DataGridAppView):
57 ALLOW_SCOPED_TOKENS = False
57 ALLOW_SCOPED_TOKENS = False
58 """
58 """
59 This view has alternative version inside EE, if modified please take a look
59 This view has alternative version inside EE, if modified please take a look
60 in there as well.
60 in there as well.
61 """
61 """
62
62
63 def load_default_context(self):
63 def load_default_context(self):
64 c = self._get_local_tmpl_context()
64 c = self._get_local_tmpl_context()
65 c.user = c.auth_user.get_instance()
65 c.user = c.auth_user.get_instance()
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 return c
67 return c
68
68
69 @LoginRequired()
69 @LoginRequired()
70 @NotAnonymous()
70 @NotAnonymous()
71 def my_account_profile(self):
71 def my_account_profile(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 c.active = 'profile'
73 c.active = 'profile'
74 c.extern_type = c.user.extern_type
74 c.extern_type = c.user.extern_type
75 return self._get_template_context(c)
75 return self._get_template_context(c)
76
76
77 @LoginRequired()
77 @LoginRequired()
78 @NotAnonymous()
78 @NotAnonymous()
79 def my_account_edit(self):
79 def my_account_edit(self):
80 c = self.load_default_context()
80 c = self.load_default_context()
81 c.active = 'profile_edit'
81 c.active = 'profile_edit'
82 c.extern_type = c.user.extern_type
82 c.extern_type = c.user.extern_type
83 c.extern_name = c.user.extern_name
83 c.extern_name = c.user.extern_name
84
84
85 schema = user_schema.UserProfileSchema().bind(
85 schema = user_schema.UserProfileSchema().bind(
86 username=c.user.username, user_emails=c.user.emails)
86 username=c.user.username, user_emails=c.user.emails)
87 appstruct = {
87 appstruct = {
88 'username': c.user.username,
88 'username': c.user.username,
89 'email': c.user.email,
89 'email': c.user.email,
90 'firstname': c.user.firstname,
90 'firstname': c.user.firstname,
91 'lastname': c.user.lastname,
91 'lastname': c.user.lastname,
92 'description': c.user.description,
92 'description': c.user.description,
93 }
93 }
94 c.form = forms.RcForm(
94 c.form = forms.RcForm(
95 schema, appstruct=appstruct,
95 schema, appstruct=appstruct,
96 action=h.route_path('my_account_update'),
96 action=h.route_path('my_account_update'),
97 buttons=(forms.buttons.save, forms.buttons.reset))
97 buttons=(forms.buttons.save, forms.buttons.reset))
98
98
99 return self._get_template_context(c)
99 return self._get_template_context(c)
100
100
101 @LoginRequired()
101 @LoginRequired()
102 @NotAnonymous()
102 @NotAnonymous()
103 @CSRFRequired()
103 @CSRFRequired()
104 def my_account_update(self):
104 def my_account_update(self):
105 _ = self.request.translate
105 _ = self.request.translate
106 c = self.load_default_context()
106 c = self.load_default_context()
107 c.active = 'profile_edit'
107 c.active = 'profile_edit'
108 c.perm_user = c.auth_user
108 c.perm_user = c.auth_user
109 c.extern_type = c.user.extern_type
109 c.extern_type = c.user.extern_type
110 c.extern_name = c.user.extern_name
110 c.extern_name = c.user.extern_name
111
111
112 schema = user_schema.UserProfileSchema().bind(
112 schema = user_schema.UserProfileSchema().bind(
113 username=c.user.username, user_emails=c.user.emails)
113 username=c.user.username, user_emails=c.user.emails)
114 form = forms.RcForm(
114 form = forms.RcForm(
115 schema, buttons=(forms.buttons.save, forms.buttons.reset))
115 schema, buttons=(forms.buttons.save, forms.buttons.reset))
116
116
117 controls = self.request.POST.items()
117 controls = self.request.POST.items()
118 try:
118 try:
119 valid_data = form.validate(controls)
119 valid_data = form.validate(controls)
120 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
120 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
121 'new_password', 'password_confirmation']
121 'new_password', 'password_confirmation']
122 if c.extern_type != "rhodecode":
122 if c.extern_type != "rhodecode":
123 # forbid updating username for external accounts
123 # forbid updating username for external accounts
124 skip_attrs.append('username')
124 skip_attrs.append('username')
125 old_email = c.user.email
125 old_email = c.user.email
126 UserModel().update_user(
126 UserModel().update_user(
127 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
127 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
128 **valid_data)
128 **valid_data)
129 if old_email != valid_data['email']:
129 if old_email != valid_data['email']:
130 old = UserEmailMap.query() \
130 old = UserEmailMap.query() \
131 .filter(UserEmailMap.user == c.user)\
131 .filter(UserEmailMap.user == c.user)\
132 .filter(UserEmailMap.email == valid_data['email'])\
132 .filter(UserEmailMap.email == valid_data['email'])\
133 .first()
133 .first()
134 old.email = old_email
134 old.email = old_email
135 h.flash(_('Your account was updated successfully'), category='success')
135 h.flash(_('Your account was updated successfully'), category='success')
136 Session().commit()
136 Session().commit()
137 except forms.ValidationFailure as e:
137 except forms.ValidationFailure as e:
138 c.form = e
138 c.form = e
139 return self._get_template_context(c)
139 return self._get_template_context(c)
140 except Exception:
140 except Exception:
141 log.exception("Exception updating user")
141 log.exception("Exception updating user")
142 h.flash(_('Error occurred during update of user'),
142 h.flash(_('Error occurred during update of user'),
143 category='error')
143 category='error')
144 raise HTTPFound(h.route_path('my_account_profile'))
144 raise HTTPFound(h.route_path('my_account_profile'))
145
145
146 @LoginRequired()
146 @LoginRequired()
147 @NotAnonymous()
147 @NotAnonymous()
148 def my_account_password(self):
148 def my_account_password(self):
149 c = self.load_default_context()
149 c = self.load_default_context()
150 c.active = 'password'
150 c.active = 'password'
151 c.extern_type = c.user.extern_type
151 c.extern_type = c.user.extern_type
152
152
153 schema = user_schema.ChangePasswordSchema().bind(
153 schema = user_schema.ChangePasswordSchema().bind(
154 username=c.user.username)
154 username=c.user.username)
155
155
156 form = forms.Form(
156 form = forms.Form(
157 schema,
157 schema,
158 action=h.route_path('my_account_password_update'),
158 action=h.route_path('my_account_password_update'),
159 buttons=(forms.buttons.save, forms.buttons.reset))
159 buttons=(forms.buttons.save, forms.buttons.reset))
160
160
161 c.form = form
161 c.form = form
162 return self._get_template_context(c)
162 return self._get_template_context(c)
163
163
164 @LoginRequired()
164 @LoginRequired()
165 @NotAnonymous()
165 @NotAnonymous()
166 @CSRFRequired()
166 @CSRFRequired()
167 def my_account_password_update(self):
167 def my_account_password_update(self):
168 _ = self.request.translate
168 _ = self.request.translate
169 c = self.load_default_context()
169 c = self.load_default_context()
170 c.active = 'password'
170 c.active = 'password'
171 c.extern_type = c.user.extern_type
171 c.extern_type = c.user.extern_type
172
172
173 schema = user_schema.ChangePasswordSchema().bind(
173 schema = user_schema.ChangePasswordSchema().bind(
174 username=c.user.username)
174 username=c.user.username)
175
175
176 form = forms.Form(
176 form = forms.Form(
177 schema, buttons=(forms.buttons.save, forms.buttons.reset))
177 schema, buttons=(forms.buttons.save, forms.buttons.reset))
178
178
179 if c.extern_type != 'rhodecode':
179 if c.extern_type != 'rhodecode':
180 raise HTTPFound(self.request.route_path('my_account_password'))
180 raise HTTPFound(self.request.route_path('my_account_password'))
181
181
182 controls = self.request.POST.items()
182 controls = self.request.POST.items()
183 try:
183 try:
184 valid_data = form.validate(controls)
184 valid_data = form.validate(controls)
185 UserModel().update_user(c.user.user_id, **valid_data)
185 UserModel().update_user(c.user.user_id, **valid_data)
186 c.user.update_userdata(force_password_change=False)
186 c.user.update_userdata(force_password_change=False)
187 Session().commit()
187 Session().commit()
188 except forms.ValidationFailure as e:
188 except forms.ValidationFailure as e:
189 c.form = e
189 c.form = e
190 return self._get_template_context(c)
190 return self._get_template_context(c)
191
191
192 except Exception:
192 except Exception:
193 log.exception("Exception updating password")
193 log.exception("Exception updating password")
194 h.flash(_('Error occurred during update of user password'),
194 h.flash(_('Error occurred during update of user password'),
195 category='error')
195 category='error')
196 else:
196 else:
197 instance = c.auth_user.get_instance()
197 instance = c.auth_user.get_instance()
198 self.session.setdefault('rhodecode_user', {}).update(
198 self.session.setdefault('rhodecode_user', {}).update(
199 {'password': md5(instance.password)})
199 {'password': md5(instance.password)})
200 self.session.save()
200 self.session.save()
201 h.flash(_("Successfully updated password"), category='success')
201 h.flash(_("Successfully updated password"), category='success')
202
202
203 raise HTTPFound(self.request.route_path('my_account_password'))
203 raise HTTPFound(self.request.route_path('my_account_password'))
204
204
205 @LoginRequired()
205 @LoginRequired()
206 @NotAnonymous()
206 @NotAnonymous()
207 def my_account_auth_tokens(self):
207 def my_account_auth_tokens(self):
208 _ = self.request.translate
208 _ = self.request.translate
209
209
210 c = self.load_default_context()
210 c = self.load_default_context()
211 c.active = 'auth_tokens'
211 c.active = 'auth_tokens'
212 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
212 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
213 c.role_values = [
213 c.role_values = [
214 (x, AuthTokenModel.cls._get_role_name(x))
214 (x, AuthTokenModel.cls._get_role_name(x))
215 for x in AuthTokenModel.cls.ROLES]
215 for x in AuthTokenModel.cls.ROLES]
216 c.role_options = [(c.role_values, _("Role"))]
216 c.role_options = [(c.role_values, _("Role"))]
217 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
217 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
218 c.user.user_id, show_expired=True)
218 c.user.user_id, show_expired=True)
219 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
219 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
220 return self._get_template_context(c)
220 return self._get_template_context(c)
221
221
222 @LoginRequired()
222 @LoginRequired()
223 @NotAnonymous()
223 @NotAnonymous()
224 @CSRFRequired()
224 @CSRFRequired()
225 def my_account_auth_tokens_view(self):
225 def my_account_auth_tokens_view(self):
226 _ = self.request.translate
226 _ = self.request.translate
227 c = self.load_default_context()
227 c = self.load_default_context()
228
228
229 auth_token_id = self.request.POST.get('auth_token_id')
229 auth_token_id = self.request.POST.get('auth_token_id')
230
230
231 if auth_token_id:
231 if auth_token_id:
232 token = UserApiKeys.get_or_404(auth_token_id)
232 token = UserApiKeys.get_or_404(auth_token_id)
233 if token.user.user_id != c.user.user_id:
233 if token.user.user_id != c.user.user_id:
234 raise HTTPNotFound()
234 raise HTTPNotFound()
235
235
236 return {
236 return {
237 'auth_token': token.api_key
237 'auth_token': token.api_key
238 }
238 }
239
239
240 def maybe_attach_token_scope(self, token):
240 def maybe_attach_token_scope(self, token):
241 # implemented in EE edition
241 # implemented in EE edition
242 pass
242 pass
243
243
244 @LoginRequired()
244 @LoginRequired()
245 @NotAnonymous()
245 @NotAnonymous()
246 @CSRFRequired()
246 @CSRFRequired()
247 def my_account_auth_tokens_add(self):
247 def my_account_auth_tokens_add(self):
248 _ = self.request.translate
248 _ = self.request.translate
249 c = self.load_default_context()
249 c = self.load_default_context()
250
250
251 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
251 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
252 description = self.request.POST.get('description')
252 description = self.request.POST.get('description')
253 role = self.request.POST.get('role')
253 role = self.request.POST.get('role')
254
254
255 token = UserModel().add_auth_token(
255 token = UserModel().add_auth_token(
256 user=c.user.user_id,
256 user=c.user.user_id,
257 lifetime_minutes=lifetime, role=role, description=description,
257 lifetime_minutes=lifetime, role=role, description=description,
258 scope_callback=self.maybe_attach_token_scope)
258 scope_callback=self.maybe_attach_token_scope)
259 token_data = token.get_api_data()
259 token_data = token.get_api_data()
260
260
261 audit_logger.store_web(
261 audit_logger.store_web(
262 'user.edit.token.add', action_data={
262 'user.edit.token.add', action_data={
263 'data': {'token': token_data, 'user': 'self'}},
263 'data': {'token': token_data, 'user': 'self'}},
264 user=self._rhodecode_user, )
264 user=self._rhodecode_user, )
265 Session().commit()
265 Session().commit()
266
266
267 h.flash(_("Auth token successfully created"), category='success')
267 h.flash(_("Auth token successfully created"), category='success')
268 return HTTPFound(h.route_path('my_account_auth_tokens'))
268 return HTTPFound(h.route_path('my_account_auth_tokens'))
269
269
270 @LoginRequired()
270 @LoginRequired()
271 @NotAnonymous()
271 @NotAnonymous()
272 @CSRFRequired()
272 @CSRFRequired()
273 def my_account_auth_tokens_delete(self):
273 def my_account_auth_tokens_delete(self):
274 _ = self.request.translate
274 _ = self.request.translate
275 c = self.load_default_context()
275 c = self.load_default_context()
276
276
277 del_auth_token = self.request.POST.get('del_auth_token')
277 del_auth_token = self.request.POST.get('del_auth_token')
278
278
279 if del_auth_token:
279 if del_auth_token:
280 token = UserApiKeys.get_or_404(del_auth_token)
280 token = UserApiKeys.get_or_404(del_auth_token)
281 token_data = token.get_api_data()
281 token_data = token.get_api_data()
282
282
283 AuthTokenModel().delete(del_auth_token, c.user.user_id)
283 AuthTokenModel().delete(del_auth_token, c.user.user_id)
284 audit_logger.store_web(
284 audit_logger.store_web(
285 'user.edit.token.delete', action_data={
285 'user.edit.token.delete', action_data={
286 'data': {'token': token_data, 'user': 'self'}},
286 'data': {'token': token_data, 'user': 'self'}},
287 user=self._rhodecode_user,)
287 user=self._rhodecode_user,)
288 Session().commit()
288 Session().commit()
289 h.flash(_("Auth token successfully deleted"), category='success')
289 h.flash(_("Auth token successfully deleted"), category='success')
290
290
291 return HTTPFound(h.route_path('my_account_auth_tokens'))
291 return HTTPFound(h.route_path('my_account_auth_tokens'))
292
292
293 @LoginRequired()
293 @LoginRequired()
294 @NotAnonymous()
294 @NotAnonymous()
295 def my_account_emails(self):
295 def my_account_emails(self):
296 _ = self.request.translate
296 _ = self.request.translate
297
297
298 c = self.load_default_context()
298 c = self.load_default_context()
299 c.active = 'emails'
299 c.active = 'emails'
300
300
301 c.user_email_map = UserEmailMap.query()\
301 c.user_email_map = UserEmailMap.query()\
302 .filter(UserEmailMap.user == c.user).all()
302 .filter(UserEmailMap.user == c.user).all()
303
303
304 schema = user_schema.AddEmailSchema().bind(
304 schema = user_schema.AddEmailSchema().bind(
305 username=c.user.username, user_emails=c.user.emails)
305 username=c.user.username, user_emails=c.user.emails)
306
306
307 form = forms.RcForm(schema,
307 form = forms.RcForm(schema,
308 action=h.route_path('my_account_emails_add'),
308 action=h.route_path('my_account_emails_add'),
309 buttons=(forms.buttons.save, forms.buttons.reset))
309 buttons=(forms.buttons.save, forms.buttons.reset))
310
310
311 c.form = form
311 c.form = form
312 return self._get_template_context(c)
312 return self._get_template_context(c)
313
313
314 @LoginRequired()
314 @LoginRequired()
315 @NotAnonymous()
315 @NotAnonymous()
316 @CSRFRequired()
316 @CSRFRequired()
317 def my_account_emails_add(self):
317 def my_account_emails_add(self):
318 _ = self.request.translate
318 _ = self.request.translate
319 c = self.load_default_context()
319 c = self.load_default_context()
320 c.active = 'emails'
320 c.active = 'emails'
321
321
322 schema = user_schema.AddEmailSchema().bind(
322 schema = user_schema.AddEmailSchema().bind(
323 username=c.user.username, user_emails=c.user.emails)
323 username=c.user.username, user_emails=c.user.emails)
324
324
325 form = forms.RcForm(
325 form = forms.RcForm(
326 schema, action=h.route_path('my_account_emails_add'),
326 schema, action=h.route_path('my_account_emails_add'),
327 buttons=(forms.buttons.save, forms.buttons.reset))
327 buttons=(forms.buttons.save, forms.buttons.reset))
328
328
329 controls = self.request.POST.items()
329 controls = self.request.POST.items()
330 try:
330 try:
331 valid_data = form.validate(controls)
331 valid_data = form.validate(controls)
332 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
332 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
333 audit_logger.store_web(
333 audit_logger.store_web(
334 'user.edit.email.add', action_data={
334 'user.edit.email.add', action_data={
335 'data': {'email': valid_data['email'], 'user': 'self'}},
335 'data': {'email': valid_data['email'], 'user': 'self'}},
336 user=self._rhodecode_user,)
336 user=self._rhodecode_user,)
337 Session().commit()
337 Session().commit()
338 except formencode.Invalid as error:
338 except formencode.Invalid as error:
339 h.flash(h.escape(error.error_dict['email']), category='error')
339 h.flash(h.escape(error.error_dict['email']), category='error')
340 except forms.ValidationFailure as e:
340 except forms.ValidationFailure as e:
341 c.user_email_map = UserEmailMap.query() \
341 c.user_email_map = UserEmailMap.query() \
342 .filter(UserEmailMap.user == c.user).all()
342 .filter(UserEmailMap.user == c.user).all()
343 c.form = e
343 c.form = e
344 return self._get_template_context(c)
344 return self._get_template_context(c)
345 except Exception:
345 except Exception:
346 log.exception("Exception adding email")
346 log.exception("Exception adding email")
347 h.flash(_('Error occurred during adding email'),
347 h.flash(_('Error occurred during adding email'),
348 category='error')
348 category='error')
349 else:
349 else:
350 h.flash(_("Successfully added email"), category='success')
350 h.flash(_("Successfully added email"), category='success')
351
351
352 raise HTTPFound(self.request.route_path('my_account_emails'))
352 raise HTTPFound(self.request.route_path('my_account_emails'))
353
353
354 @LoginRequired()
354 @LoginRequired()
355 @NotAnonymous()
355 @NotAnonymous()
356 @CSRFRequired()
356 @CSRFRequired()
357 def my_account_emails_delete(self):
357 def my_account_emails_delete(self):
358 _ = self.request.translate
358 _ = self.request.translate
359 c = self.load_default_context()
359 c = self.load_default_context()
360
360
361 del_email_id = self.request.POST.get('del_email_id')
361 del_email_id = self.request.POST.get('del_email_id')
362 if del_email_id:
362 if del_email_id:
363 email = UserEmailMap.get_or_404(del_email_id).email
363 email = UserEmailMap.get_or_404(del_email_id).email
364 UserModel().delete_extra_email(c.user.user_id, del_email_id)
364 UserModel().delete_extra_email(c.user.user_id, del_email_id)
365 audit_logger.store_web(
365 audit_logger.store_web(
366 'user.edit.email.delete', action_data={
366 'user.edit.email.delete', action_data={
367 'data': {'email': email, 'user': 'self'}},
367 'data': {'email': email, 'user': 'self'}},
368 user=self._rhodecode_user,)
368 user=self._rhodecode_user,)
369 Session().commit()
369 Session().commit()
370 h.flash(_("Email successfully deleted"),
370 h.flash(_("Email successfully deleted"),
371 category='success')
371 category='success')
372 return HTTPFound(h.route_path('my_account_emails'))
372 return HTTPFound(h.route_path('my_account_emails'))
373
373
374 @LoginRequired()
374 @LoginRequired()
375 @NotAnonymous()
375