##// END OF EJS Templates
users-admin: moved views into pyramid for editing emails and ips....
marcink -
r1821:a4cc42be default
parent child Browse files
Show More
@@ -1,115 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 23 from rhodecode.config.routing import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def admin_routes(config):
28 28 """
29 29 Admin prefixed routes
30 30 """
31 31
32 32 config.add_route(
33 33 name='admin_audit_logs',
34 34 pattern='/audit_logs')
35 35
36 36 config.add_route(
37 37 name='pull_requests_global_0', # backward compat
38 38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
39 39 config.add_route(
40 40 name='pull_requests_global_1', # backward compat
41 41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
42 42 config.add_route(
43 43 name='pull_requests_global',
44 44 pattern='/pull-request/{pull_request_id:[0-9]+}')
45 45
46 46 config.add_route(
47 47 name='admin_settings_open_source',
48 48 pattern='/settings/open_source')
49 49 config.add_route(
50 50 name='admin_settings_vcs_svn_generate_cfg',
51 51 pattern='/settings/vcs/svn_generate_cfg')
52 52
53 53 config.add_route(
54 54 name='admin_settings_system',
55 55 pattern='/settings/system')
56 56 config.add_route(
57 57 name='admin_settings_system_update',
58 58 pattern='/settings/system/updates')
59 59
60 60 config.add_route(
61 61 name='admin_settings_sessions',
62 62 pattern='/settings/sessions')
63 63 config.add_route(
64 64 name='admin_settings_sessions_cleanup',
65 65 pattern='/settings/sessions/cleanup')
66 66
67 # global permissions
68 config.add_route(
69 name='admin_permissions_ips',
70 pattern='/permissions/ips')
71
67 72 # users admin
68 73 config.add_route(
69 74 name='users',
70 75 pattern='/users')
71 76
72 77 config.add_route(
73 78 name='users_data',
74 79 pattern='/users_data')
75 80
76 81 # user auth tokens
77 82 config.add_route(
78 83 name='edit_user_auth_tokens',
79 84 pattern='/users/{user_id:\d+}/edit/auth_tokens')
80 85 config.add_route(
81 86 name='edit_user_auth_tokens_add',
82 87 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
83 88 config.add_route(
84 89 name='edit_user_auth_tokens_delete',
85 90 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
86 91
92 # user emails
93 config.add_route(
94 name='edit_user_emails',
95 pattern='/users/{user_id:\d+}/edit/emails')
96 config.add_route(
97 name='edit_user_emails_add',
98 pattern='/users/{user_id:\d+}/edit/emails/new')
99 config.add_route(
100 name='edit_user_emails_delete',
101 pattern='/users/{user_id:\d+}/edit/emails/delete')
102
103 # user IPs
104 config.add_route(
105 name='edit_user_ips',
106 pattern='/users/{user_id:\d+}/edit/ips')
107 config.add_route(
108 name='edit_user_ips_add',
109 pattern='/users/{user_id:\d+}/edit/ips/new')
110 config.add_route(
111 name='edit_user_ips_delete',
112 pattern='/users/{user_id:\d+}/edit/ips/delete')
113
87 114 # user groups management
88 115 config.add_route(
89 116 name='edit_user_groups_management',
90 117 pattern='/users/{user_id:\d+}/edit/groups_management')
91 118
92 119 config.add_route(
93 120 name='edit_user_groups_management_updates',
94 121 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
95 122
96 123 # user audit logs
97 124 config.add_route(
98 125 name='edit_user_audit_logs',
99 126 pattern='/users/{user_id:\d+}/edit/audit')
100 127
101 128
102 129 def includeme(config):
103 130 settings = config.get_settings()
104 131
105 132 # Create admin navigation registry and add it to the pyramid registry.
106 133 labs_active = str2bool(settings.get('labs_settings_active', False))
107 134 navigation_registry = NavigationRegistry(labs_active=labs_active)
108 135 config.registry.registerUtility(navigation_registry)
109 136
110 137 # main admin routes
111 138 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
112 139 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
113 140
114 141 # Scan module for configuration decorators.
115 142 config.scan()
@@ -1,142 +1,281 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.model.db import User, UserApiKeys
23 from rhodecode.model.db import User, UserApiKeys, UserEmailMap
24 from rhodecode.model.meta import Session
25 from rhodecode.model.user import UserModel
24 26
25 27 from rhodecode.tests import (
26 28 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
27 29 from rhodecode.tests.fixture import Fixture
28 30
29 31 fixture = Fixture()
30 32
31 33
32 34 def route_path(name, params=None, **kwargs):
33 35 import urllib
34 36 from rhodecode.apps._base import ADMIN_PREFIX
35 37
36 38 base_url = {
37 39 'users':
38 40 ADMIN_PREFIX + '/users',
39 41 'users_data':
40 42 ADMIN_PREFIX + '/users_data',
41 43 'edit_user_auth_tokens':
42 44 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
43 45 'edit_user_auth_tokens_add':
44 46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
45 47 'edit_user_auth_tokens_delete':
46 48 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
49
50 'edit_user_emails':
51 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
52 'edit_user_emails_add':
53 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
54 'edit_user_emails_delete':
55 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
56
57 'edit_user_ips':
58 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
59 'edit_user_ips_add':
60 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
61 'edit_user_ips_delete':
62 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
47 63 }[name].format(**kwargs)
48 64
49 65 if params:
50 66 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
51 67 return base_url
52 68
53 69
54 70 class TestAdminUsersView(TestController):
55 71
56 72 def test_show_users(self):
57 73 self.log_user()
58 74 self.app.get(route_path('users'))
59 75
60 76 def test_show_users_data(self, xhr_header):
61 77 self.log_user()
62 78 response = self.app.get(route_path(
63 79 'users_data'), extra_environ=xhr_header)
64 80
65 81 all_users = User.query().filter(
66 82 User.username != User.DEFAULT_USER).count()
67 83 assert response.json['recordsTotal'] == all_users
68 84
69 85 def test_show_users_data_filtered(self, xhr_header):
70 86 self.log_user()
71 87 response = self.app.get(route_path(
72 88 'users_data', params={'search[value]': 'empty_search'}),
73 89 extra_environ=xhr_header)
74 90
75 91 all_users = User.query().filter(
76 92 User.username != User.DEFAULT_USER).count()
77 93 assert response.json['recordsTotal'] == all_users
78 94 assert response.json['recordsFiltered'] == 0
79 95
80 96 def test_auth_tokens_default_user(self):
81 97 self.log_user()
82 98 user = User.get_default_user()
83 99 response = self.app.get(
84 100 route_path('edit_user_auth_tokens', user_id=user.user_id),
85 101 status=302)
86 102
87 103 def test_auth_tokens(self):
88 104 self.log_user()
89 105
90 106 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
91 107 response = self.app.get(
92 108 route_path('edit_user_auth_tokens', user_id=user.user_id))
93 109 for token in user.auth_tokens:
94 110 response.mustcontain(token)
95 111 response.mustcontain('never')
96 112
97 113 @pytest.mark.parametrize("desc, lifetime", [
98 114 ('forever', -1),
99 115 ('5mins', 60*5),
100 116 ('30days', 60*60*24*30),
101 117 ])
102 118 def test_add_auth_token(self, desc, lifetime, user_util):
103 119 self.log_user()
104 120 user = user_util.create_user()
105 121 user_id = user.user_id
106 122
107 123 response = self.app.post(
108 124 route_path('edit_user_auth_tokens_add', user_id=user_id),
109 125 {'description': desc, 'lifetime': lifetime,
110 126 'csrf_token': self.csrf_token})
111 127 assert_session_flash(response, 'Auth token successfully created')
112 128
113 129 response = response.follow()
114 130 user = User.get(user_id)
115 131 for auth_token in user.auth_tokens:
116 132 response.mustcontain(auth_token)
117 133
118 134 def test_delete_auth_token(self, user_util):
119 135 self.log_user()
120 136 user = user_util.create_user()
121 137 user_id = user.user_id
122 138 keys = user.extra_auth_tokens
123 139 assert 2 == len(keys)
124 140
125 141 response = self.app.post(
126 142 route_path('edit_user_auth_tokens_add', user_id=user_id),
127 143 {'description': 'desc', 'lifetime': -1,
128 144 'csrf_token': self.csrf_token})
129 145 assert_session_flash(response, 'Auth token successfully created')
130 146 response.follow()
131 147
132 148 # now delete our key
133 149 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
134 150 assert 3 == len(keys)
135 151
136 152 response = self.app.post(
137 153 route_path('edit_user_auth_tokens_delete', user_id=user_id),
138 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
154 {'del_auth_token': keys[0].user_api_key_id,
155 'csrf_token': self.csrf_token})
139 156
140 157 assert_session_flash(response, 'Auth token successfully deleted')
141 158 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
142 159 assert 2 == len(keys)
160
161 def test_ips(self):
162 self.log_user()
163 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
164 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
165 response.mustcontain('All IP addresses are allowed')
166
167 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
168 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
169 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
170 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
171 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
172 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
173 ('127_bad_ip', 'foobar', 'foobar', True),
174 ])
175 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
176 self.log_user()
177 user = user_util.create_user(username=test_name)
178 user_id = user.user_id
179
180 response = self.app.post(
181 route_path('edit_user_ips_add', user_id=user_id),
182 params={'new_ip': ip, 'csrf_token': self.csrf_token})
183
184 if failure:
185 assert_session_flash(
186 response, 'Please enter a valid IPv4 or IpV6 address')
187 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
188
189 response.mustcontain(no=[ip])
190 response.mustcontain(no=[ip_range])
191
192 else:
193 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
194 response.mustcontain(ip)
195 response.mustcontain(ip_range)
196
197 def test_ips_delete(self, user_util):
198 self.log_user()
199 user = user_util.create_user()
200 user_id = user.user_id
201 ip = '127.0.0.1/32'
202 ip_range = '127.0.0.1 - 127.0.0.1'
203 new_ip = UserModel().add_extra_ip(user_id, ip)
204 Session().commit()
205 new_ip_id = new_ip.ip_id
206
207 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
208 response.mustcontain(ip)
209 response.mustcontain(ip_range)
210
211 self.app.post(
212 route_path('edit_user_ips_delete', user_id=user_id),
213 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
214
215 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
216 response.mustcontain('All IP addresses are allowed')
217 response.mustcontain(no=[ip])
218 response.mustcontain(no=[ip_range])
219
220 def test_emails(self):
221 self.log_user()
222 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
223 response = self.app.get(route_path('edit_user_emails', user_id=user.user_id))
224 response.mustcontain('No additional emails specified')
225
226 def test_emails_add(self, user_util):
227 self.log_user()
228 user = user_util.create_user()
229 user_id = user.user_id
230
231 self.app.post(
232 route_path('edit_user_emails_add', user_id=user_id),
233 params={'new_email': 'example@rhodecode.com',
234 'csrf_token': self.csrf_token})
235
236 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
237 response.mustcontain('example@rhodecode.com')
238
239 def test_emails_add_existing_email(self, user_util, user_regular):
240 existing_email = user_regular.email
241
242 self.log_user()
243 user = user_util.create_user()
244 user_id = user.user_id
245
246 response = self.app.post(
247 route_path('edit_user_emails_add', user_id=user_id),
248 params={'new_email': existing_email,
249 'csrf_token': self.csrf_token})
250 assert_session_flash(
251 response, 'This e-mail address is already taken')
252
253 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
254 response.mustcontain(no=[existing_email])
255
256 def test_emails_delete(self, user_util):
257 self.log_user()
258 user = user_util.create_user()
259 user_id = user.user_id
260
261 self.app.post(
262 route_path('edit_user_emails_add', user_id=user_id),
263 params={'new_email': 'example@rhodecode.com',
264 'csrf_token': self.csrf_token})
265
266 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
267 response.mustcontain('example@rhodecode.com')
268
269 user_email = UserEmailMap.query()\
270 .filter(UserEmailMap.email == 'example@rhodecode.com') \
271 .filter(UserEmailMap.user_id == user_id)\
272 .one()
273
274 del_email_id = user_email.email_id
275 self.app.post(
276 route_path('edit_user_emails_delete', user_id=user_id),
277 params={'del_email_id': del_email_id,
278 'csrf_token': self.csrf_token})
279
280 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
281 response.mustcontain(no=['example@rhodecode.com']) No newline at end of file
@@ -1,308 +1,515 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 import formencode
23 24
24 25 from pyramid.httpexceptions import HTTPFound
25 26 from pyramid.view import view_config
26 27 from sqlalchemy.sql.functions import coalesce
27 28
28 from rhodecode.lib.helpers import Page
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30
31 from rhodecode.lib import audit_logger
29 32 from rhodecode.lib.ext_json import json
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 33 from rhodecode.lib.auth import (
33 34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 35 from rhodecode.lib import helpers as h
35 36 from rhodecode.lib.utils import PartialRenderer
36 37 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 38 from rhodecode.model.auth_token import AuthTokenModel
38 39 from rhodecode.model.user import UserModel
39 40 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.db import User, or_
41 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
41 42 from rhodecode.model.meta import Session
42 43
43 44 log = logging.getLogger(__name__)
44 45
45 46
46 47 class AdminUsersView(BaseAppView, DataGridAppView):
47 48 ALLOW_SCOPED_TOKENS = False
48 49 """
49 50 This view has alternative version inside EE, if modified please take a look
50 51 in there as well.
51 52 """
52 53
53 54 def load_default_context(self):
54 55 c = self._get_local_tmpl_context()
55 56 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
56 57 self._register_global_c(c)
57 58 return c
58 59
59 60 def _redirect_for_default_user(self, username):
60 61 _ = self.request.translate
61 62 if username == User.DEFAULT_USER:
62 63 h.flash(_("You can't edit this user"), category='warning')
63 64 # TODO(marcink): redirect to 'users' admin panel once this
64 65 # is a pyramid view
65 66 raise HTTPFound('/')
66 67
67 68 @HasPermissionAllDecorator('hg.admin')
68 69 @view_config(
69 70 route_name='users', request_method='GET',
70 71 renderer='rhodecode:templates/admin/users/users.mako')
71 72 def users_list(self):
72 73 c = self.load_default_context()
73 74 return self._get_template_context(c)
74 75
75 76 @HasPermissionAllDecorator('hg.admin')
76 77 @view_config(
77 78 # renderer defined below
78 79 route_name='users_data', request_method='GET',
79 80 renderer='json_ext', xhr=True)
80 81 def users_list_data(self):
81 82 draw, start, limit = self._extract_chunk(self.request)
82 83 search_q, order_by, order_dir = self._extract_ordering(self.request)
83 84
84 85 _render = PartialRenderer('data_table/_dt_elements.mako')
85 86
86 87 def user_actions(user_id, username):
87 88 return _render("user_actions", user_id, username)
88 89
89 90 users_data_total_count = User.query()\
90 91 .filter(User.username != User.DEFAULT_USER) \
91 92 .count()
92 93
93 94 # json generate
94 95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
95 96
96 97 if search_q:
97 98 like_expression = u'%{}%'.format(safe_unicode(search_q))
98 99 base_q = base_q.filter(or_(
99 100 User.username.ilike(like_expression),
100 101 User._email.ilike(like_expression),
101 102 User.name.ilike(like_expression),
102 103 User.lastname.ilike(like_expression),
103 104 ))
104 105
105 106 users_data_total_filtered_count = base_q.count()
106 107
107 108 sort_col = getattr(User, order_by, None)
108 109 if sort_col:
109 110 if order_dir == 'asc':
110 111 # handle null values properly to order by NULL last
111 112 if order_by in ['last_activity']:
112 113 sort_col = coalesce(sort_col, datetime.date.max)
113 114 sort_col = sort_col.asc()
114 115 else:
115 116 # handle null values properly to order by NULL last
116 117 if order_by in ['last_activity']:
117 118 sort_col = coalesce(sort_col, datetime.date.min)
118 119 sort_col = sort_col.desc()
119 120
120 121 base_q = base_q.order_by(sort_col)
121 122 base_q = base_q.offset(start).limit(limit)
122 123
123 124 users_list = base_q.all()
124 125
125 126 users_data = []
126 127 for user in users_list:
127 128 users_data.append({
128 129 "username": h.gravatar_with_user(user.username),
129 130 "email": user.email,
130 131 "first_name": user.first_name,
131 132 "last_name": user.last_name,
132 133 "last_login": h.format_date(user.last_login),
133 134 "last_activity": h.format_date(user.last_activity),
134 135 "active": h.bool2icon(user.active),
135 136 "active_raw": user.active,
136 137 "admin": h.bool2icon(user.admin),
137 138 "extern_type": user.extern_type,
138 139 "extern_name": user.extern_name,
139 140 "action": user_actions(user.user_id, user.username),
140 141 })
141 142
142 143 data = ({
143 144 'draw': draw,
144 145 'data': users_data,
145 146 'recordsTotal': users_data_total_count,
146 147 'recordsFiltered': users_data_total_filtered_count,
147 148 })
148 149
149 150 return data
150 151
151 152 @LoginRequired()
152 153 @HasPermissionAllDecorator('hg.admin')
153 154 @view_config(
154 155 route_name='edit_user_auth_tokens', request_method='GET',
155 156 renderer='rhodecode:templates/admin/users/user_edit.mako')
156 157 def auth_tokens(self):
157 158 _ = self.request.translate
158 159 c = self.load_default_context()
159 160
160 161 user_id = self.request.matchdict.get('user_id')
161 162 c.user = User.get_or_404(user_id, pyramid_exc=True)
162 163 self._redirect_for_default_user(c.user.username)
163 164
164 165 c.active = 'auth_tokens'
165 166
166 167 c.lifetime_values = [
167 168 (str(-1), _('forever')),
168 169 (str(5), _('5 minutes')),
169 170 (str(60), _('1 hour')),
170 171 (str(60 * 24), _('1 day')),
171 172 (str(60 * 24 * 30), _('1 month')),
172 173 ]
173 174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
174 175 c.role_values = [
175 176 (x, AuthTokenModel.cls._get_role_name(x))
176 177 for x in AuthTokenModel.cls.ROLES]
177 178 c.role_options = [(c.role_values, _("Role"))]
178 179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
179 180 c.user.user_id, show_expired=True)
180 181 return self._get_template_context(c)
181 182
182 183 def maybe_attach_token_scope(self, token):
183 184 # implemented in EE edition
184 185 pass
185 186
186 187 @LoginRequired()
187 188 @HasPermissionAllDecorator('hg.admin')
188 189 @CSRFRequired()
189 190 @view_config(
190 191 route_name='edit_user_auth_tokens_add', request_method='POST')
191 192 def auth_tokens_add(self):
192 193 _ = self.request.translate
193 194 c = self.load_default_context()
194 195
195 196 user_id = self.request.matchdict.get('user_id')
196 197 c.user = User.get_or_404(user_id, pyramid_exc=True)
198
197 199 self._redirect_for_default_user(c.user.username)
198 200
201 user_data = c.user.get_api_data()
199 202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
200 203 description = self.request.POST.get('description')
201 204 role = self.request.POST.get('role')
202 205
203 206 token = AuthTokenModel().create(
204 207 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
209
205 210 self.maybe_attach_token_scope(token)
211 audit_logger.store(
212 action='user.edit.token.add',
213 action_data={'data': {'token': token_data, 'user': user_data}},
214 user=self._rhodecode_user, )
206 215 Session().commit()
207 216
208 217 h.flash(_("Auth token successfully created"), category='success')
209 218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
210 219
211 220 @LoginRequired()
212 221 @HasPermissionAllDecorator('hg.admin')
213 222 @CSRFRequired()
214 223 @view_config(
215 224 route_name='edit_user_auth_tokens_delete', request_method='POST')
216 225 def auth_tokens_delete(self):
217 226 _ = self.request.translate
218 227 c = self.load_default_context()
219 228
220 229 user_id = self.request.matchdict.get('user_id')
221 230 c.user = User.get_or_404(user_id, pyramid_exc=True)
222 231 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
223 233
224 234 del_auth_token = self.request.POST.get('del_auth_token')
225 235
226 236 if del_auth_token:
237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
238 token_data = token.get_api_data()
239
227 240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 audit_logger.store(
242 action='user.edit.token.delete',
243 action_data={'data': {'token': token_data, 'user': user_data}},
244 user=self._rhodecode_user,)
228 245 Session().commit()
229 246 h.flash(_("Auth token successfully deleted"), category='success')
230 247
231 248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
232 249
250
251
252
253
254
255 @LoginRequired()
256 @HasPermissionAllDecorator('hg.admin')
257 @view_config(
258 route_name='edit_user_emails', request_method='GET',
259 renderer='rhodecode:templates/admin/users/user_edit.mako')
260 def emails(self):
261 _ = self.request.translate
262 c = self.load_default_context()
263
264 user_id = self.request.matchdict.get('user_id')
265 c.user = User.get_or_404(user_id, pyramid_exc=True)
266 self._redirect_for_default_user(c.user.username)
267
268 c.active = 'emails'
269 c.user_email_map = UserEmailMap.query() \
270 .filter(UserEmailMap.user == c.user).all()
271
272 return self._get_template_context(c)
273
274 @LoginRequired()
275 @HasPermissionAllDecorator('hg.admin')
276 @CSRFRequired()
277 @view_config(
278 route_name='edit_user_emails_add', request_method='POST')
279 def emails_add(self):
280 _ = self.request.translate
281 c = self.load_default_context()
282
283 user_id = self.request.matchdict.get('user_id')
284 c.user = User.get_or_404(user_id, pyramid_exc=True)
285 self._redirect_for_default_user(c.user.username)
286
287 email = self.request.POST.get('new_email')
288 user_data = c.user.get_api_data()
289 try:
290 UserModel().add_extra_email(c.user.user_id, email)
291 audit_logger.store_web(
292 'user.edit.email.add',
293 action_data={'email': email, 'user': user_data},
294 user=self._rhodecode_user)
295 Session().commit()
296 h.flash(_("Added new email address `%s` for user account") % email,
297 category='success')
298 except formencode.Invalid as error:
299 msg = error.error_dict['email']
300 h.flash(msg, category='error')
301 except Exception:
302 log.exception("Exception during email saving")
303 h.flash(_('An error occurred during email saving'),
304 category='error')
305 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
306
307 @LoginRequired()
308 @HasPermissionAllDecorator('hg.admin')
309 @CSRFRequired()
310 @view_config(
311 route_name='edit_user_emails_delete', request_method='POST')
312 def emails_delete(self):
313 _ = self.request.translate
314 c = self.load_default_context()
315
316 user_id = self.request.matchdict.get('user_id')
317 c.user = User.get_or_404(user_id, pyramid_exc=True)
318 self._redirect_for_default_user(c.user.username)
319
320 email_id = self.request.POST.get('del_email_id')
321 user_model = UserModel()
322
323 email = UserEmailMap.query().get(email_id).email
324 user_data = c.user.get_api_data()
325 user_model.delete_extra_email(c.user.user_id, email_id)
326 audit_logger.store_web(
327 'user.edit.email.delete',
328 action_data={'email': email, 'user': user_data},
329 user=self._rhodecode_user)
330 Session().commit()
331 h.flash(_("Removed email address from user account"),
332 category='success')
333 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
334
335 @LoginRequired()
336 @HasPermissionAllDecorator('hg.admin')
337 @view_config(
338 route_name='edit_user_ips', request_method='GET',
339 renderer='rhodecode:templates/admin/users/user_edit.mako')
340 def ips(self):
341 _ = self.request.translate
342 c = self.load_default_context()
343
344 user_id = self.request.matchdict.get('user_id')
345 c.user = User.get_or_404(user_id, pyramid_exc=True)
346 self._redirect_for_default_user(c.user.username)
347
348 c.active = 'ips'
349 c.user_ip_map = UserIpMap.query() \
350 .filter(UserIpMap.user == c.user).all()
351
352 c.inherit_default_ips = c.user.inherit_default_permissions
353 c.default_user_ip_map = UserIpMap.query() \
354 .filter(UserIpMap.user == User.get_default_user()).all()
355
356 return self._get_template_context(c)
357
358 @LoginRequired()
359 @HasPermissionAllDecorator('hg.admin')
360 @CSRFRequired()
361 @view_config(
362 route_name='edit_user_ips_add', request_method='POST')
363 def ips_add(self):
364 _ = self.request.translate
365 c = self.load_default_context()
366
367 user_id = self.request.matchdict.get('user_id')
368 c.user = User.get_or_404(user_id, pyramid_exc=True)
369 # NOTE(marcink): this view is allowed for default users, as we can
370 # edit their IP white list
371
372 user_model = UserModel()
373 desc = self.request.POST.get('description')
374 try:
375 ip_list = user_model.parse_ip_range(
376 self.request.POST.get('new_ip'))
377 except Exception as e:
378 ip_list = []
379 log.exception("Exception during ip saving")
380 h.flash(_('An error occurred during ip saving:%s' % (e,)),
381 category='error')
382 added = []
383 user_data = c.user.get_api_data()
384 for ip in ip_list:
385 try:
386 user_model.add_extra_ip(c.user.user_id, ip, desc)
387 audit_logger.store_web(
388 'user.edit.ip.add',
389 action_data={'ip': ip, 'user': user_data},
390 user=self._rhodecode_user)
391 Session().commit()
392 added.append(ip)
393 except formencode.Invalid as error:
394 msg = error.error_dict['ip']
395 h.flash(msg, category='error')
396 except Exception:
397 log.exception("Exception during ip saving")
398 h.flash(_('An error occurred during ip saving'),
399 category='error')
400 if added:
401 h.flash(
402 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
403 category='success')
404 if 'default_user' in self.request.POST:
405 # case for editing global IP list we do it for 'DEFAULT' user
406 raise HTTPFound(h.route_path('admin_permissions_ips'))
407 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
408
409 @LoginRequired()
410 @HasPermissionAllDecorator('hg.admin')
411 @CSRFRequired()
412 @view_config(
413 route_name='edit_user_ips_delete', request_method='POST')
414 def ips_delete(self):
415 _ = self.request.translate
416 c = self.load_default_context()
417
418 user_id = self.request.matchdict.get('user_id')
419 c.user = User.get_or_404(user_id, pyramid_exc=True)
420 # NOTE(marcink): this view is allowed for default users, as we can
421 # edit their IP white list
422
423 ip_id = self.request.POST.get('del_ip_id')
424 user_model = UserModel()
425 user_data = c.user.get_api_data()
426 ip = UserIpMap.query().get(ip_id).ip_addr
427 user_model.delete_extra_ip(c.user.user_id, ip_id)
428 audit_logger.store_web(
429 'user.edit.ip.delete',
430 action_data={'ip': ip, 'user': user_data},
431 user=self._rhodecode_user)
432 Session().commit()
433 h.flash(_("Removed ip address from user whitelist"), category='success')
434
435 if 'default_user' in self.request.POST:
436 # case for editing global IP list we do it for 'DEFAULT' user
437 raise HTTPFound(h.route_path('admin_permissions_ips'))
438 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
439
233 440 @LoginRequired()
234 441 @HasPermissionAllDecorator('hg.admin')
235 442 @view_config(
236 443 route_name='edit_user_groups_management', request_method='GET',
237 444 renderer='rhodecode:templates/admin/users/user_edit.mako')
238 445 def groups_management(self):
239 446 c = self.load_default_context()
240 447
241 448 user_id = self.request.matchdict.get('user_id')
242 449 c.user = User.get_or_404(user_id, pyramid_exc=True)
243 450 c.data = c.user.group_member
244 451 self._redirect_for_default_user(c.user.username)
245 452 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
246 453 for group in c.user.group_member]
247 454 c.groups = json.dumps(groups)
248 455 c.active = 'groups'
249 456
250 457 return self._get_template_context(c)
251 458
252 459 @LoginRequired()
253 460 @HasPermissionAllDecorator('hg.admin')
254 461 @CSRFRequired()
255 462 @view_config(
256 463 route_name='edit_user_groups_management_updates', request_method='POST')
257 464 def groups_management_updates(self):
258 465 _ = self.request.translate
259 466 c = self.load_default_context()
260 467
261 468 user_id = self.request.matchdict.get('user_id')
262 469 c.user = User.get_or_404(user_id, pyramid_exc=True)
263 470 self._redirect_for_default_user(c.user.username)
264 471
265 472 users_groups = set(self.request.POST.getall('users_group_id'))
266 473 users_groups_model = []
267 474
268 475 for ugid in users_groups:
269 476 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
270 477 user_group_model = UserGroupModel()
271 478 user_group_model.change_groups(c.user, users_groups_model)
272 479
273 480 Session().commit()
274 481 c.active = 'user_groups_management'
275 482 h.flash(_("Groups successfully changed"), category='success')
276 483
277 484 return HTTPFound(h.route_path(
278 485 'edit_user_groups_management', user_id=user_id))
279 486
280 487 @LoginRequired()
281 488 @HasPermissionAllDecorator('hg.admin')
282 489 @view_config(
283 490 route_name='edit_user_audit_logs', request_method='GET',
284 491 renderer='rhodecode:templates/admin/users/user_edit.mako')
285 492 def user_audit_logs(self):
286 493 _ = self.request.translate
287 494 c = self.load_default_context()
288 495
289 496 user_id = self.request.matchdict.get('user_id')
290 497 c.user = User.get_or_404(user_id, pyramid_exc=True)
291 498 self._redirect_for_default_user(c.user.username)
292 499 c.active = 'audit'
293 500
294 501 p = safe_int(self.request.GET.get('page', 1), 1)
295 502
296 503 filter_term = self.request.GET.get('filter')
297 504 user_log = UserModel().get_user_log(c.user, filter_term)
298 505
299 506 def url_generator(**kw):
300 507 if filter_term:
301 508 kw['filter'] = filter_term
302 509 return self.request.current_route_path(_query=kw)
303 510
304 c.audit_logs = Page(user_log, page=p, items_per_page=10,
305 url=url_generator)
511 c.audit_logs = h.Page(
512 user_log, page=p, items_per_page=10, url=url_generator)
306 513 c.filter_term = filter_term
307 514 return self._get_template_context(c)
308 515
@@ -1,958 +1,945 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_requirements(route_path, requirements):
55 55 """
56 56 Adds regex requirements to pyramid routes using a mapping dict
57 57
58 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 59 '/{action}/{id:\d+}'
60 60
61 61 """
62 62 for key, regex in requirements.items():
63 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 64 return route_path
65 65
66 66
67 67 class JSRoutesMapper(Mapper):
68 68 """
69 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 70 """
71 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 73 def __init__(self, *args, **kw):
74 74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 75 self._jsroutes = []
76 76
77 77 def connect(self, *args, **kw):
78 78 """
79 79 Wrapper for connect to take an extra argument jsroute=True
80 80
81 81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 82 """
83 83 if kw.pop('jsroute', False):
84 84 if not self._named_route_regex.match(args[0]):
85 85 raise Exception('only named routes can be added to pyroutes')
86 86 self._jsroutes.append(args[0])
87 87
88 88 super(JSRoutesMapper, self).connect(*args, **kw)
89 89
90 90 def _extract_route_information(self, route):
91 91 """
92 92 Convert a route into tuple(name, path, args), eg:
93 93 ('show_user', '/profile/%(username)s', ['username'])
94 94 """
95 95 routepath = route.routepath
96 96 def replace(matchobj):
97 97 if matchobj.group(1):
98 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 99 else:
100 100 return "%%(%s)s" % matchobj.group(2)
101 101
102 102 routepath = self._argument_prog.sub(replace, routepath)
103 103 return (
104 104 route.name,
105 105 routepath,
106 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 107 for arg in self._argument_prog.findall(route.routepath)]
108 108 )
109 109
110 110 def jsroutes(self):
111 111 """
112 112 Return a list of pyroutes.js compatible routes
113 113 """
114 114 for route_name in self._jsroutes:
115 115 yield self._extract_route_information(self._routenames[route_name])
116 116
117 117
118 118 def make_map(config):
119 119 """Create, configure and return the routes Mapper"""
120 120 rmap = JSRoutesMapper(
121 121 directory=config['pylons.paths']['controllers'],
122 122 always_scan=config['debug'])
123 123 rmap.minimization = False
124 124 rmap.explicit = False
125 125
126 126 from rhodecode.lib.utils2 import str2bool
127 127 from rhodecode.model import repo, repo_group
128 128
129 129 def check_repo(environ, match_dict):
130 130 """
131 131 check for valid repository for proper 404 handling
132 132
133 133 :param environ:
134 134 :param match_dict:
135 135 """
136 136 repo_name = match_dict.get('repo_name')
137 137
138 138 if match_dict.get('f_path'):
139 139 # fix for multiple initial slashes that causes errors
140 140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 141 repo_model = repo.RepoModel()
142 142 by_name_match = repo_model.get_by_repo_name(repo_name)
143 143 # if we match quickly from database, short circuit the operation,
144 144 # and validate repo based on the type.
145 145 if by_name_match:
146 146 return True
147 147
148 148 by_id_match = repo_model.get_repo_by_id(repo_name)
149 149 if by_id_match:
150 150 repo_name = by_id_match.repo_name
151 151 match_dict['repo_name'] = repo_name
152 152 return True
153 153
154 154 return False
155 155
156 156 def check_group(environ, match_dict):
157 157 """
158 158 check for valid repository group path for proper 404 handling
159 159
160 160 :param environ:
161 161 :param match_dict:
162 162 """
163 163 repo_group_name = match_dict.get('group_name')
164 164 repo_group_model = repo_group.RepoGroupModel()
165 165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 166 if by_name_match:
167 167 return True
168 168
169 169 return False
170 170
171 171 def check_user_group(environ, match_dict):
172 172 """
173 173 check for valid user group for proper 404 handling
174 174
175 175 :param environ:
176 176 :param match_dict:
177 177 """
178 178 return True
179 179
180 180 def check_int(environ, match_dict):
181 181 return match_dict.get('id').isdigit()
182 182
183 183
184 184 #==========================================================================
185 185 # CUSTOM ROUTES HERE
186 186 #==========================================================================
187 187
188 188 # ping and pylons error test
189 189 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
190 190 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
191 191
192 192 # ADMIN REPOSITORY ROUTES
193 193 with rmap.submapper(path_prefix=ADMIN_PREFIX,
194 194 controller='admin/repos') as m:
195 195 m.connect('repos', '/repos',
196 196 action='create', conditions={'method': ['POST']})
197 197 m.connect('repos', '/repos',
198 198 action='index', conditions={'method': ['GET']})
199 199 m.connect('new_repo', '/create_repository', jsroute=True,
200 200 action='create_repository', conditions={'method': ['GET']})
201 201 m.connect('delete_repo', '/repos/{repo_name}',
202 202 action='delete', conditions={'method': ['DELETE']},
203 203 requirements=URL_NAME_REQUIREMENTS)
204 204 m.connect('repo', '/repos/{repo_name}',
205 205 action='show', conditions={'method': ['GET'],
206 206 'function': check_repo},
207 207 requirements=URL_NAME_REQUIREMENTS)
208 208
209 209 # ADMIN REPOSITORY GROUPS ROUTES
210 210 with rmap.submapper(path_prefix=ADMIN_PREFIX,
211 211 controller='admin/repo_groups') as m:
212 212 m.connect('repo_groups', '/repo_groups',
213 213 action='create', conditions={'method': ['POST']})
214 214 m.connect('repo_groups', '/repo_groups',
215 215 action='index', conditions={'method': ['GET']})
216 216 m.connect('new_repo_group', '/repo_groups/new',
217 217 action='new', conditions={'method': ['GET']})
218 218 m.connect('update_repo_group', '/repo_groups/{group_name}',
219 219 action='update', conditions={'method': ['PUT'],
220 220 'function': check_group},
221 221 requirements=URL_NAME_REQUIREMENTS)
222 222
223 223 # EXTRAS REPO GROUP ROUTES
224 224 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
225 225 action='edit',
226 226 conditions={'method': ['GET'], 'function': check_group},
227 227 requirements=URL_NAME_REQUIREMENTS)
228 228 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
229 229 action='edit',
230 230 conditions={'method': ['PUT'], 'function': check_group},
231 231 requirements=URL_NAME_REQUIREMENTS)
232 232
233 233 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
234 234 action='edit_repo_group_advanced',
235 235 conditions={'method': ['GET'], 'function': check_group},
236 236 requirements=URL_NAME_REQUIREMENTS)
237 237 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
238 238 action='edit_repo_group_advanced',
239 239 conditions={'method': ['PUT'], 'function': check_group},
240 240 requirements=URL_NAME_REQUIREMENTS)
241 241
242 242 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
243 243 action='edit_repo_group_perms',
244 244 conditions={'method': ['GET'], 'function': check_group},
245 245 requirements=URL_NAME_REQUIREMENTS)
246 246 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
247 247 action='update_perms',
248 248 conditions={'method': ['PUT'], 'function': check_group},
249 249 requirements=URL_NAME_REQUIREMENTS)
250 250
251 251 m.connect('delete_repo_group', '/repo_groups/{group_name}',
252 252 action='delete', conditions={'method': ['DELETE'],
253 253 'function': check_group},
254 254 requirements=URL_NAME_REQUIREMENTS)
255 255
256 256 # ADMIN USER ROUTES
257 257 with rmap.submapper(path_prefix=ADMIN_PREFIX,
258 258 controller='admin/users') as m:
259 259 m.connect('users', '/users',
260 260 action='create', conditions={'method': ['POST']})
261 261 m.connect('new_user', '/users/new',
262 262 action='new', conditions={'method': ['GET']})
263 263 m.connect('update_user', '/users/{user_id}',
264 264 action='update', conditions={'method': ['PUT']})
265 265 m.connect('delete_user', '/users/{user_id}',
266 266 action='delete', conditions={'method': ['DELETE']})
267 267 m.connect('edit_user', '/users/{user_id}/edit',
268 268 action='edit', conditions={'method': ['GET']}, jsroute=True)
269 269 m.connect('user', '/users/{user_id}',
270 270 action='show', conditions={'method': ['GET']})
271 271 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
272 272 action='reset_password', conditions={'method': ['POST']})
273 273 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
274 274 action='create_personal_repo_group', conditions={'method': ['POST']})
275 275
276 276 # EXTRAS USER ROUTES
277 277 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
278 278 action='edit_advanced', conditions={'method': ['GET']})
279 279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
280 280 action='update_advanced', conditions={'method': ['PUT']})
281 281
282 282 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
283 283 action='edit_global_perms', conditions={'method': ['GET']})
284 284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
285 285 action='update_global_perms', conditions={'method': ['PUT']})
286 286
287 287 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
288 288 action='edit_perms_summary', conditions={'method': ['GET']})
289 289
290 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
291 action='edit_emails', conditions={'method': ['GET']})
292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
293 action='add_email', conditions={'method': ['PUT']})
294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
295 action='delete_email', conditions={'method': ['DELETE']})
296
297 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
298 action='edit_ips', conditions={'method': ['GET']})
299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
300 action='add_ip', conditions={'method': ['PUT']})
301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
302 action='delete_ip', conditions={'method': ['DELETE']})
303 290
304 291 # ADMIN USER GROUPS REST ROUTES
305 292 with rmap.submapper(path_prefix=ADMIN_PREFIX,
306 293 controller='admin/user_groups') as m:
307 294 m.connect('users_groups', '/user_groups',
308 295 action='create', conditions={'method': ['POST']})
309 296 m.connect('users_groups', '/user_groups',
310 297 action='index', conditions={'method': ['GET']})
311 298 m.connect('new_users_group', '/user_groups/new',
312 299 action='new', conditions={'method': ['GET']})
313 300 m.connect('update_users_group', '/user_groups/{user_group_id}',
314 301 action='update', conditions={'method': ['PUT']})
315 302 m.connect('delete_users_group', '/user_groups/{user_group_id}',
316 303 action='delete', conditions={'method': ['DELETE']})
317 304 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
318 305 action='edit', conditions={'method': ['GET']},
319 306 function=check_user_group)
320 307
321 308 # EXTRAS USER GROUP ROUTES
322 309 m.connect('edit_user_group_global_perms',
323 310 '/user_groups/{user_group_id}/edit/global_permissions',
324 311 action='edit_global_perms', conditions={'method': ['GET']})
325 312 m.connect('edit_user_group_global_perms',
326 313 '/user_groups/{user_group_id}/edit/global_permissions',
327 314 action='update_global_perms', conditions={'method': ['PUT']})
328 315 m.connect('edit_user_group_perms_summary',
329 316 '/user_groups/{user_group_id}/edit/permissions_summary',
330 317 action='edit_perms_summary', conditions={'method': ['GET']})
331 318
332 319 m.connect('edit_user_group_perms',
333 320 '/user_groups/{user_group_id}/edit/permissions',
334 321 action='edit_perms', conditions={'method': ['GET']})
335 322 m.connect('edit_user_group_perms',
336 323 '/user_groups/{user_group_id}/edit/permissions',
337 324 action='update_perms', conditions={'method': ['PUT']})
338 325
339 326 m.connect('edit_user_group_advanced',
340 327 '/user_groups/{user_group_id}/edit/advanced',
341 328 action='edit_advanced', conditions={'method': ['GET']})
342 329
343 330 m.connect('edit_user_group_advanced_sync',
344 331 '/user_groups/{user_group_id}/edit/advanced/sync',
345 332 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
346 333
347 334 m.connect('edit_user_group_members',
348 335 '/user_groups/{user_group_id}/edit/members', jsroute=True,
349 336 action='user_group_members', conditions={'method': ['GET']})
350 337
351 338 # ADMIN PERMISSIONS ROUTES
352 339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 340 controller='admin/permissions') as m:
354 341 m.connect('admin_permissions_application', '/permissions/application',
355 342 action='permission_application_update', conditions={'method': ['POST']})
356 343 m.connect('admin_permissions_application', '/permissions/application',
357 344 action='permission_application', conditions={'method': ['GET']})
358 345
359 346 m.connect('admin_permissions_global', '/permissions/global',
360 347 action='permission_global_update', conditions={'method': ['POST']})
361 348 m.connect('admin_permissions_global', '/permissions/global',
362 349 action='permission_global', conditions={'method': ['GET']})
363 350
364 351 m.connect('admin_permissions_object', '/permissions/object',
365 352 action='permission_objects_update', conditions={'method': ['POST']})
366 353 m.connect('admin_permissions_object', '/permissions/object',
367 354 action='permission_objects', conditions={'method': ['GET']})
368 355
369 356 m.connect('admin_permissions_ips', '/permissions/ips',
370 357 action='permission_ips', conditions={'method': ['POST']})
371 358 m.connect('admin_permissions_ips', '/permissions/ips',
372 359 action='permission_ips', conditions={'method': ['GET']})
373 360
374 361 m.connect('admin_permissions_overview', '/permissions/overview',
375 362 action='permission_perms', conditions={'method': ['GET']})
376 363
377 364 # ADMIN DEFAULTS REST ROUTES
378 365 with rmap.submapper(path_prefix=ADMIN_PREFIX,
379 366 controller='admin/defaults') as m:
380 367 m.connect('admin_defaults_repositories', '/defaults/repositories',
381 368 action='update_repository_defaults', conditions={'method': ['POST']})
382 369 m.connect('admin_defaults_repositories', '/defaults/repositories',
383 370 action='index', conditions={'method': ['GET']})
384 371
385 372 # ADMIN DEBUG STYLE ROUTES
386 373 if str2bool(config.get('debug_style')):
387 374 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
388 375 controller='debug_style') as m:
389 376 m.connect('debug_style_home', '',
390 377 action='index', conditions={'method': ['GET']})
391 378 m.connect('debug_style_template', '/t/{t_path}',
392 379 action='template', conditions={'method': ['GET']})
393 380
394 381 # ADMIN SETTINGS ROUTES
395 382 with rmap.submapper(path_prefix=ADMIN_PREFIX,
396 383 controller='admin/settings') as m:
397 384
398 385 # default
399 386 m.connect('admin_settings', '/settings',
400 387 action='settings_global_update',
401 388 conditions={'method': ['POST']})
402 389 m.connect('admin_settings', '/settings',
403 390 action='settings_global', conditions={'method': ['GET']})
404 391
405 392 m.connect('admin_settings_vcs', '/settings/vcs',
406 393 action='settings_vcs_update',
407 394 conditions={'method': ['POST']})
408 395 m.connect('admin_settings_vcs', '/settings/vcs',
409 396 action='settings_vcs',
410 397 conditions={'method': ['GET']})
411 398 m.connect('admin_settings_vcs', '/settings/vcs',
412 399 action='delete_svn_pattern',
413 400 conditions={'method': ['DELETE']})
414 401
415 402 m.connect('admin_settings_mapping', '/settings/mapping',
416 403 action='settings_mapping_update',
417 404 conditions={'method': ['POST']})
418 405 m.connect('admin_settings_mapping', '/settings/mapping',
419 406 action='settings_mapping', conditions={'method': ['GET']})
420 407
421 408 m.connect('admin_settings_global', '/settings/global',
422 409 action='settings_global_update',
423 410 conditions={'method': ['POST']})
424 411 m.connect('admin_settings_global', '/settings/global',
425 412 action='settings_global', conditions={'method': ['GET']})
426 413
427 414 m.connect('admin_settings_visual', '/settings/visual',
428 415 action='settings_visual_update',
429 416 conditions={'method': ['POST']})
430 417 m.connect('admin_settings_visual', '/settings/visual',
431 418 action='settings_visual', conditions={'method': ['GET']})
432 419
433 420 m.connect('admin_settings_issuetracker',
434 421 '/settings/issue-tracker', action='settings_issuetracker',
435 422 conditions={'method': ['GET']})
436 423 m.connect('admin_settings_issuetracker_save',
437 424 '/settings/issue-tracker/save',
438 425 action='settings_issuetracker_save',
439 426 conditions={'method': ['POST']})
440 427 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
441 428 action='settings_issuetracker_test',
442 429 conditions={'method': ['POST']})
443 430 m.connect('admin_issuetracker_delete',
444 431 '/settings/issue-tracker/delete',
445 432 action='settings_issuetracker_delete',
446 433 conditions={'method': ['DELETE']})
447 434
448 435 m.connect('admin_settings_email', '/settings/email',
449 436 action='settings_email_update',
450 437 conditions={'method': ['POST']})
451 438 m.connect('admin_settings_email', '/settings/email',
452 439 action='settings_email', conditions={'method': ['GET']})
453 440
454 441 m.connect('admin_settings_hooks', '/settings/hooks',
455 442 action='settings_hooks_update',
456 443 conditions={'method': ['POST', 'DELETE']})
457 444 m.connect('admin_settings_hooks', '/settings/hooks',
458 445 action='settings_hooks', conditions={'method': ['GET']})
459 446
460 447 m.connect('admin_settings_search', '/settings/search',
461 448 action='settings_search', conditions={'method': ['GET']})
462 449
463 450 m.connect('admin_settings_supervisor', '/settings/supervisor',
464 451 action='settings_supervisor', conditions={'method': ['GET']})
465 452 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
466 453 action='settings_supervisor_log', conditions={'method': ['GET']})
467 454
468 455 m.connect('admin_settings_labs', '/settings/labs',
469 456 action='settings_labs_update',
470 457 conditions={'method': ['POST']})
471 458 m.connect('admin_settings_labs', '/settings/labs',
472 459 action='settings_labs', conditions={'method': ['GET']})
473 460
474 461 # ADMIN MY ACCOUNT
475 462 with rmap.submapper(path_prefix=ADMIN_PREFIX,
476 463 controller='admin/my_account') as m:
477 464
478 465 m.connect('my_account_edit', '/my_account/edit',
479 466 action='my_account_edit', conditions={'method': ['GET']})
480 467 m.connect('my_account', '/my_account/update',
481 468 action='my_account_update', conditions={'method': ['POST']})
482 469
483 470 # NOTE(marcink): this needs to be kept for password force flag to be
484 471 # handler, remove after migration to pyramid
485 472 m.connect('my_account_password', '/my_account/password',
486 473 action='my_account_password', conditions={'method': ['GET']})
487 474
488 475 m.connect('my_account_pullrequests', '/my_account/pull_requests',
489 476 action='my_account_pullrequests', conditions={'method': ['GET']})
490 477
491 478 # NOTIFICATION REST ROUTES
492 479 with rmap.submapper(path_prefix=ADMIN_PREFIX,
493 480 controller='admin/notifications') as m:
494 481 m.connect('notifications', '/notifications',
495 482 action='index', conditions={'method': ['GET']})
496 483 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
497 484 action='mark_all_read', conditions={'method': ['POST']})
498 485 m.connect('/notifications/{notification_id}',
499 486 action='update', conditions={'method': ['PUT']})
500 487 m.connect('/notifications/{notification_id}',
501 488 action='delete', conditions={'method': ['DELETE']})
502 489 m.connect('notification', '/notifications/{notification_id}',
503 490 action='show', conditions={'method': ['GET']})
504 491
505 492 # ADMIN GIST
506 493 with rmap.submapper(path_prefix=ADMIN_PREFIX,
507 494 controller='admin/gists') as m:
508 495 m.connect('gists', '/gists',
509 496 action='create', conditions={'method': ['POST']})
510 497 m.connect('gists', '/gists', jsroute=True,
511 498 action='index', conditions={'method': ['GET']})
512 499 m.connect('new_gist', '/gists/new', jsroute=True,
513 500 action='new', conditions={'method': ['GET']})
514 501
515 502 m.connect('/gists/{gist_id}',
516 503 action='delete', conditions={'method': ['DELETE']})
517 504 m.connect('edit_gist', '/gists/{gist_id}/edit',
518 505 action='edit_form', conditions={'method': ['GET']})
519 506 m.connect('edit_gist', '/gists/{gist_id}/edit',
520 507 action='edit', conditions={'method': ['POST']})
521 508 m.connect(
522 509 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
523 510 action='check_revision', conditions={'method': ['GET']})
524 511
525 512 m.connect('gist', '/gists/{gist_id}',
526 513 action='show', conditions={'method': ['GET']})
527 514 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
528 515 revision='tip',
529 516 action='show', conditions={'method': ['GET']})
530 517 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
531 518 revision='tip',
532 519 action='show', conditions={'method': ['GET']})
533 520 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
534 521 revision='tip',
535 522 action='show', conditions={'method': ['GET']},
536 523 requirements=URL_NAME_REQUIREMENTS)
537 524
538 525 # USER JOURNAL
539 526 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
540 527 controller='journal', action='index')
541 528 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
542 529 controller='journal', action='journal_rss')
543 530 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
544 531 controller='journal', action='journal_atom')
545 532
546 533 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
547 534 controller='journal', action='public_journal')
548 535
549 536 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
550 537 controller='journal', action='public_journal_rss')
551 538
552 539 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
553 540 controller='journal', action='public_journal_rss')
554 541
555 542 rmap.connect('public_journal_atom',
556 543 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
557 544 action='public_journal_atom')
558 545
559 546 rmap.connect('public_journal_atom_old',
560 547 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
561 548 action='public_journal_atom')
562 549
563 550 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
564 551 controller='journal', action='toggle_following', jsroute=True,
565 552 conditions={'method': ['POST']})
566 553
567 554 # FEEDS
568 555 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
569 556 controller='feed', action='rss',
570 557 conditions={'function': check_repo},
571 558 requirements=URL_NAME_REQUIREMENTS)
572 559
573 560 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
574 561 controller='feed', action='atom',
575 562 conditions={'function': check_repo},
576 563 requirements=URL_NAME_REQUIREMENTS)
577 564
578 565 #==========================================================================
579 566 # REPOSITORY ROUTES
580 567 #==========================================================================
581 568
582 569 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
583 570 controller='admin/repos', action='repo_creating',
584 571 requirements=URL_NAME_REQUIREMENTS)
585 572 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
586 573 controller='admin/repos', action='repo_check',
587 574 requirements=URL_NAME_REQUIREMENTS)
588 575
589 576 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
590 577 controller='changeset', revision='tip',
591 578 conditions={'function': check_repo},
592 579 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
593 580 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
594 581 controller='changeset', revision='tip', action='changeset_children',
595 582 conditions={'function': check_repo},
596 583 requirements=URL_NAME_REQUIREMENTS)
597 584 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
598 585 controller='changeset', revision='tip', action='changeset_parents',
599 586 conditions={'function': check_repo},
600 587 requirements=URL_NAME_REQUIREMENTS)
601 588
602 589 # repo edit options
603 590 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
604 591 controller='admin/repos', action='edit_fields',
605 592 conditions={'method': ['GET'], 'function': check_repo},
606 593 requirements=URL_NAME_REQUIREMENTS)
607 594 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
608 595 controller='admin/repos', action='create_repo_field',
609 596 conditions={'method': ['PUT'], 'function': check_repo},
610 597 requirements=URL_NAME_REQUIREMENTS)
611 598 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
612 599 controller='admin/repos', action='delete_repo_field',
613 600 conditions={'method': ['DELETE'], 'function': check_repo},
614 601 requirements=URL_NAME_REQUIREMENTS)
615 602
616 603 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
617 604 controller='admin/repos', action='toggle_locking',
618 605 conditions={'method': ['GET'], 'function': check_repo},
619 606 requirements=URL_NAME_REQUIREMENTS)
620 607
621 608 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
622 609 controller='admin/repos', action='edit_remote_form',
623 610 conditions={'method': ['GET'], 'function': check_repo},
624 611 requirements=URL_NAME_REQUIREMENTS)
625 612 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
626 613 controller='admin/repos', action='edit_remote',
627 614 conditions={'method': ['PUT'], 'function': check_repo},
628 615 requirements=URL_NAME_REQUIREMENTS)
629 616
630 617 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
631 618 controller='admin/repos', action='edit_statistics_form',
632 619 conditions={'method': ['GET'], 'function': check_repo},
633 620 requirements=URL_NAME_REQUIREMENTS)
634 621 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
635 622 controller='admin/repos', action='edit_statistics',
636 623 conditions={'method': ['PUT'], 'function': check_repo},
637 624 requirements=URL_NAME_REQUIREMENTS)
638 625 rmap.connect('repo_settings_issuetracker',
639 626 '/{repo_name}/settings/issue-tracker',
640 627 controller='admin/repos', action='repo_issuetracker',
641 628 conditions={'method': ['GET'], 'function': check_repo},
642 629 requirements=URL_NAME_REQUIREMENTS)
643 630 rmap.connect('repo_issuetracker_test',
644 631 '/{repo_name}/settings/issue-tracker/test',
645 632 controller='admin/repos', action='repo_issuetracker_test',
646 633 conditions={'method': ['POST'], 'function': check_repo},
647 634 requirements=URL_NAME_REQUIREMENTS)
648 635 rmap.connect('repo_issuetracker_delete',
649 636 '/{repo_name}/settings/issue-tracker/delete',
650 637 controller='admin/repos', action='repo_issuetracker_delete',
651 638 conditions={'method': ['DELETE'], 'function': check_repo},
652 639 requirements=URL_NAME_REQUIREMENTS)
653 640 rmap.connect('repo_issuetracker_save',
654 641 '/{repo_name}/settings/issue-tracker/save',
655 642 controller='admin/repos', action='repo_issuetracker_save',
656 643 conditions={'method': ['POST'], 'function': check_repo},
657 644 requirements=URL_NAME_REQUIREMENTS)
658 645 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
659 646 controller='admin/repos', action='repo_settings_vcs_update',
660 647 conditions={'method': ['POST'], 'function': check_repo},
661 648 requirements=URL_NAME_REQUIREMENTS)
662 649 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
663 650 controller='admin/repos', action='repo_settings_vcs',
664 651 conditions={'method': ['GET'], 'function': check_repo},
665 652 requirements=URL_NAME_REQUIREMENTS)
666 653 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
667 654 controller='admin/repos', action='repo_delete_svn_pattern',
668 655 conditions={'method': ['DELETE'], 'function': check_repo},
669 656 requirements=URL_NAME_REQUIREMENTS)
670 657 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
671 658 controller='admin/repos', action='repo_settings_pullrequest',
672 659 conditions={'method': ['GET', 'POST'], 'function': check_repo},
673 660 requirements=URL_NAME_REQUIREMENTS)
674 661
675 662 # still working url for backward compat.
676 663 rmap.connect('raw_changeset_home_depraced',
677 664 '/{repo_name}/raw-changeset/{revision}',
678 665 controller='changeset', action='changeset_raw',
679 666 revision='tip', conditions={'function': check_repo},
680 667 requirements=URL_NAME_REQUIREMENTS)
681 668
682 669 # new URLs
683 670 rmap.connect('changeset_raw_home',
684 671 '/{repo_name}/changeset-diff/{revision}',
685 672 controller='changeset', action='changeset_raw',
686 673 revision='tip', conditions={'function': check_repo},
687 674 requirements=URL_NAME_REQUIREMENTS)
688 675
689 676 rmap.connect('changeset_patch_home',
690 677 '/{repo_name}/changeset-patch/{revision}',
691 678 controller='changeset', action='changeset_patch',
692 679 revision='tip', conditions={'function': check_repo},
693 680 requirements=URL_NAME_REQUIREMENTS)
694 681
695 682 rmap.connect('changeset_download_home',
696 683 '/{repo_name}/changeset-download/{revision}',
697 684 controller='changeset', action='changeset_download',
698 685 revision='tip', conditions={'function': check_repo},
699 686 requirements=URL_NAME_REQUIREMENTS)
700 687
701 688 rmap.connect('changeset_comment',
702 689 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
703 690 controller='changeset', revision='tip', action='comment',
704 691 conditions={'function': check_repo},
705 692 requirements=URL_NAME_REQUIREMENTS)
706 693
707 694 rmap.connect('changeset_comment_preview',
708 695 '/{repo_name}/changeset/comment/preview', jsroute=True,
709 696 controller='changeset', action='preview_comment',
710 697 conditions={'function': check_repo, 'method': ['POST']},
711 698 requirements=URL_NAME_REQUIREMENTS)
712 699
713 700 rmap.connect('changeset_comment_delete',
714 701 '/{repo_name}/changeset/comment/{comment_id}/delete',
715 702 controller='changeset', action='delete_comment',
716 703 conditions={'function': check_repo, 'method': ['DELETE']},
717 704 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
718 705
719 706 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
720 707 controller='changeset', action='changeset_info',
721 708 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
722 709
723 710 rmap.connect('compare_home',
724 711 '/{repo_name}/compare',
725 712 controller='compare', action='index',
726 713 conditions={'function': check_repo},
727 714 requirements=URL_NAME_REQUIREMENTS)
728 715
729 716 rmap.connect('compare_url',
730 717 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
731 718 controller='compare', action='compare',
732 719 conditions={'function': check_repo},
733 720 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
734 721
735 722 rmap.connect('pullrequest_home',
736 723 '/{repo_name}/pull-request/new', controller='pullrequests',
737 724 action='index', conditions={'function': check_repo,
738 725 'method': ['GET']},
739 726 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
740 727
741 728 rmap.connect('pullrequest',
742 729 '/{repo_name}/pull-request/new', controller='pullrequests',
743 730 action='create', conditions={'function': check_repo,
744 731 'method': ['POST']},
745 732 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
746 733
747 734 rmap.connect('pullrequest_repo_refs',
748 735 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
749 736 controller='pullrequests',
750 737 action='get_repo_refs',
751 738 conditions={'function': check_repo, 'method': ['GET']},
752 739 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
753 740
754 741 rmap.connect('pullrequest_repo_destinations',
755 742 '/{repo_name}/pull-request/repo-destinations',
756 743 controller='pullrequests',
757 744 action='get_repo_destinations',
758 745 conditions={'function': check_repo, 'method': ['GET']},
759 746 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
760 747
761 748 rmap.connect('pullrequest_show',
762 749 '/{repo_name}/pull-request/{pull_request_id}',
763 750 controller='pullrequests',
764 751 action='show', conditions={'function': check_repo,
765 752 'method': ['GET']},
766 753 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
767 754
768 755 rmap.connect('pullrequest_update',
769 756 '/{repo_name}/pull-request/{pull_request_id}',
770 757 controller='pullrequests',
771 758 action='update', conditions={'function': check_repo,
772 759 'method': ['PUT']},
773 760 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
774 761
775 762 rmap.connect('pullrequest_merge',
776 763 '/{repo_name}/pull-request/{pull_request_id}',
777 764 controller='pullrequests',
778 765 action='merge', conditions={'function': check_repo,
779 766 'method': ['POST']},
780 767 requirements=URL_NAME_REQUIREMENTS)
781 768
782 769 rmap.connect('pullrequest_delete',
783 770 '/{repo_name}/pull-request/{pull_request_id}',
784 771 controller='pullrequests',
785 772 action='delete', conditions={'function': check_repo,
786 773 'method': ['DELETE']},
787 774 requirements=URL_NAME_REQUIREMENTS)
788 775
789 776 rmap.connect('pullrequest_comment',
790 777 '/{repo_name}/pull-request-comment/{pull_request_id}',
791 778 controller='pullrequests',
792 779 action='comment', conditions={'function': check_repo,
793 780 'method': ['POST']},
794 781 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
795 782
796 783 rmap.connect('pullrequest_comment_delete',
797 784 '/{repo_name}/pull-request-comment/{comment_id}/delete',
798 785 controller='pullrequests', action='delete_comment',
799 786 conditions={'function': check_repo, 'method': ['DELETE']},
800 787 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
801 788
802 789 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
803 790 controller='changelog', conditions={'function': check_repo},
804 791 requirements=URL_NAME_REQUIREMENTS)
805 792
806 793 rmap.connect('changelog_file_home',
807 794 '/{repo_name}/changelog/{revision}/{f_path}',
808 795 controller='changelog', f_path=None,
809 796 conditions={'function': check_repo},
810 797 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
811 798
812 799 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
813 800 controller='changelog', action='changelog_elements',
814 801 conditions={'function': check_repo},
815 802 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
816 803
817 804 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
818 805 controller='files', revision='tip', f_path='',
819 806 conditions={'function': check_repo},
820 807 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
821 808
822 809 rmap.connect('files_home_simple_catchrev',
823 810 '/{repo_name}/files/{revision}',
824 811 controller='files', revision='tip', f_path='',
825 812 conditions={'function': check_repo},
826 813 requirements=URL_NAME_REQUIREMENTS)
827 814
828 815 rmap.connect('files_home_simple_catchall',
829 816 '/{repo_name}/files',
830 817 controller='files', revision='tip', f_path='',
831 818 conditions={'function': check_repo},
832 819 requirements=URL_NAME_REQUIREMENTS)
833 820
834 821 rmap.connect('files_history_home',
835 822 '/{repo_name}/history/{revision}/{f_path}',
836 823 controller='files', action='history', revision='tip', f_path='',
837 824 conditions={'function': check_repo},
838 825 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
839 826
840 827 rmap.connect('files_authors_home',
841 828 '/{repo_name}/authors/{revision}/{f_path}',
842 829 controller='files', action='authors', revision='tip', f_path='',
843 830 conditions={'function': check_repo},
844 831 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
845 832
846 833 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
847 834 controller='files', action='diff', f_path='',
848 835 conditions={'function': check_repo},
849 836 requirements=URL_NAME_REQUIREMENTS)
850 837
851 838 rmap.connect('files_diff_2way_home',
852 839 '/{repo_name}/diff-2way/{f_path}',
853 840 controller='files', action='diff_2way', f_path='',
854 841 conditions={'function': check_repo},
855 842 requirements=URL_NAME_REQUIREMENTS)
856 843
857 844 rmap.connect('files_rawfile_home',
858 845 '/{repo_name}/rawfile/{revision}/{f_path}',
859 846 controller='files', action='rawfile', revision='tip',
860 847 f_path='', conditions={'function': check_repo},
861 848 requirements=URL_NAME_REQUIREMENTS)
862 849
863 850 rmap.connect('files_raw_home',
864 851 '/{repo_name}/raw/{revision}/{f_path}',
865 852 controller='files', action='raw', revision='tip', f_path='',
866 853 conditions={'function': check_repo},
867 854 requirements=URL_NAME_REQUIREMENTS)
868 855
869 856 rmap.connect('files_render_home',
870 857 '/{repo_name}/render/{revision}/{f_path}',
871 858 controller='files', action='index', revision='tip', f_path='',
872 859 rendered=True, conditions={'function': check_repo},
873 860 requirements=URL_NAME_REQUIREMENTS)
874 861
875 862 rmap.connect('files_annotate_home',
876 863 '/{repo_name}/annotate/{revision}/{f_path}',
877 864 controller='files', action='index', revision='tip',
878 865 f_path='', annotate=True, conditions={'function': check_repo},
879 866 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
880 867
881 868 rmap.connect('files_annotate_previous',
882 869 '/{repo_name}/annotate-previous/{revision}/{f_path}',
883 870 controller='files', action='annotate_previous', revision='tip',
884 871 f_path='', annotate=True, conditions={'function': check_repo},
885 872 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
886 873
887 874 rmap.connect('files_edit',
888 875 '/{repo_name}/edit/{revision}/{f_path}',
889 876 controller='files', action='edit', revision='tip',
890 877 f_path='',
891 878 conditions={'function': check_repo, 'method': ['POST']},
892 879 requirements=URL_NAME_REQUIREMENTS)
893 880
894 881 rmap.connect('files_edit_home',
895 882 '/{repo_name}/edit/{revision}/{f_path}',
896 883 controller='files', action='edit_home', revision='tip',
897 884 f_path='', conditions={'function': check_repo},
898 885 requirements=URL_NAME_REQUIREMENTS)
899 886
900 887 rmap.connect('files_add',
901 888 '/{repo_name}/add/{revision}/{f_path}',
902 889 controller='files', action='add', revision='tip',
903 890 f_path='',
904 891 conditions={'function': check_repo, 'method': ['POST']},
905 892 requirements=URL_NAME_REQUIREMENTS)
906 893
907 894 rmap.connect('files_add_home',
908 895 '/{repo_name}/add/{revision}/{f_path}',
909 896 controller='files', action='add_home', revision='tip',
910 897 f_path='', conditions={'function': check_repo},
911 898 requirements=URL_NAME_REQUIREMENTS)
912 899
913 900 rmap.connect('files_delete',
914 901 '/{repo_name}/delete/{revision}/{f_path}',
915 902 controller='files', action='delete', revision='tip',
916 903 f_path='',
917 904 conditions={'function': check_repo, 'method': ['POST']},
918 905 requirements=URL_NAME_REQUIREMENTS)
919 906
920 907 rmap.connect('files_delete_home',
921 908 '/{repo_name}/delete/{revision}/{f_path}',
922 909 controller='files', action='delete_home', revision='tip',
923 910 f_path='', conditions={'function': check_repo},
924 911 requirements=URL_NAME_REQUIREMENTS)
925 912
926 913 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
927 914 controller='files', action='archivefile',
928 915 conditions={'function': check_repo},
929 916 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
930 917
931 918 rmap.connect('files_nodelist_home',
932 919 '/{repo_name}/nodelist/{revision}/{f_path}',
933 920 controller='files', action='nodelist',
934 921 conditions={'function': check_repo},
935 922 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
936 923
937 924 rmap.connect('files_nodetree_full',
938 925 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
939 926 controller='files', action='nodetree_full',
940 927 conditions={'function': check_repo},
941 928 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
942 929
943 930 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
944 931 controller='forks', action='fork_create',
945 932 conditions={'function': check_repo, 'method': ['POST']},
946 933 requirements=URL_NAME_REQUIREMENTS)
947 934
948 935 rmap.connect('repo_fork_home', '/{repo_name}/fork',
949 936 controller='forks', action='fork',
950 937 conditions={'function': check_repo},
951 938 requirements=URL_NAME_REQUIREMENTS)
952 939
953 940 rmap.connect('repo_forks_home', '/{repo_name}/forks',
954 941 controller='forks', action='forks',
955 942 conditions={'function': check_repo},
956 943 requirements=URL_NAME_REQUIREMENTS)
957 944
958 945 return rmap
@@ -1,643 +1,493 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Users crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator, AuthUser)
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.exceptions import (
42 42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
43 43 UserOwnsUserGroupsException, UserCreationError)
44 44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45 45
46 46 from rhodecode.model.db import (
47 47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
48 48 from rhodecode.model.forms import (
49 49 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
50 50 from rhodecode.model.repo_group import RepoGroupModel
51 51 from rhodecode.model.user import UserModel
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.permission import PermissionModel
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class UsersController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60
61 61 @LoginRequired()
62 62 def __before__(self):
63 63 super(UsersController, self).__before__()
64 64 c.available_permissions = config['available_permissions']
65 65 c.allowed_languages = [
66 66 ('en', 'English (en)'),
67 67 ('de', 'German (de)'),
68 68 ('fr', 'French (fr)'),
69 69 ('it', 'Italian (it)'),
70 70 ('ja', 'Japanese (ja)'),
71 71 ('pl', 'Polish (pl)'),
72 72 ('pt', 'Portuguese (pt)'),
73 73 ('ru', 'Russian (ru)'),
74 74 ('zh', 'Chinese (zh)'),
75 75 ]
76 76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
77 77
78 78 def _get_personal_repo_group_template_vars(self):
79 79 DummyUser = AttributeDict({
80 80 'username': '${username}',
81 81 'user_id': '${user_id}',
82 82 })
83 83 c.default_create_repo_group = RepoGroupModel() \
84 84 .get_default_create_personal_repo_group()
85 85 c.personal_repo_group_name = RepoGroupModel() \
86 86 .get_personal_group_name(DummyUser)
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 @auth.CSRFRequired()
90 90 def create(self):
91 91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
92 92 user_model = UserModel()
93 93 user_form = UserForm()()
94 94 try:
95 95 form_result = user_form.to_python(dict(request.POST))
96 96 user = user_model.create(form_result)
97 97 Session().flush()
98 98 creation_data = user.get_api_data()
99 99 username = form_result['username']
100 100
101 101 audit_logger.store_web(
102 102 'user.create', action_data={'data': creation_data},
103 103 user=c.rhodecode_user)
104 104
105 105 user_link = h.link_to(h.escape(username),
106 106 url('edit_user',
107 107 user_id=user.user_id))
108 108 h.flash(h.literal(_('Created user %(user_link)s')
109 109 % {'user_link': user_link}), category='success')
110 110 Session().commit()
111 111 except formencode.Invalid as errors:
112 112 self._get_personal_repo_group_template_vars()
113 113 return htmlfill.render(
114 114 render('admin/users/user_add.mako'),
115 115 defaults=errors.value,
116 116 errors=errors.error_dict or {},
117 117 prefix_error=False,
118 118 encoding="UTF-8",
119 119 force_defaults=False)
120 120 except UserCreationError as e:
121 121 h.flash(e, 'error')
122 122 except Exception:
123 123 log.exception("Exception creation of user")
124 124 h.flash(_('Error occurred during creation of user %s')
125 125 % request.POST.get('username'), category='error')
126 126 return redirect(h.route_path('users'))
127 127
128 128 @HasPermissionAllDecorator('hg.admin')
129 129 def new(self):
130 130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
131 131 self._get_personal_repo_group_template_vars()
132 132 return render('admin/users/user_add.mako')
133 133
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 @auth.CSRFRequired()
136 136 def update(self, user_id):
137 137
138 138 user_id = safe_int(user_id)
139 139 c.user = User.get_or_404(user_id)
140 140 c.active = 'profile'
141 141 c.extern_type = c.user.extern_type
142 142 c.extern_name = c.user.extern_name
143 143 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
144 144 available_languages = [x[0] for x in c.allowed_languages]
145 145 _form = UserForm(edit=True, available_languages=available_languages,
146 146 old_data={'user_id': user_id,
147 147 'email': c.user.email})()
148 148 form_result = {}
149 149 old_values = c.user.get_api_data()
150 150 try:
151 151 form_result = _form.to_python(dict(request.POST))
152 152 skip_attrs = ['extern_type', 'extern_name']
153 153 # TODO: plugin should define if username can be updated
154 154 if c.extern_type != "rhodecode":
155 155 # forbid updating username for external accounts
156 156 skip_attrs.append('username')
157 157
158 158 UserModel().update_user(
159 159 user_id, skip_attrs=skip_attrs, **form_result)
160 160
161 161 audit_logger.store_web(
162 162 'user.edit', action_data={'old_data': old_values},
163 163 user=c.rhodecode_user)
164 164
165 165 Session().commit()
166 166 h.flash(_('User updated successfully'), category='success')
167 167 except formencode.Invalid as errors:
168 168 defaults = errors.value
169 169 e = errors.error_dict or {}
170 170
171 171 return htmlfill.render(
172 172 render('admin/users/user_edit.mako'),
173 173 defaults=defaults,
174 174 errors=e,
175 175 prefix_error=False,
176 176 encoding="UTF-8",
177 177 force_defaults=False)
178 178 except UserCreationError as e:
179 179 h.flash(e, 'error')
180 180 except Exception:
181 181 log.exception("Exception updating user")
182 182 h.flash(_('Error occurred during update of user %s')
183 183 % form_result.get('username'), category='error')
184 184 return redirect(url('edit_user', user_id=user_id))
185 185
186 186 @HasPermissionAllDecorator('hg.admin')
187 187 @auth.CSRFRequired()
188 188 def delete(self, user_id):
189 189 user_id = safe_int(user_id)
190 190 c.user = User.get_or_404(user_id)
191 191
192 192 _repos = c.user.repositories
193 193 _repo_groups = c.user.repository_groups
194 194 _user_groups = c.user.user_groups
195 195
196 196 handle_repos = None
197 197 handle_repo_groups = None
198 198 handle_user_groups = None
199 199 # dummy call for flash of handle
200 200 set_handle_flash_repos = lambda: None
201 201 set_handle_flash_repo_groups = lambda: None
202 202 set_handle_flash_user_groups = lambda: None
203 203
204 204 if _repos and request.POST.get('user_repos'):
205 205 do = request.POST['user_repos']
206 206 if do == 'detach':
207 207 handle_repos = 'detach'
208 208 set_handle_flash_repos = lambda: h.flash(
209 209 _('Detached %s repositories') % len(_repos),
210 210 category='success')
211 211 elif do == 'delete':
212 212 handle_repos = 'delete'
213 213 set_handle_flash_repos = lambda: h.flash(
214 214 _('Deleted %s repositories') % len(_repos),
215 215 category='success')
216 216
217 217 if _repo_groups and request.POST.get('user_repo_groups'):
218 218 do = request.POST['user_repo_groups']
219 219 if do == 'detach':
220 220 handle_repo_groups = 'detach'
221 221 set_handle_flash_repo_groups = lambda: h.flash(
222 222 _('Detached %s repository groups') % len(_repo_groups),
223 223 category='success')
224 224 elif do == 'delete':
225 225 handle_repo_groups = 'delete'
226 226 set_handle_flash_repo_groups = lambda: h.flash(
227 227 _('Deleted %s repository groups') % len(_repo_groups),
228 228 category='success')
229 229
230 230 if _user_groups and request.POST.get('user_user_groups'):
231 231 do = request.POST['user_user_groups']
232 232 if do == 'detach':
233 233 handle_user_groups = 'detach'
234 234 set_handle_flash_user_groups = lambda: h.flash(
235 235 _('Detached %s user groups') % len(_user_groups),
236 236 category='success')
237 237 elif do == 'delete':
238 238 handle_user_groups = 'delete'
239 239 set_handle_flash_user_groups = lambda: h.flash(
240 240 _('Deleted %s user groups') % len(_user_groups),
241 241 category='success')
242 242
243 243 old_values = c.user.get_api_data()
244 244 try:
245 245 UserModel().delete(c.user, handle_repos=handle_repos,
246 246 handle_repo_groups=handle_repo_groups,
247 247 handle_user_groups=handle_user_groups)
248 248
249 249 audit_logger.store_web(
250 250 'user.delete', action_data={'old_data': old_values},
251 251 user=c.rhodecode_user)
252 252
253 253 Session().commit()
254 254 set_handle_flash_repos()
255 255 set_handle_flash_repo_groups()
256 256 set_handle_flash_user_groups()
257 257 h.flash(_('Successfully deleted user'), category='success')
258 258 except (UserOwnsReposException, UserOwnsRepoGroupsException,
259 259 UserOwnsUserGroupsException, DefaultUserException) as e:
260 260 h.flash(e, category='warning')
261 261 except Exception:
262 262 log.exception("Exception during deletion of user")
263 263 h.flash(_('An error occurred during deletion of user'),
264 264 category='error')
265 265 return redirect(h.route_path('users'))
266 266
267 267 @HasPermissionAllDecorator('hg.admin')
268 268 @auth.CSRFRequired()
269 269 def reset_password(self, user_id):
270 270 """
271 271 toggle reset password flag for this user
272 272 """
273 273 user_id = safe_int(user_id)
274 274 c.user = User.get_or_404(user_id)
275 275 try:
276 276 old_value = c.user.user_data.get('force_password_change')
277 277 c.user.update_userdata(force_password_change=not old_value)
278 278
279 279 if old_value:
280 280 msg = _('Force password change disabled for user')
281 281 audit_logger.store_web(
282 282 'user.edit.password_reset.disabled',
283 283 user=c.rhodecode_user)
284 284 else:
285 285 msg = _('Force password change enabled for user')
286 286 audit_logger.store_web(
287 287 'user.edit.password_reset.enabled',
288 288 user=c.rhodecode_user)
289 289
290 290 Session().commit()
291 291 h.flash(msg, category='success')
292 292 except Exception:
293 293 log.exception("Exception during password reset for user")
294 294 h.flash(_('An error occurred during password reset for user'),
295 295 category='error')
296 296
297 297 return redirect(url('edit_user_advanced', user_id=user_id))
298 298
299 299 @HasPermissionAllDecorator('hg.admin')
300 300 @auth.CSRFRequired()
301 301 def create_personal_repo_group(self, user_id):
302 302 """
303 303 Create personal repository group for this user
304 304 """
305 305 from rhodecode.model.repo_group import RepoGroupModel
306 306
307 307 user_id = safe_int(user_id)
308 308 c.user = User.get_or_404(user_id)
309 309 personal_repo_group = RepoGroup.get_user_personal_repo_group(
310 310 c.user.user_id)
311 311 if personal_repo_group:
312 312 return redirect(url('edit_user_advanced', user_id=user_id))
313 313
314 314 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
315 315 c.user)
316 316 named_personal_group = RepoGroup.get_by_group_name(
317 317 personal_repo_group_name)
318 318 try:
319 319
320 320 if named_personal_group and named_personal_group.user_id == c.user.user_id:
321 321 # migrate the same named group, and mark it as personal
322 322 named_personal_group.personal = True
323 323 Session().add(named_personal_group)
324 324 Session().commit()
325 325 msg = _('Linked repository group `%s` as personal' % (
326 326 personal_repo_group_name,))
327 327 h.flash(msg, category='success')
328 328 elif not named_personal_group:
329 329 RepoGroupModel().create_personal_repo_group(c.user)
330 330
331 331 msg = _('Created repository group `%s`' % (
332 332 personal_repo_group_name,))
333 333 h.flash(msg, category='success')
334 334 else:
335 335 msg = _('Repository group `%s` is already taken' % (
336 336 personal_repo_group_name,))
337 337 h.flash(msg, category='warning')
338 338 except Exception:
339 339 log.exception("Exception during repository group creation")
340 340 msg = _(
341 341 'An error occurred during repository group creation for user')
342 342 h.flash(msg, category='error')
343 343 Session().rollback()
344 344
345 345 return redirect(url('edit_user_advanced', user_id=user_id))
346 346
347 347 @HasPermissionAllDecorator('hg.admin')
348 348 def show(self, user_id):
349 349 """GET /users/user_id: Show a specific item"""
350 350 # url('user', user_id=ID)
351 351 User.get_or_404(-1)
352 352
353 353 @HasPermissionAllDecorator('hg.admin')
354 354 def edit(self, user_id):
355 355 """GET /users/user_id/edit: Form to edit an existing item"""
356 356 # url('edit_user', user_id=ID)
357 357 user_id = safe_int(user_id)
358 358 c.user = User.get_or_404(user_id)
359 359 if c.user.username == User.DEFAULT_USER:
360 360 h.flash(_("You can't edit this user"), category='warning')
361 361 return redirect(h.route_path('users'))
362 362
363 363 c.active = 'profile'
364 364 c.extern_type = c.user.extern_type
365 365 c.extern_name = c.user.extern_name
366 366 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
367 367
368 368 defaults = c.user.get_dict()
369 369 defaults.update({'language': c.user.user_data.get('language')})
370 370 return htmlfill.render(
371 371 render('admin/users/user_edit.mako'),
372 372 defaults=defaults,
373 373 encoding="UTF-8",
374 374 force_defaults=False)
375 375
376 376 @HasPermissionAllDecorator('hg.admin')
377 377 def edit_advanced(self, user_id):
378 378 user_id = safe_int(user_id)
379 379 user = c.user = User.get_or_404(user_id)
380 380 if user.username == User.DEFAULT_USER:
381 381 h.flash(_("You can't edit this user"), category='warning')
382 382 return redirect(h.route_path('users'))
383 383
384 384 c.active = 'advanced'
385 385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
386 386 c.personal_repo_group_name = RepoGroupModel()\
387 387 .get_personal_group_name(user)
388 388 c.first_admin = User.get_first_super_admin()
389 389 defaults = user.get_dict()
390 390
391 391 # Interim workaround if the user participated on any pull requests as a
392 392 # reviewer.
393 393 has_review = bool(PullRequestReviewers.query().filter(
394 394 PullRequestReviewers.user_id == user_id).first())
395 395 c.can_delete_user = not has_review
396 396 c.can_delete_user_message = _(
397 397 'The user participates as reviewer in pull requests and '
398 398 'cannot be deleted. You can set the user to '
399 399 '"inactive" instead of deleting it.') if has_review else ''
400 400
401 401 return htmlfill.render(
402 402 render('admin/users/user_edit.mako'),
403 403 defaults=defaults,
404 404 encoding="UTF-8",
405 405 force_defaults=False)
406 406
407 407 @HasPermissionAllDecorator('hg.admin')
408 408 def edit_global_perms(self, user_id):
409 409 user_id = safe_int(user_id)
410 410 c.user = User.get_or_404(user_id)
411 411 if c.user.username == User.DEFAULT_USER:
412 412 h.flash(_("You can't edit this user"), category='warning')
413 413 return redirect(h.route_path('users'))
414 414
415 415 c.active = 'global_perms'
416 416
417 417 c.default_user = User.get_default_user()
418 418 defaults = c.user.get_dict()
419 419 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
420 420 defaults.update(c.default_user.get_default_perms())
421 421 defaults.update(c.user.get_default_perms())
422 422
423 423 return htmlfill.render(
424 424 render('admin/users/user_edit.mako'),
425 425 defaults=defaults,
426 426 encoding="UTF-8",
427 427 force_defaults=False)
428 428
429 429 @HasPermissionAllDecorator('hg.admin')
430 430 @auth.CSRFRequired()
431 431 def update_global_perms(self, user_id):
432 432 user_id = safe_int(user_id)
433 433 user = User.get_or_404(user_id)
434 434 c.active = 'global_perms'
435 435 try:
436 436 # first stage that verifies the checkbox
437 437 _form = UserIndividualPermissionsForm()
438 438 form_result = _form.to_python(dict(request.POST))
439 439 inherit_perms = form_result['inherit_default_permissions']
440 440 user.inherit_default_permissions = inherit_perms
441 441 Session().add(user)
442 442
443 443 if not inherit_perms:
444 444 # only update the individual ones if we un check the flag
445 445 _form = UserPermissionsForm(
446 446 [x[0] for x in c.repo_create_choices],
447 447 [x[0] for x in c.repo_create_on_write_choices],
448 448 [x[0] for x in c.repo_group_create_choices],
449 449 [x[0] for x in c.user_group_create_choices],
450 450 [x[0] for x in c.fork_choices],
451 451 [x[0] for x in c.inherit_default_permission_choices])()
452 452
453 453 form_result = _form.to_python(dict(request.POST))
454 454 form_result.update({'perm_user_id': user.user_id})
455 455
456 456 PermissionModel().update_user_permissions(form_result)
457 457
458 458 # TODO(marcink): implement global permissions
459 459 # audit_log.store_web('user.edit.permissions')
460 460
461 461 Session().commit()
462 462 h.flash(_('User global permissions updated successfully'),
463 463 category='success')
464 464
465 465 except formencode.Invalid as errors:
466 466 defaults = errors.value
467 467 c.user = user
468 468 return htmlfill.render(
469 469 render('admin/users/user_edit.mako'),
470 470 defaults=defaults,
471 471 errors=errors.error_dict or {},
472 472 prefix_error=False,
473 473 encoding="UTF-8",
474 474 force_defaults=False)
475 475 except Exception:
476 476 log.exception("Exception during permissions saving")
477 477 h.flash(_('An error occurred during permissions saving'),
478 478 category='error')
479 479 return redirect(url('edit_user_global_perms', user_id=user_id))
480 480
481 481 @HasPermissionAllDecorator('hg.admin')
482 482 def edit_perms_summary(self, user_id):
483 483 user_id = safe_int(user_id)
484 484 c.user = User.get_or_404(user_id)
485 485 if c.user.username == User.DEFAULT_USER:
486 486 h.flash(_("You can't edit this user"), category='warning')
487 487 return redirect(h.route_path('users'))
488 488
489 489 c.active = 'perms_summary'
490 490 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
491 491
492 492 return render('admin/users/user_edit.mako')
493 493
494 @HasPermissionAllDecorator('hg.admin')
495 def edit_emails(self, user_id):
496 user_id = safe_int(user_id)
497 c.user = User.get_or_404(user_id)
498 if c.user.username == User.DEFAULT_USER:
499 h.flash(_("You can't edit this user"), category='warning')
500 return redirect(h.route_path('users'))
501
502 c.active = 'emails'
503 c.user_email_map = UserEmailMap.query() \
504 .filter(UserEmailMap.user == c.user).all()
505
506 defaults = c.user.get_dict()
507 return htmlfill.render(
508 render('admin/users/user_edit.mako'),
509 defaults=defaults,
510 encoding="UTF-8",
511 force_defaults=False)
512
513 @HasPermissionAllDecorator('hg.admin')
514 @auth.CSRFRequired()
515 def add_email(self, user_id):
516 user_id = safe_int(user_id)
517 c.user = User.get_or_404(user_id)
518
519 email = request.POST.get('new_email')
520 user_model = UserModel()
521 user_data = c.user.get_api_data()
522 try:
523 user_model.add_extra_email(user_id, email)
524 audit_logger.store_web(
525 'user.edit.email.add',
526 action_data={'email': email, 'user': user_data},
527 user=c.rhodecode_user)
528 Session().commit()
529 h.flash(_("Added new email address `%s` for user account") % email,
530 category='success')
531 except formencode.Invalid as error:
532 msg = error.error_dict['email']
533 h.flash(msg, category='error')
534 except Exception:
535 log.exception("Exception during email saving")
536 h.flash(_('An error occurred during email saving'),
537 category='error')
538 return redirect(url('edit_user_emails', user_id=user_id))
539
540 @HasPermissionAllDecorator('hg.admin')
541 @auth.CSRFRequired()
542 def delete_email(self, user_id):
543 user_id = safe_int(user_id)
544 c.user = User.get_or_404(user_id)
545 email_id = request.POST.get('del_email_id')
546 user_model = UserModel()
547
548 email = UserEmailMap.query().get(email_id).email
549 user_data = c.user.get_api_data()
550 user_model.delete_extra_email(user_id, email_id)
551 audit_logger.store_web(
552 'user.edit.email.delete',
553 action_data={'email': email, 'user': user_data},
554 user=c.rhodecode_user)
555 Session().commit()
556 h.flash(_("Removed email address from user account"), category='success')
557 return redirect(url('edit_user_emails', user_id=user_id))
558
559 @HasPermissionAllDecorator('hg.admin')
560 def edit_ips(self, user_id):
561 user_id = safe_int(user_id)
562 c.user = User.get_or_404(user_id)
563 if c.user.username == User.DEFAULT_USER:
564 h.flash(_("You can't edit this user"), category='warning')
565 return redirect(h.route_path('users'))
566
567 c.active = 'ips'
568 c.user_ip_map = UserIpMap.query() \
569 .filter(UserIpMap.user == c.user).all()
570
571 c.inherit_default_ips = c.user.inherit_default_permissions
572 c.default_user_ip_map = UserIpMap.query() \
573 .filter(UserIpMap.user == User.get_default_user()).all()
574
575 defaults = c.user.get_dict()
576 return htmlfill.render(
577 render('admin/users/user_edit.mako'),
578 defaults=defaults,
579 encoding="UTF-8",
580 force_defaults=False)
581
582 @HasPermissionAllDecorator('hg.admin')
583 @auth.CSRFRequired()
584 def add_ip(self, user_id):
585 user_id = safe_int(user_id)
586 c.user = User.get_or_404(user_id)
587 user_model = UserModel()
588 try:
589 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
590 except Exception as e:
591 ip_list = []
592 log.exception("Exception during ip saving")
593 h.flash(_('An error occurred during ip saving:%s' % (e,)),
594 category='error')
595
596 desc = request.POST.get('description')
597 added = []
598 user_data = c.user.get_api_data()
599 for ip in ip_list:
600 try:
601 user_model.add_extra_ip(user_id, ip, desc)
602 audit_logger.store_web(
603 'user.edit.ip.add',
604 action_data={'ip': ip, 'user': user_data},
605 user=c.rhodecode_user)
606 Session().commit()
607 added.append(ip)
608 except formencode.Invalid as error:
609 msg = error.error_dict['ip']
610 h.flash(msg, category='error')
611 except Exception:
612 log.exception("Exception during ip saving")
613 h.flash(_('An error occurred during ip saving'),
614 category='error')
615 if added:
616 h.flash(
617 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
618 category='success')
619 if 'default_user' in request.POST:
620 return redirect(url('admin_permissions_ips'))
621 return redirect(url('edit_user_ips', user_id=user_id))
622
623 @HasPermissionAllDecorator('hg.admin')
624 @auth.CSRFRequired()
625 def delete_ip(self, user_id):
626 user_id = safe_int(user_id)
627 c.user = User.get_or_404(user_id)
628
629 ip_id = request.POST.get('del_ip_id')
630 user_model = UserModel()
631 user_data = c.user.get_api_data()
632 ip = UserIpMap.query().get(ip_id).ip_addr
633 user_model.delete_extra_ip(user_id, ip_id)
634 audit_logger.store_web(
635 'user.edit.ip.delete',
636 action_data={'ip': ip, 'user': user_data},
637 user=c.rhodecode_user)
638 Session().commit()
639 h.flash(_("Removed ip address from user whitelist"), category='success')
640
641 if 'default_user' in request.POST:
642 return redirect(url('admin_permissions_ips'))
643 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,147 +1,154 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 18 pyroutes.register('gists', '/_admin/gists', []);
19 19 pyroutes.register('new_gist', '/_admin/gists/new', []);
20 20 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
21 21 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
22 22 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
23 23 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
24 24 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
25 25 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
26 26 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
27 27 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
28 28 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
29 29 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
30 30 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
31 31 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
32 32 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
33 33 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
34 34 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
35 35 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
36 36 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
37 37 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
38 38 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
39 39 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
40 40 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
41 41 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
42 42 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 43 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
44 44 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 45 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
46 46 pyroutes.register('favicon', '/favicon.ico', []);
47 47 pyroutes.register('robots', '/robots.txt', []);
48 48 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
49 49 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
50 50 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
51 51 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
52 52 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
53 53 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
54 54 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
55 55 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
56 56 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
57 57 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
58 58 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
59 59 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
60 60 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
61 61 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
62 62 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
63 63 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
64 64 pyroutes.register('ops_ping', '_admin/ops/ping', []);
65 65 pyroutes.register('admin_home', '/_admin', []);
66 66 pyroutes.register('admin_audit_logs', '_admin/audit_logs', []);
67 67 pyroutes.register('pull_requests_global_0', '_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
68 68 pyroutes.register('pull_requests_global_1', '_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
69 69 pyroutes.register('pull_requests_global', '_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
70 70 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
71 71 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
72 72 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
73 73 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
74 74 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
75 75 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
76 pyroutes.register('admin_permissions_ips', '_admin/permissions/ips', []);
76 77 pyroutes.register('users', '_admin/users', []);
77 78 pyroutes.register('users_data', '_admin/users_data', []);
78 79 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
79 80 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
80 81 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
82 pyroutes.register('edit_user_emails', '_admin/users/%(user_id)s/edit/emails', ['user_id']);
83 pyroutes.register('edit_user_emails_add', '_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
84 pyroutes.register('edit_user_emails_delete', '_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
85 pyroutes.register('edit_user_ips', '_admin/users/%(user_id)s/edit/ips', ['user_id']);
86 pyroutes.register('edit_user_ips_add', '_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
87 pyroutes.register('edit_user_ips_delete', '_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
81 88 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
82 89 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
83 90 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
84 91 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
85 92 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
86 93 pyroutes.register('channelstream_proxy', '/_channelstream', []);
87 94 pyroutes.register('login', '/_admin/login', []);
88 95 pyroutes.register('logout', '/_admin/logout', []);
89 96 pyroutes.register('register', '/_admin/register', []);
90 97 pyroutes.register('reset_password', '/_admin/password_reset', []);
91 98 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
92 99 pyroutes.register('home', '/', []);
93 100 pyroutes.register('user_autocomplete_data', '/_users', []);
94 101 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
95 102 pyroutes.register('repo_list_data', '/_repos', []);
96 103 pyroutes.register('goto_switcher_data', '/_goto_data', []);
97 104 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
98 105 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
99 106 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
100 107 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
101 108 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
102 109 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
103 110 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
104 111 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
105 112 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
106 113 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
107 114 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
108 115 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
109 116 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
110 117 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
111 118 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
112 119 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
113 120 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
114 121 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
115 122 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
116 123 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
117 124 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
118 125 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
119 126 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
120 127 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
121 128 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
122 129 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
123 130 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
124 131 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
125 132 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
126 133 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
127 134 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
128 135 pyroutes.register('search', '/_admin/search', []);
129 136 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
130 137 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
131 138 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
132 139 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
133 140 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
134 141 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
135 142 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
136 143 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
137 144 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
138 145 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
139 146 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
140 147 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
141 148 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
142 149 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
143 150 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
144 151 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
145 152 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
146 153 pyroutes.register('apiv2', '/_admin/api', []);
147 154 }
@@ -1,67 +1,67 b''
1 1
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Default IP Whitelist For All Users')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="ips_wrap">
9 9 <table class="rctable ip-whitelist">
10 10 <tr>
11 11 <th>IP Address</th>
12 12 <th>IP Range</th>
13 13 <th>Description</th>
14 14 <th></th>
15 15 </tr>
16 16 %if c.user_ip_map:
17 17 %for ip in c.user_ip_map:
18 18 <tr>
19 19 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
20 20 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
21 21 <td class="td-description"><div class="ip">${ip.description}</div></td>
22 22 <td class="td-action">
23 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='delete')}
23 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST')}
24 24 ${h.hidden('del_ip_id',ip.ip_id)}
25 25 ${h.hidden('default_user', 'True')}
26 26 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
27 27 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
28 28 ${h.end_form()}
29 29 </td>
30 30 </tr>
31 31 %endfor
32 32 %else:
33 33 <tr>
34 34 <td class="ip">${_('All IP addresses are allowed')}</td>
35 35 <td></td>
36 36 <td></td>
37 37 <td></td>
38 38 </tr>
39 39 %endif
40 40 </table>
41 41 </div>
42 42
43 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='put')}
43 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST')}
44 44 <div class="form">
45 45 <!-- fields -->
46 46 <div class="fields">
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="new_ip">${_('New IP Address')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.hidden('default_user', 'True')}
53 53 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
54 54 <span class="help-block">${_('Enter a comma separated list of IP Addresses like 127.0.0.1,\n'
55 55 'or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n'
56 56 'To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax')}</span>
57 57 </div>
58 58 </div>
59 59 <div class="buttons">
60 60 ${h.submit('save',_('Add'),class_="btn")}
61 61 ${h.reset('reset',_('Reset'),class_="btn")}
62 62 </div>
63 63 </div>
64 64 </div>
65 65 ${h.end_form()}
66 66 </div>
67 67 </div>
@@ -1,56 +1,56 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user settings') % c.user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Users'),h.route_path('users'))}
15 15 &raquo;
16 16 % if c.user.active:
17 17 ${c.user.username}
18 18 % else:
19 19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 20 % endif
21 21
22 22 </%def>
23 23
24 24 <%def name="menu_bar_nav()">
25 25 ${self.menu_items(active='admin')}
26 26 </%def>
27 27
28 28 <%def name="main()">
29 29 <div class="box user_settings">
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 </div>
33 33
34 34 ##main
35 35 <div class="sidebar-col-wrapper">
36 36 <div class="sidebar">
37 37 <ul class="nav nav-pills nav-stacked">
38 38 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
39 39 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
40 40 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
41 41 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
42 42 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
43 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
44 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
43 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
44 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.route_path('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
45 45 <li class="${'active' if c.active=='groups' else ''}"><a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a></li>
46 46 <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('User audit')}</a></li>
47 47 </ul>
48 48 </div>
49 49
50 50 <div class="main-content-full-width">
51 51 <%include file="/admin/users/user_edit_${c.active}.mako"/>
52 52 </div>
53 53 </div>
54 54 </div>
55 55
56 56 </%def>
@@ -1,157 +1,157 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="apikeys_wrap">
7 7 <p>
8 8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 10 </p>
11 11 <table class="rctable auth_tokens">
12 12 <tr>
13 13 <th>${_('Token')}</th>
14 14 <th>${_('Scope')}</th>
15 15 <th>${_('Description')}</th>
16 16 <th>${_('Role')}</th>
17 17 <th>${_('Expiration')}</th>
18 18 <th>${_('Action')}</th>
19 19 </tr>
20 20 %if c.user_auth_tokens:
21 21 %for auth_token in c.user_auth_tokens:
22 22 <tr class="${'expired' if auth_token.expired else ''}">
23 23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
24 24 <td class="td">${auth_token.scope_humanized}</td>
25 25 <td class="td-wrap">${auth_token.description}</td>
26 26 <td class="td-tags">
27 27 <span class="tag disabled">${auth_token.role_humanized}</span>
28 28 </td>
29 29 <td class="td-exp">
30 30 %if auth_token.expires == -1:
31 31 ${_('never')}
32 32 %else:
33 33 %if auth_token.expired:
34 34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
35 35 %else:
36 36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
37 37 %endif
38 38 %endif
39 39 </td>
40 40 <td class="td-action">
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='post')}
42 ${h.hidden('del_auth_token',auth_token.api_key)}
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='POST')}
42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
43 43 <button class="btn btn-link btn-danger" type="submit"
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
45 45 ${_('Delete')}
46 46 </button>
47 47 ${h.end_form()}
48 48 </td>
49 49 </tr>
50 50 %endfor
51 51 %else:
52 52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
53 53 %endif
54 54 </table>
55 55 </div>
56 56
57 57 <div class="user_auth_tokens">
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='post')}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='POST')}
59 59 <div class="form form-vertical">
60 60 <!-- fields -->
61 61 <div class="fields">
62 62 <div class="field">
63 63 <div class="label">
64 64 <label for="new_email">${_('New authentication token')}:</label>
65 65 </div>
66 66 <div class="input">
67 67 ${h.text('description', class_='medium', placeholder=_('Description'))}
68 68 ${h.select('lifetime', '', c.lifetime_options)}
69 69 ${h.select('role', '', c.role_options)}
70 70
71 71 % if c.allow_scoped_tokens:
72 72 ${h.hidden('scope_repo_id')}
73 73 % else:
74 74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
75 75 % endif
76 76 </div>
77 77 <p class="help-block">
78 78 ${_('Repository scope works only with tokens with VCS type.')}
79 79 </p>
80 80 </div>
81 81 <div class="buttons">
82 82 ${h.submit('save',_('Add'),class_="btn")}
83 83 ${h.reset('reset',_('Reset'),class_="btn")}
84 84 </div>
85 85 </div>
86 86 </div>
87 87 ${h.end_form()}
88 88 </div>
89 89 </div>
90 90 </div>
91 91
92 92 <script>
93 93
94 94 $(document).ready(function(){
95 95 var select2Options = {
96 96 'containerCssClass': "drop-menu",
97 97 'dropdownCssClass': "drop-menu-dropdown",
98 98 'dropdownAutoWidth': true
99 99 };
100 100 $("#lifetime").select2(select2Options);
101 101 $("#role").select2(select2Options);
102 102
103 103 var repoFilter = function(data) {
104 104 var results = [];
105 105
106 106 if (!data.results[0]) {
107 107 return data
108 108 }
109 109
110 110 $.each(data.results[0].children, function() {
111 111 // replace name to ID for submision
112 112 this.id = this.obj.repo_id;
113 113 results.push(this);
114 114 });
115 115
116 116 data.results[0].children = results;
117 117 return data;
118 118 };
119 119
120 120 $("#scope_repo_id_disabled").select2(select2Options);
121 121
122 122 $("#scope_repo_id").select2({
123 123 cachedDataSource: {},
124 124 minimumInputLength: 2,
125 125 placeholder: "${_('repository scope')}",
126 126 dropdownAutoWidth: true,
127 127 containerCssClass: "drop-menu",
128 128 dropdownCssClass: "drop-menu-dropdown",
129 129 formatResult: formatResult,
130 130 query: $.debounce(250, function(query){
131 131 self = this;
132 132 var cacheKey = query.term;
133 133 var cachedData = self.cachedDataSource[cacheKey];
134 134
135 135 if (cachedData) {
136 136 query.callback({results: cachedData.results});
137 137 } else {
138 138 $.ajax({
139 139 url: pyroutes.url('repo_list_data'),
140 140 data: {'query': query.term},
141 141 dataType: 'json',
142 142 type: 'GET',
143 143 success: function(data) {
144 144 data = repoFilter(data);
145 145 self.cachedDataSource[cacheKey] = data;
146 146 query.callback({results: data.results});
147 147 },
148 148 error: function(data, textStatus, errorThrown) {
149 149 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
150 150 }
151 151 })
152 152 }
153 153 })
154 154 });
155 155
156 156 });
157 157 </script>
@@ -1,71 +1,71 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Additional Email Addresses')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="emails_wrap">
9 9 <table class="rctable account_emails useremails">
10 10 <tr>
11 11 <td class="td-user">
12 12 ${base.gravatar(c.user.email, 16)}
13 13 <span class="user email">${c.user.email}</span>
14 14 </td>
15 15 <td class="td-tags">
16 16 <span class="tag">${_('Primary')}</span>
17 17 </td>
18 18 </tr>
19 19 %if c.user_email_map:
20 20 %for em in c.user_email_map:
21 21 <tr>
22 22 <td class="td-user">
23 23 ${base.gravatar(em.email, 16)}
24 24 <span class="user email">${em.email}</span>
25 25 </td>
26 26 <td class="td-action">
27 ${h.secure_form(url('edit_user_emails', user_id=c.user.user_id),method='delete')}
27 ${h.secure_form(h.route_path('edit_user_emails_delete', user_id=c.user.user_id), method='POST')}
28 28 ${h.hidden('del_email_id',em.email_id)}
29 29 <button class="btn btn-link btn-danger" type="submit"
30 30 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
31 31 ${_('Delete')}
32 32 </button>
33 33 ${h.end_form()}
34 34 </td>
35 35 </tr>
36 36 %endfor
37 37 %else:
38 38 <tr class="noborder">
39 39 <td colspan="3">
40 40 <div class="td-email">
41 41 ${_('No additional emails specified')}
42 42 </div>
43 43 </td>
44 44 </tr>
45 45 %endif
46 46 </table>
47 47 </div>
48 48
49 ${h.secure_form(url('edit_user_emails', user_id=c.user.user_id),method='put')}
49 ${h.secure_form(h.route_path('edit_user_emails_add', user_id=c.user.user_id), method='POST')}
50 50 <div class="form">
51 51 <!-- fields -->
52 52 <div class="fields">
53 53 <div class="field">
54 54 <div class="label">
55 55 <label for="new_email">${_('New email address')}:</label>
56 56 </div>
57 57 <div class="input">
58 58 ${h.text('new_email', class_='medium')}
59 59 </div>
60 60 </div>
61 61 <div class="buttons">
62 62 ${h.submit('save',_('Add'),class_="btn btn-small")}
63 63 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
64 64 </div>
65 65 </div>
66 66 </div>
67 67 ${h.end_form()}
68 68 </div>
69 69 </div>
70 70
71 71
@@ -1,78 +1,78 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Custom IP Whitelist')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="ips_wrap">
7 7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
8 8 <table class="rctable ip-whitelist">
9 9 <tr>
10 10 <th>${_('IP Address')}</th>
11 11 <th>${_('IP Range')}</th>
12 12 <th>${_('Description')}</th>
13 13 <th></th>
14 14 </tr>
15 15 %if c.default_user_ip_map and c.inherit_default_ips:
16 16 %for ip in c.default_user_ip_map:
17 17 <tr>
18 18 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
19 19 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
20 20 <td class="td-description">${h.literal(_('Inherited from %s') % h.link_to('*default*',h.url('admin_permissions_ips')))}</td>
21 21 <td></td>
22 22 </tr>
23 23 %endfor
24 24 %endif
25 25
26 26 %if c.user_ip_map:
27 27 %for ip in c.user_ip_map:
28 28 <tr>
29 29 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
30 30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
31 31 <td class="td-description"><div class="ip">${ip.description}</div></td>
32 32 <td class="td-action">
33 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='delete')}
33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST')}
34 34 ${h.hidden('del_ip_id',ip.ip_id)}
35 35 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
36 36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
37 37 ${h.end_form()}
38 38 </td>
39 39 </tr>
40 40 %endfor
41 41 %endif
42 42 %if not c.default_user_ip_map and not c.user_ip_map:
43 43 <tr>
44 44 <td><h2 class="ip">${_('All IP addresses are allowed')}</h2></td>
45 45 <td></td>
46 46 <td></td>
47 47 <td></td>
48 48 </tr>
49 49 %endif
50 50 </table>
51 51 </div>
52 52
53 53 <div>
54 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='put')}
54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST')}
55 55 <div class="form">
56 56 <!-- fields -->
57 57 <div class="fields">
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="new_ip">${_('New IP Address')}:</label>
61 61 </div>
62 62 <div class="input">
63 63 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
64 64 <span class="help-block">${_('Enter comma separated list of ip addresses like 127.0.0.1,\n'
65 65 'or use a ip address with a mask 127.0.0.1/24, to create a network range.\n'
66 66 'To specify multiple address range use 127.0.0.1-127.0.0.10 syntax')}</span>
67 67 </div>
68 68 </div>
69 69 <div class="buttons">
70 70 ${h.submit('save',_('Add'),class_="btn btn-small")}
71 71 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
72 72 </div>
73 73 </div>
74 74 </div>
75 75 ${h.end_form()}
76 76 </div>
77 77 </div>
78 78 </div>
@@ -1,213 +1,229 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22 from rhodecode.model.db import User, UserIpMap
23 23 from rhodecode.model.permission import PermissionModel
24 24 from rhodecode.tests import (
25 25 TestController, url, clear_all_caches, assert_session_flash)
26 26
27 27
28 def route_path(name, params=None, **kwargs):
29 import urllib
30 from rhodecode.apps._base import ADMIN_PREFIX
31
32 base_url = {
33 'edit_user_ips':
34 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
35 'edit_user_ips_add':
36 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
37 'edit_user_ips_delete':
38 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
39 }[name].format(**kwargs)
40
41 if params:
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 return base_url
44
45
28 46 class TestAdminPermissionsController(TestController):
29 47
30 48 @pytest.fixture(scope='class', autouse=True)
31 49 def prepare(self, request):
32 50 # cleanup and reset to default permissions after
33 51 @request.addfinalizer
34 52 def cleanup():
35 53 PermissionModel().create_default_user_permissions(
36 54 User.get_default_user(), force=True)
37 55
38 56 def test_index_application(self):
39 57 self.log_user()
40 58 self.app.get(url('admin_permissions_application'))
41 59
42 60 @pytest.mark.parametrize(
43 61 'anonymous, default_register, default_register_message, default_password_reset,'
44 62 'default_extern_activate, expect_error, expect_form_error', [
45 63 (True, 'hg.register.none', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
46 64 False, False),
47 65 (True, 'hg.register.manual_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.auto',
48 66 False, False),
49 67 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
50 68 False, False),
51 69 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
52 70 False, False),
53 71 (True, 'hg.register.XXX', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
54 72 False, True),
55 73 (True, '', '', 'hg.password_reset.enabled', '', True, False),
56 74 ])
57 75 def test_update_application_permissions(
58 76 self, anonymous, default_register, default_register_message, default_password_reset,
59 77 default_extern_activate, expect_error, expect_form_error):
60 78
61 79 self.log_user()
62 80
63 81 # TODO: anonymous access set here to False, breaks some other tests
64 82 params = {
65 83 'csrf_token': self.csrf_token,
66 84 'anonymous': anonymous,
67 85 'default_register': default_register,
68 86 'default_register_message': default_register_message,
69 87 'default_password_reset': default_password_reset,
70 88 'default_extern_activate': default_extern_activate,
71 89 }
72 90 response = self.app.post(url('admin_permissions_application'),
73 91 params=params)
74 92 if expect_form_error:
75 93 assert response.status_int == 200
76 94 response.mustcontain('Value must be one of')
77 95 else:
78 96 if expect_error:
79 97 msg = 'Error occurred during update of permissions'
80 98 else:
81 99 msg = 'Application permissions updated successfully'
82 100 assert_session_flash(response, msg)
83 101
84 102 def test_index_object(self):
85 103 self.log_user()
86 104 self.app.get(url('admin_permissions_object'))
87 105
88 106 @pytest.mark.parametrize(
89 107 'repo, repo_group, user_group, expect_error, expect_form_error', [
90 108 ('repository.none', 'group.none', 'usergroup.none', False, False),
91 109 ('repository.read', 'group.read', 'usergroup.read', False, False),
92 110 ('repository.write', 'group.write', 'usergroup.write',
93 111 False, False),
94 112 ('repository.admin', 'group.admin', 'usergroup.admin',
95 113 False, False),
96 114 ('repository.XXX', 'group.admin', 'usergroup.admin', False, True),
97 115 ('', '', '', True, False),
98 116 ])
99 117 def test_update_object_permissions(self, repo, repo_group, user_group,
100 118 expect_error, expect_form_error):
101 119 self.log_user()
102 120
103 121 params = {
104 122 'csrf_token': self.csrf_token,
105 123 'default_repo_perm': repo,
106 124 'overwrite_default_repo': False,
107 125 'default_group_perm': repo_group,
108 126 'overwrite_default_group': False,
109 127 'default_user_group_perm': user_group,
110 128 'overwrite_default_user_group': False,
111 129 }
112 130 response = self.app.post(url('admin_permissions_object'),
113 131 params=params)
114 132 if expect_form_error:
115 133 assert response.status_int == 200
116 134 response.mustcontain('Value must be one of')
117 135 else:
118 136 if expect_error:
119 137 msg = 'Error occurred during update of permissions'
120 138 else:
121 139 msg = 'Object permissions updated successfully'
122 140 assert_session_flash(response, msg)
123 141
124 142 def test_index_global(self):
125 143 self.log_user()
126 144 self.app.get(url('admin_permissions_global'))
127 145
128 146 @pytest.mark.parametrize(
129 147 'repo_create, repo_create_write, user_group_create, repo_group_create,'
130 148 'fork_create, inherit_default_permissions, expect_error,'
131 149 'expect_form_error', [
132 150 ('hg.create.none', 'hg.create.write_on_repogroup.false',
133 151 'hg.usergroup.create.false', 'hg.repogroup.create.false',
134 152 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
135 153 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
136 154 'hg.usergroup.create.true', 'hg.repogroup.create.true',
137 155 'hg.fork.repository', 'hg.inherit_default_perms.false',
138 156 False, False),
139 157 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
140 158 'hg.usergroup.create.true', 'hg.repogroup.create.true',
141 159 'hg.fork.repository', 'hg.inherit_default_perms.false',
142 160 False, True),
143 161 ('', '', '', '', '', '', True, False),
144 162 ])
145 163 def test_update_global_permissions(
146 164 self, repo_create, repo_create_write, user_group_create,
147 165 repo_group_create, fork_create, inherit_default_permissions,
148 166 expect_error, expect_form_error):
149 167 self.log_user()
150 168
151 169 params = {
152 170 'csrf_token': self.csrf_token,
153 171 'default_repo_create': repo_create,
154 172 'default_repo_create_on_write': repo_create_write,
155 173 'default_user_group_create': user_group_create,
156 174 'default_repo_group_create': repo_group_create,
157 175 'default_fork_create': fork_create,
158 176 'default_inherit_default_permissions': inherit_default_permissions
159 177 }
160 178 response = self.app.post(url('admin_permissions_global'),
161 179 params=params)
162 180 if expect_form_error:
163 181 assert response.status_int == 200
164 182 response.mustcontain('Value must be one of')
165 183 else:
166 184 if expect_error:
167 185 msg = 'Error occurred during update of permissions'
168 186 else:
169 187 msg = 'Global permissions updated successfully'
170 188 assert_session_flash(response, msg)
171 189
172 190 def test_index_ips(self):
173 191 self.log_user()
174 192 response = self.app.get(url('admin_permissions_ips'))
175 193 # TODO: Test response...
176 194 response.mustcontain('All IP addresses are allowed')
177 195
178 196 def test_add_delete_ips(self):
179 197 self.log_user()
180 198 clear_all_caches()
181 199
182 200 # ADD
183 201 default_user_id = User.get_default_user().user_id
184 response = self.app.post(
185 url('edit_user_ips', user_id=default_user_id),
186 params={'new_ip': '127.0.0.0/24', '_method': 'put',
187 'csrf_token': self.csrf_token})
202 self.app.post(
203 route_path('edit_user_ips_add', user_id=default_user_id),
204 params={'new_ip': '127.0.0.0/24', 'csrf_token': self.csrf_token})
188 205
189 206 response = self.app.get(url('admin_permissions_ips'))
190 207 response.mustcontain('127.0.0.0/24')
191 208 response.mustcontain('127.0.0.0 - 127.0.0.255')
192 209
193 210 # DELETE
194 211 default_user_id = User.get_default_user().user_id
195 212 del_ip_id = UserIpMap.query().filter(UserIpMap.user_id ==
196 213 default_user_id).first().ip_id
197 214
198 215 response = self.app.post(
199 url('edit_user_ips', user_id=default_user_id),
200 params={'_method': 'delete', 'del_ip_id': del_ip_id,
201 'csrf_token': self.csrf_token})
216 route_path('edit_user_ips_delete', user_id=default_user_id),
217 params={'del_ip_id': del_ip_id, 'csrf_token': self.csrf_token})
202 218
203 219 assert_session_flash(response, 'Removed ip address from user whitelist')
204 220
205 221 clear_all_caches()
206 222 response = self.app.get(url('admin_permissions_ips'))
207 223 response.mustcontain('All IP addresses are allowed')
208 224 response.mustcontain(no=['127.0.0.0/24'])
209 225 response.mustcontain(no=['127.0.0.0 - 127.0.0.255'])
210 226
211 227 def test_index_overview(self):
212 228 self.log_user()
213 229 self.app.get(url('admin_permissions_overview'))
@@ -1,575 +1,512 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22 from sqlalchemy.orm.exc import NoResultFound
23 23
24 24 from rhodecode.lib.auth import check_password
25 25 from rhodecode.lib import helpers as h
26 26 from rhodecode.model import validators
27 27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.user import UserModel
30 30 from rhodecode.tests import (
31 31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
32 32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
33 33 from rhodecode.tests.fixture import Fixture
34 34 from rhodecode.tests.utils import AssertResponse
35 35
36 36 fixture = Fixture()
37 37
38 38
39 39 def route_path(name, params=None, **kwargs):
40 40 import urllib
41 41 from rhodecode.apps._base import ADMIN_PREFIX
42 42
43 43 base_url = {
44 44 'users_data':
45 45 ADMIN_PREFIX + '/users_data',
46 46 }[name].format(**kwargs)
47 47
48 48 if params:
49 49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 50 return base_url
51 51
52 52
53 53 class TestAdminUsersController(TestController):
54 54 test_user_1 = 'testme'
55 55 destroy_users = set()
56 56
57 57 @classmethod
58 58 def teardown_method(cls, method):
59 59 fixture.destroy_users(cls.destroy_users)
60 60
61 61 def test_create(self, xhr_header):
62 62 self.log_user()
63 63 username = 'newtestuser'
64 64 password = 'test12'
65 65 password_confirmation = password
66 66 name = 'name'
67 67 lastname = 'lastname'
68 68 email = 'mail@mail.com'
69 69
70 70 self.app.get(url('new_user'))
71 71
72 72 response = self.app.post(url('users'), params={
73 73 'username': username,
74 74 'password': password,
75 75 'password_confirmation': password_confirmation,
76 76 'firstname': name,
77 77 'active': True,
78 78 'lastname': lastname,
79 79 'extern_name': 'rhodecode',
80 80 'extern_type': 'rhodecode',
81 81 'email': email,
82 82 'csrf_token': self.csrf_token,
83 83 })
84 84 user_link = link_to(
85 85 username,
86 86 url('edit_user', user_id=User.get_by_username(username).user_id))
87 87 assert_session_flash(response, 'Created user %s' % (user_link,))
88 88 self.destroy_users.add(username)
89 89
90 90 new_user = User.query().filter(User.username == username).one()
91 91
92 92 assert new_user.username == username
93 93 assert check_password(password, new_user.password)
94 94 assert new_user.name == name
95 95 assert new_user.lastname == lastname
96 96 assert new_user.email == email
97 97
98 98 response = self.app.get(route_path('users_data'),
99 99 extra_environ=xhr_header)
100 100 response.mustcontain(username)
101 101
102 102 def test_create_err(self):
103 103 self.log_user()
104 104 username = 'new_user'
105 105 password = ''
106 106 name = 'name'
107 107 lastname = 'lastname'
108 108 email = 'errmail.com'
109 109
110 110 self.app.get(url('new_user'))
111 111
112 112 response = self.app.post(url('users'), params={
113 113 'username': username,
114 114 'password': password,
115 115 'name': name,
116 116 'active': False,
117 117 'lastname': lastname,
118 118 'email': email,
119 119 'csrf_token': self.csrf_token,
120 120 })
121 121
122 122 msg = validators.ValidUsername(
123 123 False, {})._messages['system_invalid_username']
124 124 msg = h.html_escape(msg % {'username': 'new_user'})
125 125 response.mustcontain('<span class="error-message">%s</span>' % msg)
126 126 response.mustcontain(
127 127 '<span class="error-message">Please enter a value</span>')
128 128 response.mustcontain(
129 129 '<span class="error-message">An email address must contain a'
130 130 ' single @</span>')
131 131
132 132 def get_user():
133 133 Session().query(User).filter(User.username == username).one()
134 134
135 135 with pytest.raises(NoResultFound):
136 136 get_user()
137 137
138 138 def test_new(self):
139 139 self.log_user()
140 140 self.app.get(url('new_user'))
141 141
142 142 @pytest.mark.parametrize("name, attrs", [
143 143 ('firstname', {'firstname': 'new_username'}),
144 144 ('lastname', {'lastname': 'new_username'}),
145 145 ('admin', {'admin': True}),
146 146 ('admin', {'admin': False}),
147 147 ('extern_type', {'extern_type': 'ldap'}),
148 148 ('extern_type', {'extern_type': None}),
149 149 ('extern_name', {'extern_name': 'test'}),
150 150 ('extern_name', {'extern_name': None}),
151 151 ('active', {'active': False}),
152 152 ('active', {'active': True}),
153 153 ('email', {'email': 'some@email.com'}),
154 154 ('language', {'language': 'de'}),
155 155 ('language', {'language': 'en'}),
156 156 # ('new_password', {'new_password': 'foobar123',
157 157 # 'password_confirmation': 'foobar123'})
158 158 ])
159 159 def test_update(self, name, attrs):
160 160 self.log_user()
161 161 usr = fixture.create_user(self.test_user_1, password='qweqwe',
162 162 email='testme@rhodecode.org',
163 163 extern_type='rhodecode',
164 164 extern_name=self.test_user_1,
165 165 skip_if_exists=True)
166 166 Session().commit()
167 167 self.destroy_users.add(self.test_user_1)
168 168 params = usr.get_api_data()
169 169 cur_lang = params['language'] or 'en'
170 170 params.update({
171 171 'password_confirmation': '',
172 172 'new_password': '',
173 173 'language': cur_lang,
174 174 '_method': 'put',
175 175 'csrf_token': self.csrf_token,
176 176 })
177 177 params.update({'new_password': ''})
178 178 params.update(attrs)
179 179 if name == 'email':
180 180 params['emails'] = [attrs['email']]
181 181 elif name == 'extern_type':
182 182 # cannot update this via form, expected value is original one
183 183 params['extern_type'] = "rhodecode"
184 184 elif name == 'extern_name':
185 185 # cannot update this via form, expected value is original one
186 186 params['extern_name'] = self.test_user_1
187 187 # special case since this user is not
188 188 # logged in yet his data is not filled
189 189 # so we use creation data
190 190
191 191 response = self.app.post(url('user', user_id=usr.user_id), params)
192 192 assert response.status_int == 302
193 193 assert_session_flash(response, 'User updated successfully')
194 194
195 195 updated_user = User.get_by_username(self.test_user_1)
196 196 updated_params = updated_user.get_api_data()
197 197 updated_params.update({'password_confirmation': ''})
198 198 updated_params.update({'new_password': ''})
199 199
200 200 del params['_method']
201 201 del params['csrf_token']
202 202 assert params == updated_params
203 203
204 204 def test_update_and_migrate_password(
205 205 self, autologin_user, real_crypto_backend):
206 206 from rhodecode.lib import auth
207 207
208 208 # create new user, with sha256 password
209 209 temp_user = 'test_admin_sha256'
210 210 user = fixture.create_user(temp_user)
211 211 user.password = auth._RhodeCodeCryptoSha256().hash_create(
212 212 b'test123')
213 213 Session().add(user)
214 214 Session().commit()
215 215 self.destroy_users.add('test_admin_sha256')
216 216
217 217 params = user.get_api_data()
218 218
219 219 params.update({
220 220 'password_confirmation': 'qweqwe123',
221 221 'new_password': 'qweqwe123',
222 222 'language': 'en',
223 223 '_method': 'put',
224 224 'csrf_token': autologin_user.csrf_token,
225 225 })
226 226
227 227 response = self.app.post(url('user', user_id=user.user_id), params)
228 228 assert response.status_int == 302
229 229 assert_session_flash(response, 'User updated successfully')
230 230
231 231 # new password should be bcrypted, after log-in and transfer
232 232 user = User.get_by_username(temp_user)
233 233 assert user.password.startswith('$')
234 234
235 235 updated_user = User.get_by_username(temp_user)
236 236 updated_params = updated_user.get_api_data()
237 237 updated_params.update({'password_confirmation': 'qweqwe123'})
238 238 updated_params.update({'new_password': 'qweqwe123'})
239 239
240 240 del params['_method']
241 241 del params['csrf_token']
242 242 assert params == updated_params
243 243
244 244 def test_delete(self):
245 245 self.log_user()
246 246 username = 'newtestuserdeleteme'
247 247
248 248 fixture.create_user(name=username)
249 249
250 250 new_user = Session().query(User)\
251 251 .filter(User.username == username).one()
252 252 response = self.app.post(url('user', user_id=new_user.user_id),
253 253 params={'_method': 'delete',
254 254 'csrf_token': self.csrf_token})
255 255
256 256 assert_session_flash(response, 'Successfully deleted user')
257 257
258 258 def test_delete_owner_of_repository(self):
259 259 self.log_user()
260 260 username = 'newtestuserdeleteme_repo_owner'
261 261 obj_name = 'test_repo'
262 262 usr = fixture.create_user(name=username)
263 263 self.destroy_users.add(username)
264 264 fixture.create_repo(obj_name, cur_user=usr.username)
265 265
266 266 new_user = Session().query(User)\
267 267 .filter(User.username == username).one()
268 268 response = self.app.post(url('user', user_id=new_user.user_id),
269 269 params={'_method': 'delete',
270 270 'csrf_token': self.csrf_token})
271 271
272 272 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
273 273 'Switch owners or remove those repositories:%s' % (username,
274 274 obj_name)
275 275 assert_session_flash(response, msg)
276 276 fixture.destroy_repo(obj_name)
277 277
278 278 def test_delete_owner_of_repository_detaching(self):
279 279 self.log_user()
280 280 username = 'newtestuserdeleteme_repo_owner_detach'
281 281 obj_name = 'test_repo'
282 282 usr = fixture.create_user(name=username)
283 283 self.destroy_users.add(username)
284 284 fixture.create_repo(obj_name, cur_user=usr.username)
285 285
286 286 new_user = Session().query(User)\
287 287 .filter(User.username == username).one()
288 288 response = self.app.post(url('user', user_id=new_user.user_id),
289 289 params={'_method': 'delete',
290 290 'user_repos': 'detach',
291 291 'csrf_token': self.csrf_token})
292 292
293 293 msg = 'Detached 1 repositories'
294 294 assert_session_flash(response, msg)
295 295 fixture.destroy_repo(obj_name)
296 296
297 297 def test_delete_owner_of_repository_deleting(self):
298 298 self.log_user()
299 299 username = 'newtestuserdeleteme_repo_owner_delete'
300 300 obj_name = 'test_repo'
301 301 usr = fixture.create_user(name=username)
302 302 self.destroy_users.add(username)
303 303 fixture.create_repo(obj_name, cur_user=usr.username)
304 304
305 305 new_user = Session().query(User)\
306 306 .filter(User.username == username).one()
307 307 response = self.app.post(url('user', user_id=new_user.user_id),
308 308 params={'_method': 'delete',
309 309 'user_repos': 'delete',
310 310 'csrf_token': self.csrf_token})
311 311
312 312 msg = 'Deleted 1 repositories'
313 313 assert_session_flash(response, msg)
314 314
315 315 def test_delete_owner_of_repository_group(self):
316 316 self.log_user()
317 317 username = 'newtestuserdeleteme_repo_group_owner'
318 318 obj_name = 'test_group'
319 319 usr = fixture.create_user(name=username)
320 320 self.destroy_users.add(username)
321 321 fixture.create_repo_group(obj_name, cur_user=usr.username)
322 322
323 323 new_user = Session().query(User)\
324 324 .filter(User.username == username).one()
325 325 response = self.app.post(url('user', user_id=new_user.user_id),
326 326 params={'_method': 'delete',
327 327 'csrf_token': self.csrf_token})
328 328
329 329 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
330 330 'Switch owners or remove those repository groups:%s' % (username,
331 331 obj_name)
332 332 assert_session_flash(response, msg)
333 333 fixture.destroy_repo_group(obj_name)
334 334
335 335 def test_delete_owner_of_repository_group_detaching(self):
336 336 self.log_user()
337 337 username = 'newtestuserdeleteme_repo_group_owner_detach'
338 338 obj_name = 'test_group'
339 339 usr = fixture.create_user(name=username)
340 340 self.destroy_users.add(username)
341 341 fixture.create_repo_group(obj_name, cur_user=usr.username)
342 342
343 343 new_user = Session().query(User)\
344 344 .filter(User.username == username).one()
345 345 response = self.app.post(url('user', user_id=new_user.user_id),
346 346 params={'_method': 'delete',
347 347 'user_repo_groups': 'delete',
348 348 'csrf_token': self.csrf_token})
349 349
350 350 msg = 'Deleted 1 repository groups'
351 351 assert_session_flash(response, msg)
352 352
353 353 def test_delete_owner_of_repository_group_deleting(self):
354 354 self.log_user()
355 355 username = 'newtestuserdeleteme_repo_group_owner_delete'
356 356 obj_name = 'test_group'
357 357 usr = fixture.create_user(name=username)
358 358 self.destroy_users.add(username)
359 359 fixture.create_repo_group(obj_name, cur_user=usr.username)
360 360
361 361 new_user = Session().query(User)\
362 362 .filter(User.username == username).one()
363 363 response = self.app.post(url('user', user_id=new_user.user_id),
364 364 params={'_method': 'delete',
365 365 'user_repo_groups': 'detach',
366 366 'csrf_token': self.csrf_token})
367 367
368 368 msg = 'Detached 1 repository groups'
369 369 assert_session_flash(response, msg)
370 370 fixture.destroy_repo_group(obj_name)
371 371
372 372 def test_delete_owner_of_user_group(self):
373 373 self.log_user()
374 374 username = 'newtestuserdeleteme_user_group_owner'
375 375 obj_name = 'test_user_group'
376 376 usr = fixture.create_user(name=username)
377 377 self.destroy_users.add(username)
378 378 fixture.create_user_group(obj_name, cur_user=usr.username)
379 379
380 380 new_user = Session().query(User)\
381 381 .filter(User.username == username).one()
382 382 response = self.app.post(url('user', user_id=new_user.user_id),
383 383 params={'_method': 'delete',
384 384 'csrf_token': self.csrf_token})
385 385
386 386 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
387 387 'Switch owners or remove those user groups:%s' % (username,
388 388 obj_name)
389 389 assert_session_flash(response, msg)
390 390 fixture.destroy_user_group(obj_name)
391 391
392 392 def test_delete_owner_of_user_group_detaching(self):
393 393 self.log_user()
394 394 username = 'newtestuserdeleteme_user_group_owner_detaching'
395 395 obj_name = 'test_user_group'
396 396 usr = fixture.create_user(name=username)
397 397 self.destroy_users.add(username)
398 398 fixture.create_user_group(obj_name, cur_user=usr.username)
399 399
400 400 new_user = Session().query(User)\
401 401 .filter(User.username == username).one()
402 402 try:
403 403 response = self.app.post(url('user', user_id=new_user.user_id),
404 404 params={'_method': 'delete',
405 405 'user_user_groups': 'detach',
406 406 'csrf_token': self.csrf_token})
407 407
408 408 msg = 'Detached 1 user groups'
409 409 assert_session_flash(response, msg)
410 410 finally:
411 411 fixture.destroy_user_group(obj_name)
412 412
413 413 def test_delete_owner_of_user_group_deleting(self):
414 414 self.log_user()
415 415 username = 'newtestuserdeleteme_user_group_owner_deleting'
416 416 obj_name = 'test_user_group'
417 417 usr = fixture.create_user(name=username)
418 418 self.destroy_users.add(username)
419 419 fixture.create_user_group(obj_name, cur_user=usr.username)
420 420
421 421 new_user = Session().query(User)\
422 422 .filter(User.username == username).one()
423 423 response = self.app.post(url('user', user_id=new_user.user_id),
424 424 params={'_method': 'delete',
425 425 'user_user_groups': 'delete',
426 426 'csrf_token': self.csrf_token})
427 427
428 428 msg = 'Deleted 1 user groups'
429 429 assert_session_flash(response, msg)
430 430
431 431 def test_edit(self):
432 432 self.log_user()
433 433 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
434 434 self.app.get(url('edit_user', user_id=user.user_id))
435 435
436 436 @pytest.mark.parametrize(
437 437 'repo_create, repo_create_write, user_group_create, repo_group_create,'
438 438 'fork_create, inherit_default_permissions, expect_error,'
439 439 'expect_form_error', [
440 440 ('hg.create.none', 'hg.create.write_on_repogroup.false',
441 441 'hg.usergroup.create.false', 'hg.repogroup.create.false',
442 442 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
443 443 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
444 444 'hg.usergroup.create.false', 'hg.repogroup.create.false',
445 445 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
446 446 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
447 447 'hg.usergroup.create.true', 'hg.repogroup.create.true',
448 448 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
449 449 False),
450 450 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
451 451 'hg.usergroup.create.true', 'hg.repogroup.create.true',
452 452 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
453 453 True),
454 454 ('', '', '', '', '', '', True, False),
455 455 ])
456 456 def test_global_perms_on_user(
457 457 self, repo_create, repo_create_write, user_group_create,
458 458 repo_group_create, fork_create, expect_error, expect_form_error,
459 459 inherit_default_permissions):
460 460 self.log_user()
461 461 user = fixture.create_user('dummy')
462 462 uid = user.user_id
463 463
464 464 # ENABLE REPO CREATE ON A GROUP
465 465 perm_params = {
466 466 'inherit_default_permissions': False,
467 467 'default_repo_create': repo_create,
468 468 'default_repo_create_on_write': repo_create_write,
469 469 'default_user_group_create': user_group_create,
470 470 'default_repo_group_create': repo_group_create,
471 471 'default_fork_create': fork_create,
472 472 'default_inherit_default_permissions': inherit_default_permissions,
473 473 '_method': 'put',
474 474 'csrf_token': self.csrf_token,
475 475 }
476 476 response = self.app.post(
477 477 url('edit_user_global_perms', user_id=uid),
478 478 params=perm_params)
479 479
480 480 if expect_form_error:
481 481 assert response.status_int == 200
482 482 response.mustcontain('Value must be one of')
483 483 else:
484 484 if expect_error:
485 485 msg = 'An error occurred during permissions saving'
486 486 else:
487 487 msg = 'User global permissions updated successfully'
488 488 ug = User.get(uid)
489 489 del perm_params['_method']
490 490 del perm_params['inherit_default_permissions']
491 491 del perm_params['csrf_token']
492 492 assert perm_params == ug.get_default_perms()
493 493 assert_session_flash(response, msg)
494 494 fixture.destroy_user(uid)
495 495
496 496 def test_global_permissions_initial_values(self, user_util):
497 497 self.log_user()
498 498 user = user_util.create_user()
499 499 uid = user.user_id
500 500 response = self.app.get(url('edit_user_global_perms', user_id=uid))
501 501 default_user = User.get_default_user()
502 502 default_permissions = default_user.get_default_perms()
503 503 assert_response = AssertResponse(response)
504 504 expected_permissions = (
505 505 'default_repo_create', 'default_repo_create_on_write',
506 506 'default_fork_create', 'default_repo_group_create',
507 507 'default_user_group_create', 'default_inherit_default_permissions')
508 508 for permission in expected_permissions:
509 509 css_selector = '[name={}][checked=checked]'.format(permission)
510 510 element = assert_response.get_element(css_selector)
511 511 assert element.value == default_permissions[permission]
512 512
513 def test_ips(self):
514 self.log_user()
515 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
516 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
517 response.mustcontain('All IP addresses are allowed')
518
519 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
520 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
521 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
522 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
523 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
524 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
525 ('127_bad_ip', 'foobar', 'foobar', True),
526 ])
527 def test_add_ip(self, test_name, ip, ip_range, failure):
528 self.log_user()
529 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
530 user_id = user.user_id
531
532 response = self.app.post(url('edit_user_ips', user_id=user_id),
533 params={'new_ip': ip, '_method': 'put',
534 'csrf_token': self.csrf_token})
535
536 if failure:
537 assert_session_flash(
538 response, 'Please enter a valid IPv4 or IpV6 address')
539 response = self.app.get(url('edit_user_ips', user_id=user_id))
540 response.mustcontain(no=[ip])
541 response.mustcontain(no=[ip_range])
542
543 else:
544 response = self.app.get(url('edit_user_ips', user_id=user_id))
545 response.mustcontain(ip)
546 response.mustcontain(ip_range)
547
548 # cleanup
549 for del_ip in UserIpMap.query().filter(
550 UserIpMap.user_id == user_id).all():
551 Session().delete(del_ip)
552 Session().commit()
553
554 def test_delete_ip(self):
555 self.log_user()
556 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
557 user_id = user.user_id
558 ip = '127.0.0.1/32'
559 ip_range = '127.0.0.1 - 127.0.0.1'
560 new_ip = UserModel().add_extra_ip(user_id, ip)
561 Session().commit()
562 new_ip_id = new_ip.ip_id
563
564 response = self.app.get(url('edit_user_ips', user_id=user_id))
565 response.mustcontain(ip)
566 response.mustcontain(ip_range)
567
568 self.app.post(url('edit_user_ips', user_id=user_id),
569 params={'_method': 'delete', 'del_ip_id': new_ip_id,
570 'csrf_token': self.csrf_token})
571
572 response = self.app.get(url('edit_user_ips', user_id=user_id))
573 response.mustcontain('All IP addresses are allowed')
574 response.mustcontain(no=[ip])
575 response.mustcontain(no=[ip_range])
General Comments 0
You need to be logged in to leave comments. Login now