##// END OF EJS Templates
admin-users: moved grid browsing to pyramid....
marcink -
r1520:67ca1dd5 default
parent child Browse files
Show More
@@ -1,68 +1,77 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def includeme(config):
27 def includeme(config):
28 settings = config.get_settings()
28 settings = config.get_settings()
29
29
30 # Create admin navigation registry and add it to the pyramid registry.
30 # Create admin navigation registry and add it to the pyramid registry.
31 labs_active = str2bool(settings.get('labs_settings_active', False))
31 labs_active = str2bool(settings.get('labs_settings_active', False))
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 config.registry.registerUtility(navigation_registry)
33 config.registry.registerUtility(navigation_registry)
34
34
35 config.add_route(
35 config.add_route(
36 name='admin_settings_open_source',
36 name='admin_settings_open_source',
37 pattern=ADMIN_PREFIX + '/settings/open_source')
37 pattern=ADMIN_PREFIX + '/settings/open_source')
38 config.add_route(
38 config.add_route(
39 name='admin_settings_vcs_svn_generate_cfg',
39 name='admin_settings_vcs_svn_generate_cfg',
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
41
41
42 config.add_route(
42 config.add_route(
43 name='admin_settings_system',
43 name='admin_settings_system',
44 pattern=ADMIN_PREFIX + '/settings/system')
44 pattern=ADMIN_PREFIX + '/settings/system')
45 config.add_route(
45 config.add_route(
46 name='admin_settings_system_update',
46 name='admin_settings_system_update',
47 pattern=ADMIN_PREFIX + '/settings/system/updates')
47 pattern=ADMIN_PREFIX + '/settings/system/updates')
48
48
49 config.add_route(
49 config.add_route(
50 name='admin_settings_sessions',
50 name='admin_settings_sessions',
51 pattern=ADMIN_PREFIX + '/settings/sessions')
51 pattern=ADMIN_PREFIX + '/settings/sessions')
52 config.add_route(
52 config.add_route(
53 name='admin_settings_sessions_cleanup',
53 name='admin_settings_sessions_cleanup',
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
55
55
56 # users admin
57 config.add_route(
58 name='users',
59 pattern=ADMIN_PREFIX + '/users')
60
61 config.add_route(
62 name='users_data',
63 pattern=ADMIN_PREFIX + '/users_data')
64
56 # user auth tokens
65 # user auth tokens
57 config.add_route(
66 config.add_route(
58 name='edit_user_auth_tokens',
67 name='edit_user_auth_tokens',
59 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens')
68 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens')
60 config.add_route(
69 config.add_route(
61 name='edit_user_auth_tokens_add',
70 name='edit_user_auth_tokens_add',
62 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/new')
71 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/new')
63 config.add_route(
72 config.add_route(
64 name='edit_user_auth_tokens_delete',
73 name='edit_user_auth_tokens_delete',
65 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/delete')
74 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/delete')
66
75
67 # Scan module for configuration decorators.
76 # Scan module for configuration decorators.
68 config.scan()
77 config.scan()
@@ -1,114 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User, UserApiKeys
23 from rhodecode.model.db import User, UserApiKeys
24
24
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.tests import (
25 from rhodecode.tests import (
27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
26 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
30
28
31 fixture = Fixture()
29 fixture = Fixture()
32
30
33
31
32 def route_path(name, params=None, **kwargs):
33 import urllib
34 from rhodecode.apps._base import ADMIN_PREFIX
34
35
35 def route_path(name, **kwargs):
36 base_url = {
36 return {
37 'users':
37 'users':
38 ADMIN_PREFIX + '/users',
38 ADMIN_PREFIX + '/users',
39 'users_data':
39 'users_data':
40 ADMIN_PREFIX + '/users_data',
40 ADMIN_PREFIX + '/users_data',
41 'edit_user_auth_tokens':
41 'edit_user_auth_tokens':
42 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
42 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
43 'edit_user_auth_tokens_add':
43 'edit_user_auth_tokens_add':
44 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
44 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
45 'edit_user_auth_tokens_delete':
45 'edit_user_auth_tokens_delete':
46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
47 }[name].format(**kwargs)
47 }[name].format(**kwargs)
48
48
49 if params:
50 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
51 return base_url
52
49
53
50 class TestAdminUsersView(TestController):
54 class TestAdminUsersView(TestController):
51
55
56 def test_show_users(self):
57 self.log_user()
58 self.app.get(route_path('users'))
59
60 def test_show_users_data(self, xhr_header):
61 self.log_user()
62 response = self.app.get(route_path(
63 'users_data'), extra_environ=xhr_header)
64
65 all_users = User.query().filter(
66 User.username != User.DEFAULT_USER).count()
67 assert response.json['recordsTotal'] == all_users
68
69 def test_show_users_data_filtered(self, xhr_header):
70 self.log_user()
71 response = self.app.get(route_path(
72 'users_data', params={'search[value]': 'empty_search'}),
73 extra_environ=xhr_header)
74
75 all_users = User.query().filter(
76 User.username != User.DEFAULT_USER).count()
77 assert response.json['recordsTotal'] == all_users
78 assert response.json['recordsFiltered'] == 0
79
52 def test_auth_tokens_default_user(self):
80 def test_auth_tokens_default_user(self):
53 self.log_user()
81 self.log_user()
54 user = User.get_default_user()
82 user = User.get_default_user()
55 response = self.app.get(
83 response = self.app.get(
56 route_path('edit_user_auth_tokens', user_id=user.user_id),
84 route_path('edit_user_auth_tokens', user_id=user.user_id),
57 status=302)
85 status=302)
58
86
59 def test_auth_tokens(self):
87 def test_auth_tokens(self):
60 self.log_user()
88 self.log_user()
61
89
62 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
90 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
63 response = self.app.get(
91 response = self.app.get(
64 route_path('edit_user_auth_tokens', user_id=user.user_id))
92 route_path('edit_user_auth_tokens', user_id=user.user_id))
65 for token in user.auth_tokens:
93 for token in user.auth_tokens:
66 response.mustcontain(token)
94 response.mustcontain(token)
67 response.mustcontain('never')
95 response.mustcontain('never')
68
96
69 @pytest.mark.parametrize("desc, lifetime", [
97 @pytest.mark.parametrize("desc, lifetime", [
70 ('forever', -1),
98 ('forever', -1),
71 ('5mins', 60*5),
99 ('5mins', 60*5),
72 ('30days', 60*60*24*30),
100 ('30days', 60*60*24*30),
73 ])
101 ])
74 def test_add_auth_token(self, desc, lifetime, user_util):
102 def test_add_auth_token(self, desc, lifetime, user_util):
75 self.log_user()
103 self.log_user()
76 user = user_util.create_user()
104 user = user_util.create_user()
77 user_id = user.user_id
105 user_id = user.user_id
78
106
79 response = self.app.post(
107 response = self.app.post(
80 route_path('edit_user_auth_tokens_add', user_id=user_id),
108 route_path('edit_user_auth_tokens_add', user_id=user_id),
81 {'description': desc, 'lifetime': lifetime,
109 {'description': desc, 'lifetime': lifetime,
82 'csrf_token': self.csrf_token})
110 'csrf_token': self.csrf_token})
83 assert_session_flash(response, 'Auth token successfully created')
111 assert_session_flash(response, 'Auth token successfully created')
84
112
85 response = response.follow()
113 response = response.follow()
86 user = User.get(user_id)
114 user = User.get(user_id)
87 for auth_token in user.auth_tokens:
115 for auth_token in user.auth_tokens:
88 response.mustcontain(auth_token)
116 response.mustcontain(auth_token)
89
117
90 def test_delete_auth_token(self, user_util):
118 def test_delete_auth_token(self, user_util):
91 self.log_user()
119 self.log_user()
92 user = user_util.create_user()
120 user = user_util.create_user()
93 user_id = user.user_id
121 user_id = user.user_id
94 keys = user.extra_auth_tokens
122 keys = user.extra_auth_tokens
95 assert 2 == len(keys)
123 assert 2 == len(keys)
96
124
97 response = self.app.post(
125 response = self.app.post(
98 route_path('edit_user_auth_tokens_add', user_id=user_id),
126 route_path('edit_user_auth_tokens_add', user_id=user_id),
99 {'description': 'desc', 'lifetime': -1,
127 {'description': 'desc', 'lifetime': -1,
100 'csrf_token': self.csrf_token})
128 'csrf_token': self.csrf_token})
101 assert_session_flash(response, 'Auth token successfully created')
129 assert_session_flash(response, 'Auth token successfully created')
102 response.follow()
130 response.follow()
103
131
104 # now delete our key
132 # now delete our key
105 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
133 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
106 assert 3 == len(keys)
134 assert 3 == len(keys)
107
135
108 response = self.app.post(
136 response = self.app.post(
109 route_path('edit_user_auth_tokens_delete', user_id=user_id),
137 route_path('edit_user_auth_tokens_delete', user_id=user_id),
110 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
138 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
111
139
112 assert_session_flash(response, 'Auth token successfully deleted')
140 assert_session_flash(response, 'Auth token successfully deleted')
113 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
141 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
114 assert 2 == len(keys)
142 assert 2 == len(keys)
@@ -1,141 +1,240 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils import PartialRenderer
30 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.utils2 import safe_int, safe_unicode
32 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.db import User
33 from rhodecode.model.db import User, or_
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class AdminUsersView(BaseAppView):
39 class AdminUsersView(BaseAppView):
40 ALLOW_SCOPED_TOKENS = False
40 ALLOW_SCOPED_TOKENS = False
41 """
41 """
42 This view has alternative version inside EE, if modified please take a look
42 This view has alternative version inside EE, if modified please take a look
43 in there as well.
43 in there as well.
44 """
44 """
45
45
46 def load_default_context(self):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
48 c.auth_user = self.request.user
48 c.auth_user = self.request.user
49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 self._register_global_c(c)
50 self._register_global_c(c)
51 return c
51 return c
52
52
53 def _redirect_for_default_user(self, username):
53 def _redirect_for_default_user(self, username):
54 _ = self.request.translate
54 _ = self.request.translate
55 if username == User.DEFAULT_USER:
55 if username == User.DEFAULT_USER:
56 h.flash(_("You can't edit this user"), category='warning')
56 h.flash(_("You can't edit this user"), category='warning')
57 # TODO(marcink): redirect to 'users' admin panel once this
57 # TODO(marcink): redirect to 'users' admin panel once this
58 # is a pyramid view
58 # is a pyramid view
59 raise HTTPFound('/')
59 raise HTTPFound('/')
60
60
61 def _extract_ordering(self, request):
62 column_index = safe_int(request.GET.get('order[0][column]'))
63 order_dir = request.GET.get(
64 'order[0][dir]', 'desc')
65 order_by = request.GET.get(
66 'columns[%s][data][sort]' % column_index, 'name_raw')
67
68 # translate datatable to DB columns
69 order_by = {
70 'first_name': 'name',
71 'last_name': 'lastname',
72 'last_activity': ''
73 }.get(order_by) or order_by
74
75 search_q = request.GET.get('search[value]')
76 return search_q, order_by, order_dir
77
78 def _extract_chunk(self, request):
79 start = safe_int(request.GET.get('start'), 0)
80 length = safe_int(request.GET.get('length'), 25)
81 draw = safe_int(request.GET.get('draw'))
82 return draw, start, length
83
84 @HasPermissionAllDecorator('hg.admin')
85 @view_config(
86 route_name='users', request_method='GET',
87 renderer='rhodecode:templates/admin/users/users.mako')
88 def users_list(self):
89 c = self.load_default_context()
90 return self._get_template_context(c)
91
92 @HasPermissionAllDecorator('hg.admin')
93 @view_config(
94 # renderer defined below
95 route_name='users_data', request_method='GET', renderer='json',
96 xhr=True)
97 def users_list_data(self):
98 draw, start, limit = self._extract_chunk(self.request)
99 search_q, order_by, order_dir = self._extract_ordering(self.request)
100
101 _render = PartialRenderer('data_table/_dt_elements.mako')
102
103 def user_actions(user_id, username):
104 return _render("user_actions", user_id, username)
105
106 users_data_total_count = User.query()\
107 .filter(User.username != User.DEFAULT_USER) \
108 .count()
109
110 # json generate
111 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112
113 if search_q:
114 like_expression = u'{}%'.format(safe_unicode(search_q))
115 base_q = base_q.filter(or_(
116 User.username.ilike(like_expression),
117 User._email.ilike(like_expression),
118 User.name.ilike(like_expression),
119 User.lastname.ilike(like_expression),
120 ))
121
122 users_data_total_filtered_count = base_q.count()
123
124 sort_col = getattr(User, order_by, None)
125 if sort_col and order_dir == 'asc':
126 base_q = base_q.order_by(sort_col.asc())
127 elif sort_col:
128 base_q = base_q.order_by(sort_col.desc())
129
130 base_q = base_q.offset(start).limit(limit)
131 users_list = base_q.all()
132
133 users_data = []
134 for user in users_list:
135 users_data.append({
136 "username": h.gravatar_with_user(user.username),
137 "email": user.email,
138 "first_name": h.escape(user.name),
139 "last_name": h.escape(user.lastname),
140 "last_login": h.format_date(user.last_login),
141 "last_activity": h.format_date(
142 h.time_to_datetime(user.user_data.get('last_activity', 0))),
143 "active": h.bool2icon(user.active),
144 "active_raw": user.active,
145 "admin": h.bool2icon(user.admin),
146 "extern_type": user.extern_type,
147 "extern_name": user.extern_name,
148 "action": user_actions(user.user_id, user.username),
149 })
150
151 data = ({
152 'draw': draw,
153 'data': users_data,
154 'recordsTotal': users_data_total_count,
155 'recordsFiltered': users_data_total_filtered_count,
156 })
157
158 return data
159
61 @LoginRequired()
160 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
161 @HasPermissionAllDecorator('hg.admin')
63 @view_config(
162 @view_config(
64 route_name='edit_user_auth_tokens', request_method='GET',
163 route_name='edit_user_auth_tokens', request_method='GET',
65 renderer='rhodecode:templates/admin/users/user_edit.mako')
164 renderer='rhodecode:templates/admin/users/user_edit.mako')
66 def auth_tokens(self):
165 def auth_tokens(self):
67 _ = self.request.translate
166 _ = self.request.translate
68 c = self.load_default_context()
167 c = self.load_default_context()
69
168
70 user_id = self.request.matchdict.get('user_id')
169 user_id = self.request.matchdict.get('user_id')
71 c.user = User.get_or_404(user_id, pyramid_exc=True)
170 c.user = User.get_or_404(user_id, pyramid_exc=True)
72 self._redirect_for_default_user(c.user.username)
171 self._redirect_for_default_user(c.user.username)
73
172
74 c.active = 'auth_tokens'
173 c.active = 'auth_tokens'
75
174
76 c.lifetime_values = [
175 c.lifetime_values = [
77 (str(-1), _('forever')),
176 (str(-1), _('forever')),
78 (str(5), _('5 minutes')),
177 (str(5), _('5 minutes')),
79 (str(60), _('1 hour')),
178 (str(60), _('1 hour')),
80 (str(60 * 24), _('1 day')),
179 (str(60 * 24), _('1 day')),
81 (str(60 * 24 * 30), _('1 month')),
180 (str(60 * 24 * 30), _('1 month')),
82 ]
181 ]
83 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
182 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
84 c.role_values = [
183 c.role_values = [
85 (x, AuthTokenModel.cls._get_role_name(x))
184 (x, AuthTokenModel.cls._get_role_name(x))
86 for x in AuthTokenModel.cls.ROLES]
185 for x in AuthTokenModel.cls.ROLES]
87 c.role_options = [(c.role_values, _("Role"))]
186 c.role_options = [(c.role_values, _("Role"))]
88 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
187 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
89 c.user.user_id, show_expired=True)
188 c.user.user_id, show_expired=True)
90 return self._get_template_context(c)
189 return self._get_template_context(c)
91
190
92 def maybe_attach_token_scope(self, token):
191 def maybe_attach_token_scope(self, token):
93 # implemented in EE edition
192 # implemented in EE edition
94 pass
193 pass
95
194
96 @LoginRequired()
195 @LoginRequired()
97 @HasPermissionAllDecorator('hg.admin')
196 @HasPermissionAllDecorator('hg.admin')
98 @CSRFRequired()
197 @CSRFRequired()
99 @view_config(
198 @view_config(
100 route_name='edit_user_auth_tokens_add', request_method='POST')
199 route_name='edit_user_auth_tokens_add', request_method='POST')
101 def auth_tokens_add(self):
200 def auth_tokens_add(self):
102 _ = self.request.translate
201 _ = self.request.translate
103 c = self.load_default_context()
202 c = self.load_default_context()
104
203
105 user_id = self.request.matchdict.get('user_id')
204 user_id = self.request.matchdict.get('user_id')
106 c.user = User.get_or_404(user_id, pyramid_exc=True)
205 c.user = User.get_or_404(user_id, pyramid_exc=True)
107 self._redirect_for_default_user(c.user.username)
206 self._redirect_for_default_user(c.user.username)
108
207
109 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
208 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
110 description = self.request.POST.get('description')
209 description = self.request.POST.get('description')
111 role = self.request.POST.get('role')
210 role = self.request.POST.get('role')
112
211
113 token = AuthTokenModel().create(
212 token = AuthTokenModel().create(
114 c.user.user_id, description, lifetime, role)
213 c.user.user_id, description, lifetime, role)
115 self.maybe_attach_token_scope(token)
214 self.maybe_attach_token_scope(token)
116 Session().commit()
215 Session().commit()
117
216
118 h.flash(_("Auth token successfully created"), category='success')
217 h.flash(_("Auth token successfully created"), category='success')
119 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
120
219
121 @LoginRequired()
220 @LoginRequired()
122 @HasPermissionAllDecorator('hg.admin')
221 @HasPermissionAllDecorator('hg.admin')
123 @CSRFRequired()
222 @CSRFRequired()
124 @view_config(
223 @view_config(
125 route_name='edit_user_auth_tokens_delete', request_method='POST')
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
126 def auth_tokens_delete(self):
225 def auth_tokens_delete(self):
127 _ = self.request.translate
226 _ = self.request.translate
128 c = self.load_default_context()
227 c = self.load_default_context()
129
228
130 user_id = self.request.matchdict.get('user_id')
229 user_id = self.request.matchdict.get('user_id')
131 c.user = User.get_or_404(user_id, pyramid_exc=True)
230 c.user = User.get_or_404(user_id, pyramid_exc=True)
132 self._redirect_for_default_user(c.user.username)
231 self._redirect_for_default_user(c.user.username)
133
232
134 del_auth_token = self.request.POST.get('del_auth_token')
233 del_auth_token = self.request.POST.get('del_auth_token')
135
234
136 if del_auth_token:
235 if del_auth_token:
137 AuthTokenModel().delete(del_auth_token, c.user.user_id)
236 AuthTokenModel().delete(del_auth_token, c.user.user_id)
138 Session().commit()
237 Session().commit()
139 h.flash(_("Auth token successfully deleted"), category='success')
238 h.flash(_("Auth token successfully deleted"), category='success')
140
239
141 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
240 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
@@ -1,1156 +1,1154 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
46 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
47 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
48 # file path eats up everything at the end
48 # file path eats up everything at the end
49 'f_path': r'.*',
49 'f_path': r'.*',
50 # reference types
50 # reference types
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 }
53 }
54
54
55
55
56 def add_route_requirements(route_path, requirements):
56 def add_route_requirements(route_path, requirements):
57 """
57 """
58 Adds regex requirements to pyramid routes using a mapping dict
58 Adds regex requirements to pyramid routes using a mapping dict
59
59
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 '/{action}/{id:\d+}'
61 '/{action}/{id:\d+}'
62
62
63 """
63 """
64 for key, regex in requirements.items():
64 for key, regex in requirements.items():
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 return route_path
66 return route_path
67
67
68
68
69 class JSRoutesMapper(Mapper):
69 class JSRoutesMapper(Mapper):
70 """
70 """
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 """
72 """
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
76 super(JSRoutesMapper, self).__init__(*args, **kw)
76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 self._jsroutes = []
77 self._jsroutes = []
78
78
79 def connect(self, *args, **kw):
79 def connect(self, *args, **kw):
80 """
80 """
81 Wrapper for connect to take an extra argument jsroute=True
81 Wrapper for connect to take an extra argument jsroute=True
82
82
83 :param jsroute: boolean, if True will add the route to the pyroutes list
83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 """
84 """
85 if kw.pop('jsroute', False):
85 if kw.pop('jsroute', False):
86 if not self._named_route_regex.match(args[0]):
86 if not self._named_route_regex.match(args[0]):
87 raise Exception('only named routes can be added to pyroutes')
87 raise Exception('only named routes can be added to pyroutes')
88 self._jsroutes.append(args[0])
88 self._jsroutes.append(args[0])
89
89
90 super(JSRoutesMapper, self).connect(*args, **kw)
90 super(JSRoutesMapper, self).connect(*args, **kw)
91
91
92 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
93 """
93 """
94 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
95 ('show_user', '/profile/%(username)s', ['username'])
95 ('show_user', '/profile/%(username)s', ['username'])
96 """
96 """
97 routepath = route.routepath
97 routepath = route.routepath
98 def replace(matchobj):
98 def replace(matchobj):
99 if matchobj.group(1):
99 if matchobj.group(1):
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 else:
101 else:
102 return "%%(%s)s" % matchobj.group(2)
102 return "%%(%s)s" % matchobj.group(2)
103
103
104 routepath = self._argument_prog.sub(replace, routepath)
104 routepath = self._argument_prog.sub(replace, routepath)
105 return (
105 return (
106 route.name,
106 route.name,
107 routepath,
107 routepath,
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 for arg in self._argument_prog.findall(route.routepath)]
109 for arg in self._argument_prog.findall(route.routepath)]
110 )
110 )
111
111
112 def jsroutes(self):
112 def jsroutes(self):
113 """
113 """
114 Return a list of pyroutes.js compatible routes
114 Return a list of pyroutes.js compatible routes
115 """
115 """
116 for route_name in self._jsroutes:
116 for route_name in self._jsroutes:
117 yield self._extract_route_information(self._routenames[route_name])
117 yield self._extract_route_information(self._routenames[route_name])
118
118
119
119
120 def make_map(config):
120 def make_map(config):
121 """Create, configure and return the routes Mapper"""
121 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 always_scan=config['debug'])
123 always_scan=config['debug'])
124 rmap.minimization = False
124 rmap.minimization = False
125 rmap.explicit = False
125 rmap.explicit = False
126
126
127 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.lib.utils2 import str2bool
128 from rhodecode.model import repo, repo_group
128 from rhodecode.model import repo, repo_group
129
129
130 def check_repo(environ, match_dict):
130 def check_repo(environ, match_dict):
131 """
131 """
132 check for valid repository for proper 404 handling
132 check for valid repository for proper 404 handling
133
133
134 :param environ:
134 :param environ:
135 :param match_dict:
135 :param match_dict:
136 """
136 """
137 repo_name = match_dict.get('repo_name')
137 repo_name = match_dict.get('repo_name')
138
138
139 if match_dict.get('f_path'):
139 if match_dict.get('f_path'):
140 # fix for multiple initial slashes that causes errors
140 # fix for multiple initial slashes that causes errors
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 repo_model = repo.RepoModel()
142 repo_model = repo.RepoModel()
143 by_name_match = repo_model.get_by_repo_name(repo_name)
143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 # if we match quickly from database, short circuit the operation,
144 # if we match quickly from database, short circuit the operation,
145 # and validate repo based on the type.
145 # and validate repo based on the type.
146 if by_name_match:
146 if by_name_match:
147 return True
147 return True
148
148
149 by_id_match = repo_model.get_repo_by_id(repo_name)
149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 if by_id_match:
150 if by_id_match:
151 repo_name = by_id_match.repo_name
151 repo_name = by_id_match.repo_name
152 match_dict['repo_name'] = repo_name
152 match_dict['repo_name'] = repo_name
153 return True
153 return True
154
154
155 return False
155 return False
156
156
157 def check_group(environ, match_dict):
157 def check_group(environ, match_dict):
158 """
158 """
159 check for valid repository group path for proper 404 handling
159 check for valid repository group path for proper 404 handling
160
160
161 :param environ:
161 :param environ:
162 :param match_dict:
162 :param match_dict:
163 """
163 """
164 repo_group_name = match_dict.get('group_name')
164 repo_group_name = match_dict.get('group_name')
165 repo_group_model = repo_group.RepoGroupModel()
165 repo_group_model = repo_group.RepoGroupModel()
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 if by_name_match:
167 if by_name_match:
168 return True
168 return True
169
169
170 return False
170 return False
171
171
172 def check_user_group(environ, match_dict):
172 def check_user_group(environ, match_dict):
173 """
173 """
174 check for valid user group for proper 404 handling
174 check for valid user group for proper 404 handling
175
175
176 :param environ:
176 :param environ:
177 :param match_dict:
177 :param match_dict:
178 """
178 """
179 return True
179 return True
180
180
181 def check_int(environ, match_dict):
181 def check_int(environ, match_dict):
182 return match_dict.get('id').isdigit()
182 return match_dict.get('id').isdigit()
183
183
184
184
185 #==========================================================================
185 #==========================================================================
186 # CUSTOM ROUTES HERE
186 # CUSTOM ROUTES HERE
187 #==========================================================================
187 #==========================================================================
188
188
189 # MAIN PAGE
189 # MAIN PAGE
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 action='goto_switcher_data')
192 action='goto_switcher_data')
193 rmap.connect('repo_list_data', '/_repos', controller='home',
193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 action='repo_list_data')
194 action='repo_list_data')
195
195
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 action='user_autocomplete_data', jsroute=True)
197 action='user_autocomplete_data', jsroute=True)
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data', jsroute=True)
199 action='user_group_autocomplete_data', jsroute=True)
200
200
201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
202 rmap.connect('rst_help',
202 rmap.connect('rst_help',
203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
204 _static=True)
204 _static=True)
205 rmap.connect('markdown_help',
205 rmap.connect('markdown_help',
206 'http://daringfireball.net/projects/markdown/syntax',
206 'http://daringfireball.net/projects/markdown/syntax',
207 _static=True)
207 _static=True)
208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
211 # TODO: anderson - making this a static link since redirect won't play
211 # TODO: anderson - making this a static link since redirect won't play
212 # nice with POST requests
212 # nice with POST requests
213 rmap.connect('enterprise_license_convert_from_old',
213 rmap.connect('enterprise_license_convert_from_old',
214 'https://rhodecode.com/u/license-upgrade',
214 'https://rhodecode.com/u/license-upgrade',
215 _static=True)
215 _static=True)
216
216
217 routing_links.connect_redirection_links(rmap)
217 routing_links.connect_redirection_links(rmap)
218
218
219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
221
221
222 # ADMIN REPOSITORY ROUTES
222 # ADMIN REPOSITORY ROUTES
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 controller='admin/repos') as m:
224 controller='admin/repos') as m:
225 m.connect('repos', '/repos',
225 m.connect('repos', '/repos',
226 action='create', conditions={'method': ['POST']})
226 action='create', conditions={'method': ['POST']})
227 m.connect('repos', '/repos',
227 m.connect('repos', '/repos',
228 action='index', conditions={'method': ['GET']})
228 action='index', conditions={'method': ['GET']})
229 m.connect('new_repo', '/create_repository', jsroute=True,
229 m.connect('new_repo', '/create_repository', jsroute=True,
230 action='create_repository', conditions={'method': ['GET']})
230 action='create_repository', conditions={'method': ['GET']})
231 m.connect('/repos/{repo_name}',
231 m.connect('/repos/{repo_name}',
232 action='update', conditions={'method': ['PUT'],
232 action='update', conditions={'method': ['PUT'],
233 'function': check_repo},
233 'function': check_repo},
234 requirements=URL_NAME_REQUIREMENTS)
234 requirements=URL_NAME_REQUIREMENTS)
235 m.connect('delete_repo', '/repos/{repo_name}',
235 m.connect('delete_repo', '/repos/{repo_name}',
236 action='delete', conditions={'method': ['DELETE']},
236 action='delete', conditions={'method': ['DELETE']},
237 requirements=URL_NAME_REQUIREMENTS)
237 requirements=URL_NAME_REQUIREMENTS)
238 m.connect('repo', '/repos/{repo_name}',
238 m.connect('repo', '/repos/{repo_name}',
239 action='show', conditions={'method': ['GET'],
239 action='show', conditions={'method': ['GET'],
240 'function': check_repo},
240 'function': check_repo},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN REPOSITORY GROUPS ROUTES
243 # ADMIN REPOSITORY GROUPS ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/repo_groups') as m:
245 controller='admin/repo_groups') as m:
246 m.connect('repo_groups', '/repo_groups',
246 m.connect('repo_groups', '/repo_groups',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('repo_groups', '/repo_groups',
248 m.connect('repo_groups', '/repo_groups',
249 action='index', conditions={'method': ['GET']})
249 action='index', conditions={'method': ['GET']})
250 m.connect('new_repo_group', '/repo_groups/new',
250 m.connect('new_repo_group', '/repo_groups/new',
251 action='new', conditions={'method': ['GET']})
251 action='new', conditions={'method': ['GET']})
252 m.connect('update_repo_group', '/repo_groups/{group_name}',
252 m.connect('update_repo_group', '/repo_groups/{group_name}',
253 action='update', conditions={'method': ['PUT'],
253 action='update', conditions={'method': ['PUT'],
254 'function': check_group},
254 'function': check_group},
255 requirements=URL_NAME_REQUIREMENTS)
255 requirements=URL_NAME_REQUIREMENTS)
256
256
257 # EXTRAS REPO GROUP ROUTES
257 # EXTRAS REPO GROUP ROUTES
258 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
258 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
259 action='edit',
259 action='edit',
260 conditions={'method': ['GET'], 'function': check_group},
260 conditions={'method': ['GET'], 'function': check_group},
261 requirements=URL_NAME_REQUIREMENTS)
261 requirements=URL_NAME_REQUIREMENTS)
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 action='edit',
263 action='edit',
264 conditions={'method': ['PUT'], 'function': check_group},
264 conditions={'method': ['PUT'], 'function': check_group},
265 requirements=URL_NAME_REQUIREMENTS)
265 requirements=URL_NAME_REQUIREMENTS)
266
266
267 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
267 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
268 action='edit_repo_group_advanced',
268 action='edit_repo_group_advanced',
269 conditions={'method': ['GET'], 'function': check_group},
269 conditions={'method': ['GET'], 'function': check_group},
270 requirements=URL_NAME_REQUIREMENTS)
270 requirements=URL_NAME_REQUIREMENTS)
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 action='edit_repo_group_advanced',
272 action='edit_repo_group_advanced',
273 conditions={'method': ['PUT'], 'function': check_group},
273 conditions={'method': ['PUT'], 'function': check_group},
274 requirements=URL_NAME_REQUIREMENTS)
274 requirements=URL_NAME_REQUIREMENTS)
275
275
276 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
276 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
277 action='edit_repo_group_perms',
277 action='edit_repo_group_perms',
278 conditions={'method': ['GET'], 'function': check_group},
278 conditions={'method': ['GET'], 'function': check_group},
279 requirements=URL_NAME_REQUIREMENTS)
279 requirements=URL_NAME_REQUIREMENTS)
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 action='update_perms',
281 action='update_perms',
282 conditions={'method': ['PUT'], 'function': check_group},
282 conditions={'method': ['PUT'], 'function': check_group},
283 requirements=URL_NAME_REQUIREMENTS)
283 requirements=URL_NAME_REQUIREMENTS)
284
284
285 m.connect('delete_repo_group', '/repo_groups/{group_name}',
285 m.connect('delete_repo_group', '/repo_groups/{group_name}',
286 action='delete', conditions={'method': ['DELETE'],
286 action='delete', conditions={'method': ['DELETE'],
287 'function': check_group},
287 'function': check_group},
288 requirements=URL_NAME_REQUIREMENTS)
288 requirements=URL_NAME_REQUIREMENTS)
289
289
290 # ADMIN USER ROUTES
290 # ADMIN USER ROUTES
291 with rmap.submapper(path_prefix=ADMIN_PREFIX,
291 with rmap.submapper(path_prefix=ADMIN_PREFIX,
292 controller='admin/users') as m:
292 controller='admin/users') as m:
293 m.connect('users', '/users',
293 m.connect('users', '/users',
294 action='create', conditions={'method': ['POST']})
294 action='create', conditions={'method': ['POST']})
295 m.connect('users', '/users',
296 action='index', conditions={'method': ['GET']})
297 m.connect('new_user', '/users/new',
295 m.connect('new_user', '/users/new',
298 action='new', conditions={'method': ['GET']})
296 action='new', conditions={'method': ['GET']})
299 m.connect('update_user', '/users/{user_id}',
297 m.connect('update_user', '/users/{user_id}',
300 action='update', conditions={'method': ['PUT']})
298 action='update', conditions={'method': ['PUT']})
301 m.connect('delete_user', '/users/{user_id}',
299 m.connect('delete_user', '/users/{user_id}',
302 action='delete', conditions={'method': ['DELETE']})
300 action='delete', conditions={'method': ['DELETE']})
303 m.connect('edit_user', '/users/{user_id}/edit',
301 m.connect('edit_user', '/users/{user_id}/edit',
304 action='edit', conditions={'method': ['GET']}, jsroute=True)
302 action='edit', conditions={'method': ['GET']}, jsroute=True)
305 m.connect('user', '/users/{user_id}',
303 m.connect('user', '/users/{user_id}',
306 action='show', conditions={'method': ['GET']})
304 action='show', conditions={'method': ['GET']})
307 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
305 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
308 action='reset_password', conditions={'method': ['POST']})
306 action='reset_password', conditions={'method': ['POST']})
309 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
307 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
310 action='create_personal_repo_group', conditions={'method': ['POST']})
308 action='create_personal_repo_group', conditions={'method': ['POST']})
311
309
312 # EXTRAS USER ROUTES
310 # EXTRAS USER ROUTES
313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
311 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
314 action='edit_advanced', conditions={'method': ['GET']})
312 action='edit_advanced', conditions={'method': ['GET']})
315 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
316 action='update_advanced', conditions={'method': ['PUT']})
314 action='update_advanced', conditions={'method': ['PUT']})
317
315
318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
316 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
319 action='edit_global_perms', conditions={'method': ['GET']})
317 action='edit_global_perms', conditions={'method': ['GET']})
320 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
321 action='update_global_perms', conditions={'method': ['PUT']})
319 action='update_global_perms', conditions={'method': ['PUT']})
322
320
323 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
321 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
324 action='edit_perms_summary', conditions={'method': ['GET']})
322 action='edit_perms_summary', conditions={'method': ['GET']})
325
323
326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
324 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
327 action='edit_emails', conditions={'method': ['GET']})
325 action='edit_emails', conditions={'method': ['GET']})
328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
329 action='add_email', conditions={'method': ['PUT']})
327 action='add_email', conditions={'method': ['PUT']})
330 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
331 action='delete_email', conditions={'method': ['DELETE']})
329 action='delete_email', conditions={'method': ['DELETE']})
332
330
333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
331 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
334 action='edit_ips', conditions={'method': ['GET']})
332 action='edit_ips', conditions={'method': ['GET']})
335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
336 action='add_ip', conditions={'method': ['PUT']})
334 action='add_ip', conditions={'method': ['PUT']})
337 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
338 action='delete_ip', conditions={'method': ['DELETE']})
336 action='delete_ip', conditions={'method': ['DELETE']})
339
337
340 # ADMIN USER GROUPS REST ROUTES
338 # ADMIN USER GROUPS REST ROUTES
341 with rmap.submapper(path_prefix=ADMIN_PREFIX,
339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
342 controller='admin/user_groups') as m:
340 controller='admin/user_groups') as m:
343 m.connect('users_groups', '/user_groups',
341 m.connect('users_groups', '/user_groups',
344 action='create', conditions={'method': ['POST']})
342 action='create', conditions={'method': ['POST']})
345 m.connect('users_groups', '/user_groups',
343 m.connect('users_groups', '/user_groups',
346 action='index', conditions={'method': ['GET']})
344 action='index', conditions={'method': ['GET']})
347 m.connect('new_users_group', '/user_groups/new',
345 m.connect('new_users_group', '/user_groups/new',
348 action='new', conditions={'method': ['GET']})
346 action='new', conditions={'method': ['GET']})
349 m.connect('update_users_group', '/user_groups/{user_group_id}',
347 m.connect('update_users_group', '/user_groups/{user_group_id}',
350 action='update', conditions={'method': ['PUT']})
348 action='update', conditions={'method': ['PUT']})
351 m.connect('delete_users_group', '/user_groups/{user_group_id}',
349 m.connect('delete_users_group', '/user_groups/{user_group_id}',
352 action='delete', conditions={'method': ['DELETE']})
350 action='delete', conditions={'method': ['DELETE']})
353 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
351 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
354 action='edit', conditions={'method': ['GET']},
352 action='edit', conditions={'method': ['GET']},
355 function=check_user_group)
353 function=check_user_group)
356
354
357 # EXTRAS USER GROUP ROUTES
355 # EXTRAS USER GROUP ROUTES
358 m.connect('edit_user_group_global_perms',
356 m.connect('edit_user_group_global_perms',
359 '/user_groups/{user_group_id}/edit/global_permissions',
357 '/user_groups/{user_group_id}/edit/global_permissions',
360 action='edit_global_perms', conditions={'method': ['GET']})
358 action='edit_global_perms', conditions={'method': ['GET']})
361 m.connect('edit_user_group_global_perms',
359 m.connect('edit_user_group_global_perms',
362 '/user_groups/{user_group_id}/edit/global_permissions',
360 '/user_groups/{user_group_id}/edit/global_permissions',
363 action='update_global_perms', conditions={'method': ['PUT']})
361 action='update_global_perms', conditions={'method': ['PUT']})
364 m.connect('edit_user_group_perms_summary',
362 m.connect('edit_user_group_perms_summary',
365 '/user_groups/{user_group_id}/edit/permissions_summary',
363 '/user_groups/{user_group_id}/edit/permissions_summary',
366 action='edit_perms_summary', conditions={'method': ['GET']})
364 action='edit_perms_summary', conditions={'method': ['GET']})
367
365
368 m.connect('edit_user_group_perms',
366 m.connect('edit_user_group_perms',
369 '/user_groups/{user_group_id}/edit/permissions',
367 '/user_groups/{user_group_id}/edit/permissions',
370 action='edit_perms', conditions={'method': ['GET']})
368 action='edit_perms', conditions={'method': ['GET']})
371 m.connect('edit_user_group_perms',
369 m.connect('edit_user_group_perms',
372 '/user_groups/{user_group_id}/edit/permissions',
370 '/user_groups/{user_group_id}/edit/permissions',
373 action='update_perms', conditions={'method': ['PUT']})
371 action='update_perms', conditions={'method': ['PUT']})
374
372
375 m.connect('edit_user_group_advanced',
373 m.connect('edit_user_group_advanced',
376 '/user_groups/{user_group_id}/edit/advanced',
374 '/user_groups/{user_group_id}/edit/advanced',
377 action='edit_advanced', conditions={'method': ['GET']})
375 action='edit_advanced', conditions={'method': ['GET']})
378
376
379 m.connect('edit_user_group_members',
377 m.connect('edit_user_group_members',
380 '/user_groups/{user_group_id}/edit/members', jsroute=True,
378 '/user_groups/{user_group_id}/edit/members', jsroute=True,
381 action='user_group_members', conditions={'method': ['GET']})
379 action='user_group_members', conditions={'method': ['GET']})
382
380
383 # ADMIN PERMISSIONS ROUTES
381 # ADMIN PERMISSIONS ROUTES
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
382 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 controller='admin/permissions') as m:
383 controller='admin/permissions') as m:
386 m.connect('admin_permissions_application', '/permissions/application',
384 m.connect('admin_permissions_application', '/permissions/application',
387 action='permission_application_update', conditions={'method': ['POST']})
385 action='permission_application_update', conditions={'method': ['POST']})
388 m.connect('admin_permissions_application', '/permissions/application',
386 m.connect('admin_permissions_application', '/permissions/application',
389 action='permission_application', conditions={'method': ['GET']})
387 action='permission_application', conditions={'method': ['GET']})
390
388
391 m.connect('admin_permissions_global', '/permissions/global',
389 m.connect('admin_permissions_global', '/permissions/global',
392 action='permission_global_update', conditions={'method': ['POST']})
390 action='permission_global_update', conditions={'method': ['POST']})
393 m.connect('admin_permissions_global', '/permissions/global',
391 m.connect('admin_permissions_global', '/permissions/global',
394 action='permission_global', conditions={'method': ['GET']})
392 action='permission_global', conditions={'method': ['GET']})
395
393
396 m.connect('admin_permissions_object', '/permissions/object',
394 m.connect('admin_permissions_object', '/permissions/object',
397 action='permission_objects_update', conditions={'method': ['POST']})
395 action='permission_objects_update', conditions={'method': ['POST']})
398 m.connect('admin_permissions_object', '/permissions/object',
396 m.connect('admin_permissions_object', '/permissions/object',
399 action='permission_objects', conditions={'method': ['GET']})
397 action='permission_objects', conditions={'method': ['GET']})
400
398
401 m.connect('admin_permissions_ips', '/permissions/ips',
399 m.connect('admin_permissions_ips', '/permissions/ips',
402 action='permission_ips', conditions={'method': ['POST']})
400 action='permission_ips', conditions={'method': ['POST']})
403 m.connect('admin_permissions_ips', '/permissions/ips',
401 m.connect('admin_permissions_ips', '/permissions/ips',
404 action='permission_ips', conditions={'method': ['GET']})
402 action='permission_ips', conditions={'method': ['GET']})
405
403
406 m.connect('admin_permissions_overview', '/permissions/overview',
404 m.connect('admin_permissions_overview', '/permissions/overview',
407 action='permission_perms', conditions={'method': ['GET']})
405 action='permission_perms', conditions={'method': ['GET']})
408
406
409 # ADMIN DEFAULTS REST ROUTES
407 # ADMIN DEFAULTS REST ROUTES
410 with rmap.submapper(path_prefix=ADMIN_PREFIX,
408 with rmap.submapper(path_prefix=ADMIN_PREFIX,
411 controller='admin/defaults') as m:
409 controller='admin/defaults') as m:
412 m.connect('admin_defaults_repositories', '/defaults/repositories',
410 m.connect('admin_defaults_repositories', '/defaults/repositories',
413 action='update_repository_defaults', conditions={'method': ['POST']})
411 action='update_repository_defaults', conditions={'method': ['POST']})
414 m.connect('admin_defaults_repositories', '/defaults/repositories',
412 m.connect('admin_defaults_repositories', '/defaults/repositories',
415 action='index', conditions={'method': ['GET']})
413 action='index', conditions={'method': ['GET']})
416
414
417 # ADMIN DEBUG STYLE ROUTES
415 # ADMIN DEBUG STYLE ROUTES
418 if str2bool(config.get('debug_style')):
416 if str2bool(config.get('debug_style')):
419 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
417 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
420 controller='debug_style') as m:
418 controller='debug_style') as m:
421 m.connect('debug_style_home', '',
419 m.connect('debug_style_home', '',
422 action='index', conditions={'method': ['GET']})
420 action='index', conditions={'method': ['GET']})
423 m.connect('debug_style_template', '/t/{t_path}',
421 m.connect('debug_style_template', '/t/{t_path}',
424 action='template', conditions={'method': ['GET']})
422 action='template', conditions={'method': ['GET']})
425
423
426 # ADMIN SETTINGS ROUTES
424 # ADMIN SETTINGS ROUTES
427 with rmap.submapper(path_prefix=ADMIN_PREFIX,
425 with rmap.submapper(path_prefix=ADMIN_PREFIX,
428 controller='admin/settings') as m:
426 controller='admin/settings') as m:
429
427
430 # default
428 # default
431 m.connect('admin_settings', '/settings',
429 m.connect('admin_settings', '/settings',
432 action='settings_global_update',
430 action='settings_global_update',
433 conditions={'method': ['POST']})
431 conditions={'method': ['POST']})
434 m.connect('admin_settings', '/settings',
432 m.connect('admin_settings', '/settings',
435 action='settings_global', conditions={'method': ['GET']})
433 action='settings_global', conditions={'method': ['GET']})
436
434
437 m.connect('admin_settings_vcs', '/settings/vcs',
435 m.connect('admin_settings_vcs', '/settings/vcs',
438 action='settings_vcs_update',
436 action='settings_vcs_update',
439 conditions={'method': ['POST']})
437 conditions={'method': ['POST']})
440 m.connect('admin_settings_vcs', '/settings/vcs',
438 m.connect('admin_settings_vcs', '/settings/vcs',
441 action='settings_vcs',
439 action='settings_vcs',
442 conditions={'method': ['GET']})
440 conditions={'method': ['GET']})
443 m.connect('admin_settings_vcs', '/settings/vcs',
441 m.connect('admin_settings_vcs', '/settings/vcs',
444 action='delete_svn_pattern',
442 action='delete_svn_pattern',
445 conditions={'method': ['DELETE']})
443 conditions={'method': ['DELETE']})
446
444
447 m.connect('admin_settings_mapping', '/settings/mapping',
445 m.connect('admin_settings_mapping', '/settings/mapping',
448 action='settings_mapping_update',
446 action='settings_mapping_update',
449 conditions={'method': ['POST']})
447 conditions={'method': ['POST']})
450 m.connect('admin_settings_mapping', '/settings/mapping',
448 m.connect('admin_settings_mapping', '/settings/mapping',
451 action='settings_mapping', conditions={'method': ['GET']})
449 action='settings_mapping', conditions={'method': ['GET']})
452
450
453 m.connect('admin_settings_global', '/settings/global',
451 m.connect('admin_settings_global', '/settings/global',
454 action='settings_global_update',
452 action='settings_global_update',
455 conditions={'method': ['POST']})
453 conditions={'method': ['POST']})
456 m.connect('admin_settings_global', '/settings/global',
454 m.connect('admin_settings_global', '/settings/global',
457 action='settings_global', conditions={'method': ['GET']})
455 action='settings_global', conditions={'method': ['GET']})
458
456
459 m.connect('admin_settings_visual', '/settings/visual',
457 m.connect('admin_settings_visual', '/settings/visual',
460 action='settings_visual_update',
458 action='settings_visual_update',
461 conditions={'method': ['POST']})
459 conditions={'method': ['POST']})
462 m.connect('admin_settings_visual', '/settings/visual',
460 m.connect('admin_settings_visual', '/settings/visual',
463 action='settings_visual', conditions={'method': ['GET']})
461 action='settings_visual', conditions={'method': ['GET']})
464
462
465 m.connect('admin_settings_issuetracker',
463 m.connect('admin_settings_issuetracker',
466 '/settings/issue-tracker', action='settings_issuetracker',
464 '/settings/issue-tracker', action='settings_issuetracker',
467 conditions={'method': ['GET']})
465 conditions={'method': ['GET']})
468 m.connect('admin_settings_issuetracker_save',
466 m.connect('admin_settings_issuetracker_save',
469 '/settings/issue-tracker/save',
467 '/settings/issue-tracker/save',
470 action='settings_issuetracker_save',
468 action='settings_issuetracker_save',
471 conditions={'method': ['POST']})
469 conditions={'method': ['POST']})
472 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
470 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
473 action='settings_issuetracker_test',
471 action='settings_issuetracker_test',
474 conditions={'method': ['POST']})
472 conditions={'method': ['POST']})
475 m.connect('admin_issuetracker_delete',
473 m.connect('admin_issuetracker_delete',
476 '/settings/issue-tracker/delete',
474 '/settings/issue-tracker/delete',
477 action='settings_issuetracker_delete',
475 action='settings_issuetracker_delete',
478 conditions={'method': ['DELETE']})
476 conditions={'method': ['DELETE']})
479
477
480 m.connect('admin_settings_email', '/settings/email',
478 m.connect('admin_settings_email', '/settings/email',
481 action='settings_email_update',
479 action='settings_email_update',
482 conditions={'method': ['POST']})
480 conditions={'method': ['POST']})
483 m.connect('admin_settings_email', '/settings/email',
481 m.connect('admin_settings_email', '/settings/email',
484 action='settings_email', conditions={'method': ['GET']})
482 action='settings_email', conditions={'method': ['GET']})
485
483
486 m.connect('admin_settings_hooks', '/settings/hooks',
484 m.connect('admin_settings_hooks', '/settings/hooks',
487 action='settings_hooks_update',
485 action='settings_hooks_update',
488 conditions={'method': ['POST', 'DELETE']})
486 conditions={'method': ['POST', 'DELETE']})
489 m.connect('admin_settings_hooks', '/settings/hooks',
487 m.connect('admin_settings_hooks', '/settings/hooks',
490 action='settings_hooks', conditions={'method': ['GET']})
488 action='settings_hooks', conditions={'method': ['GET']})
491
489
492 m.connect('admin_settings_search', '/settings/search',
490 m.connect('admin_settings_search', '/settings/search',
493 action='settings_search', conditions={'method': ['GET']})
491 action='settings_search', conditions={'method': ['GET']})
494
492
495 m.connect('admin_settings_supervisor', '/settings/supervisor',
493 m.connect('admin_settings_supervisor', '/settings/supervisor',
496 action='settings_supervisor', conditions={'method': ['GET']})
494 action='settings_supervisor', conditions={'method': ['GET']})
497 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
495 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
498 action='settings_supervisor_log', conditions={'method': ['GET']})
496 action='settings_supervisor_log', conditions={'method': ['GET']})
499
497
500 m.connect('admin_settings_labs', '/settings/labs',
498 m.connect('admin_settings_labs', '/settings/labs',
501 action='settings_labs_update',
499 action='settings_labs_update',
502 conditions={'method': ['POST']})
500 conditions={'method': ['POST']})
503 m.connect('admin_settings_labs', '/settings/labs',
501 m.connect('admin_settings_labs', '/settings/labs',
504 action='settings_labs', conditions={'method': ['GET']})
502 action='settings_labs', conditions={'method': ['GET']})
505
503
506 # ADMIN MY ACCOUNT
504 # ADMIN MY ACCOUNT
507 with rmap.submapper(path_prefix=ADMIN_PREFIX,
505 with rmap.submapper(path_prefix=ADMIN_PREFIX,
508 controller='admin/my_account') as m:
506 controller='admin/my_account') as m:
509
507
510 m.connect('my_account', '/my_account',
508 m.connect('my_account', '/my_account',
511 action='my_account', conditions={'method': ['GET']})
509 action='my_account', conditions={'method': ['GET']})
512 m.connect('my_account_edit', '/my_account/edit',
510 m.connect('my_account_edit', '/my_account/edit',
513 action='my_account_edit', conditions={'method': ['GET']})
511 action='my_account_edit', conditions={'method': ['GET']})
514 m.connect('my_account', '/my_account',
512 m.connect('my_account', '/my_account',
515 action='my_account_update', conditions={'method': ['POST']})
513 action='my_account_update', conditions={'method': ['POST']})
516
514
517 m.connect('my_account_password', '/my_account/password',
515 m.connect('my_account_password', '/my_account/password',
518 action='my_account_password', conditions={'method': ['GET', 'POST']})
516 action='my_account_password', conditions={'method': ['GET', 'POST']})
519
517
520 m.connect('my_account_repos', '/my_account/repos',
518 m.connect('my_account_repos', '/my_account/repos',
521 action='my_account_repos', conditions={'method': ['GET']})
519 action='my_account_repos', conditions={'method': ['GET']})
522
520
523 m.connect('my_account_watched', '/my_account/watched',
521 m.connect('my_account_watched', '/my_account/watched',
524 action='my_account_watched', conditions={'method': ['GET']})
522 action='my_account_watched', conditions={'method': ['GET']})
525
523
526 m.connect('my_account_pullrequests', '/my_account/pull_requests',
524 m.connect('my_account_pullrequests', '/my_account/pull_requests',
527 action='my_account_pullrequests', conditions={'method': ['GET']})
525 action='my_account_pullrequests', conditions={'method': ['GET']})
528
526
529 m.connect('my_account_perms', '/my_account/perms',
527 m.connect('my_account_perms', '/my_account/perms',
530 action='my_account_perms', conditions={'method': ['GET']})
528 action='my_account_perms', conditions={'method': ['GET']})
531
529
532 m.connect('my_account_emails', '/my_account/emails',
530 m.connect('my_account_emails', '/my_account/emails',
533 action='my_account_emails', conditions={'method': ['GET']})
531 action='my_account_emails', conditions={'method': ['GET']})
534 m.connect('my_account_emails', '/my_account/emails',
532 m.connect('my_account_emails', '/my_account/emails',
535 action='my_account_emails_add', conditions={'method': ['POST']})
533 action='my_account_emails_add', conditions={'method': ['POST']})
536 m.connect('my_account_emails', '/my_account/emails',
534 m.connect('my_account_emails', '/my_account/emails',
537 action='my_account_emails_delete', conditions={'method': ['DELETE']})
535 action='my_account_emails_delete', conditions={'method': ['DELETE']})
538
536
539 m.connect('my_account_notifications', '/my_account/notifications',
537 m.connect('my_account_notifications', '/my_account/notifications',
540 action='my_notifications',
538 action='my_notifications',
541 conditions={'method': ['GET']})
539 conditions={'method': ['GET']})
542 m.connect('my_account_notifications_toggle_visibility',
540 m.connect('my_account_notifications_toggle_visibility',
543 '/my_account/toggle_visibility',
541 '/my_account/toggle_visibility',
544 action='my_notifications_toggle_visibility',
542 action='my_notifications_toggle_visibility',
545 conditions={'method': ['POST']})
543 conditions={'method': ['POST']})
546 m.connect('my_account_notifications_test_channelstream',
544 m.connect('my_account_notifications_test_channelstream',
547 '/my_account/test_channelstream',
545 '/my_account/test_channelstream',
548 action='my_account_notifications_test_channelstream',
546 action='my_account_notifications_test_channelstream',
549 conditions={'method': ['POST']})
547 conditions={'method': ['POST']})
550
548
551 # NOTIFICATION REST ROUTES
549 # NOTIFICATION REST ROUTES
552 with rmap.submapper(path_prefix=ADMIN_PREFIX,
550 with rmap.submapper(path_prefix=ADMIN_PREFIX,
553 controller='admin/notifications') as m:
551 controller='admin/notifications') as m:
554 m.connect('notifications', '/notifications',
552 m.connect('notifications', '/notifications',
555 action='index', conditions={'method': ['GET']})
553 action='index', conditions={'method': ['GET']})
556 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
554 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
557 action='mark_all_read', conditions={'method': ['POST']})
555 action='mark_all_read', conditions={'method': ['POST']})
558 m.connect('/notifications/{notification_id}',
556 m.connect('/notifications/{notification_id}',
559 action='update', conditions={'method': ['PUT']})
557 action='update', conditions={'method': ['PUT']})
560 m.connect('/notifications/{notification_id}',
558 m.connect('/notifications/{notification_id}',
561 action='delete', conditions={'method': ['DELETE']})
559 action='delete', conditions={'method': ['DELETE']})
562 m.connect('notification', '/notifications/{notification_id}',
560 m.connect('notification', '/notifications/{notification_id}',
563 action='show', conditions={'method': ['GET']})
561 action='show', conditions={'method': ['GET']})
564
562
565 # ADMIN GIST
563 # ADMIN GIST
566 with rmap.submapper(path_prefix=ADMIN_PREFIX,
564 with rmap.submapper(path_prefix=ADMIN_PREFIX,
567 controller='admin/gists') as m:
565 controller='admin/gists') as m:
568 m.connect('gists', '/gists',
566 m.connect('gists', '/gists',
569 action='create', conditions={'method': ['POST']})
567 action='create', conditions={'method': ['POST']})
570 m.connect('gists', '/gists', jsroute=True,
568 m.connect('gists', '/gists', jsroute=True,
571 action='index', conditions={'method': ['GET']})
569 action='index', conditions={'method': ['GET']})
572 m.connect('new_gist', '/gists/new', jsroute=True,
570 m.connect('new_gist', '/gists/new', jsroute=True,
573 action='new', conditions={'method': ['GET']})
571 action='new', conditions={'method': ['GET']})
574
572
575 m.connect('/gists/{gist_id}',
573 m.connect('/gists/{gist_id}',
576 action='delete', conditions={'method': ['DELETE']})
574 action='delete', conditions={'method': ['DELETE']})
577 m.connect('edit_gist', '/gists/{gist_id}/edit',
575 m.connect('edit_gist', '/gists/{gist_id}/edit',
578 action='edit_form', conditions={'method': ['GET']})
576 action='edit_form', conditions={'method': ['GET']})
579 m.connect('edit_gist', '/gists/{gist_id}/edit',
577 m.connect('edit_gist', '/gists/{gist_id}/edit',
580 action='edit', conditions={'method': ['POST']})
578 action='edit', conditions={'method': ['POST']})
581 m.connect(
579 m.connect(
582 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
580 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
583 action='check_revision', conditions={'method': ['GET']})
581 action='check_revision', conditions={'method': ['GET']})
584
582
585 m.connect('gist', '/gists/{gist_id}',
583 m.connect('gist', '/gists/{gist_id}',
586 action='show', conditions={'method': ['GET']})
584 action='show', conditions={'method': ['GET']})
587 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
585 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
588 revision='tip',
586 revision='tip',
589 action='show', conditions={'method': ['GET']})
587 action='show', conditions={'method': ['GET']})
590 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
588 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
591 revision='tip',
589 revision='tip',
592 action='show', conditions={'method': ['GET']})
590 action='show', conditions={'method': ['GET']})
593 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
591 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
594 revision='tip',
592 revision='tip',
595 action='show', conditions={'method': ['GET']},
593 action='show', conditions={'method': ['GET']},
596 requirements=URL_NAME_REQUIREMENTS)
594 requirements=URL_NAME_REQUIREMENTS)
597
595
598 # ADMIN MAIN PAGES
596 # ADMIN MAIN PAGES
599 with rmap.submapper(path_prefix=ADMIN_PREFIX,
597 with rmap.submapper(path_prefix=ADMIN_PREFIX,
600 controller='admin/admin') as m:
598 controller='admin/admin') as m:
601 m.connect('admin_home', '', action='index')
599 m.connect('admin_home', '', action='index')
602 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
600 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
603 action='add_repo')
601 action='add_repo')
604 m.connect(
602 m.connect(
605 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
603 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
606 action='pull_requests')
604 action='pull_requests')
607 m.connect(
605 m.connect(
608 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
606 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
609 action='pull_requests')
607 action='pull_requests')
610 m.connect(
608 m.connect(
611 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
609 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
612 action='pull_requests')
610 action='pull_requests')
613
611
614 # USER JOURNAL
612 # USER JOURNAL
615 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
613 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
616 controller='journal', action='index')
614 controller='journal', action='index')
617 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
615 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
618 controller='journal', action='journal_rss')
616 controller='journal', action='journal_rss')
619 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
617 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
620 controller='journal', action='journal_atom')
618 controller='journal', action='journal_atom')
621
619
622 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
620 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
623 controller='journal', action='public_journal')
621 controller='journal', action='public_journal')
624
622
625 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
623 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
626 controller='journal', action='public_journal_rss')
624 controller='journal', action='public_journal_rss')
627
625
628 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
626 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
629 controller='journal', action='public_journal_rss')
627 controller='journal', action='public_journal_rss')
630
628
631 rmap.connect('public_journal_atom',
629 rmap.connect('public_journal_atom',
632 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
630 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
633 action='public_journal_atom')
631 action='public_journal_atom')
634
632
635 rmap.connect('public_journal_atom_old',
633 rmap.connect('public_journal_atom_old',
636 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
634 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
637 action='public_journal_atom')
635 action='public_journal_atom')
638
636
639 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
637 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
640 controller='journal', action='toggle_following', jsroute=True,
638 controller='journal', action='toggle_following', jsroute=True,
641 conditions={'method': ['POST']})
639 conditions={'method': ['POST']})
642
640
643 # FULL TEXT SEARCH
641 # FULL TEXT SEARCH
644 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
642 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
645 controller='search')
643 controller='search')
646 rmap.connect('search_repo_home', '/{repo_name}/search',
644 rmap.connect('search_repo_home', '/{repo_name}/search',
647 controller='search',
645 controller='search',
648 action='index',
646 action='index',
649 conditions={'function': check_repo},
647 conditions={'function': check_repo},
650 requirements=URL_NAME_REQUIREMENTS)
648 requirements=URL_NAME_REQUIREMENTS)
651
649
652 # FEEDS
650 # FEEDS
653 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
651 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
654 controller='feed', action='rss',
652 controller='feed', action='rss',
655 conditions={'function': check_repo},
653 conditions={'function': check_repo},
656 requirements=URL_NAME_REQUIREMENTS)
654 requirements=URL_NAME_REQUIREMENTS)
657
655
658 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
656 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
659 controller='feed', action='atom',
657 controller='feed', action='atom',
660 conditions={'function': check_repo},
658 conditions={'function': check_repo},
661 requirements=URL_NAME_REQUIREMENTS)
659 requirements=URL_NAME_REQUIREMENTS)
662
660
663 #==========================================================================
661 #==========================================================================
664 # REPOSITORY ROUTES
662 # REPOSITORY ROUTES
665 #==========================================================================
663 #==========================================================================
666
664
667 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
665 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
668 controller='admin/repos', action='repo_creating',
666 controller='admin/repos', action='repo_creating',
669 requirements=URL_NAME_REQUIREMENTS)
667 requirements=URL_NAME_REQUIREMENTS)
670 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
668 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
671 controller='admin/repos', action='repo_check',
669 controller='admin/repos', action='repo_check',
672 requirements=URL_NAME_REQUIREMENTS)
670 requirements=URL_NAME_REQUIREMENTS)
673
671
674 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
672 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
675 controller='summary', action='repo_stats',
673 controller='summary', action='repo_stats',
676 conditions={'function': check_repo},
674 conditions={'function': check_repo},
677 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
675 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
678
676
679 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
677 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
680 controller='summary', action='repo_refs_data',
678 controller='summary', action='repo_refs_data',
681 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
679 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
682 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
680 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
683 controller='summary', action='repo_refs_changelog_data',
681 controller='summary', action='repo_refs_changelog_data',
684 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
682 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
685 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
683 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
686 controller='summary', action='repo_default_reviewers_data',
684 controller='summary', action='repo_default_reviewers_data',
687 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
685 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
688
686
689 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
687 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
690 controller='changeset', revision='tip',
688 controller='changeset', revision='tip',
691 conditions={'function': check_repo},
689 conditions={'function': check_repo},
692 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
690 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
693 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
691 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
694 controller='changeset', revision='tip', action='changeset_children',
692 controller='changeset', revision='tip', action='changeset_children',
695 conditions={'function': check_repo},
693 conditions={'function': check_repo},
696 requirements=URL_NAME_REQUIREMENTS)
694 requirements=URL_NAME_REQUIREMENTS)
697 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
695 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
698 controller='changeset', revision='tip', action='changeset_parents',
696 controller='changeset', revision='tip', action='changeset_parents',
699 conditions={'function': check_repo},
697 conditions={'function': check_repo},
700 requirements=URL_NAME_REQUIREMENTS)
698 requirements=URL_NAME_REQUIREMENTS)
701
699
702 # repo edit options
700 # repo edit options
703 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
701 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
704 controller='admin/repos', action='edit',
702 controller='admin/repos', action='edit',
705 conditions={'method': ['GET'], 'function': check_repo},
703 conditions={'method': ['GET'], 'function': check_repo},
706 requirements=URL_NAME_REQUIREMENTS)
704 requirements=URL_NAME_REQUIREMENTS)
707
705
708 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
706 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
709 jsroute=True,
707 jsroute=True,
710 controller='admin/repos', action='edit_permissions',
708 controller='admin/repos', action='edit_permissions',
711 conditions={'method': ['GET'], 'function': check_repo},
709 conditions={'method': ['GET'], 'function': check_repo},
712 requirements=URL_NAME_REQUIREMENTS)
710 requirements=URL_NAME_REQUIREMENTS)
713 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
711 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
714 controller='admin/repos', action='edit_permissions_update',
712 controller='admin/repos', action='edit_permissions_update',
715 conditions={'method': ['PUT'], 'function': check_repo},
713 conditions={'method': ['PUT'], 'function': check_repo},
716 requirements=URL_NAME_REQUIREMENTS)
714 requirements=URL_NAME_REQUIREMENTS)
717
715
718 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
716 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
719 controller='admin/repos', action='edit_fields',
717 controller='admin/repos', action='edit_fields',
720 conditions={'method': ['GET'], 'function': check_repo},
718 conditions={'method': ['GET'], 'function': check_repo},
721 requirements=URL_NAME_REQUIREMENTS)
719 requirements=URL_NAME_REQUIREMENTS)
722 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
720 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
723 controller='admin/repos', action='create_repo_field',
721 controller='admin/repos', action='create_repo_field',
724 conditions={'method': ['PUT'], 'function': check_repo},
722 conditions={'method': ['PUT'], 'function': check_repo},
725 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
726 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
724 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
727 controller='admin/repos', action='delete_repo_field',
725 controller='admin/repos', action='delete_repo_field',
728 conditions={'method': ['DELETE'], 'function': check_repo},
726 conditions={'method': ['DELETE'], 'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
727 requirements=URL_NAME_REQUIREMENTS)
730
728
731 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
729 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
732 controller='admin/repos', action='edit_advanced',
730 controller='admin/repos', action='edit_advanced',
733 conditions={'method': ['GET'], 'function': check_repo},
731 conditions={'method': ['GET'], 'function': check_repo},
734 requirements=URL_NAME_REQUIREMENTS)
732 requirements=URL_NAME_REQUIREMENTS)
735
733
736 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
734 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
737 controller='admin/repos', action='edit_advanced_locking',
735 controller='admin/repos', action='edit_advanced_locking',
738 conditions={'method': ['PUT'], 'function': check_repo},
736 conditions={'method': ['PUT'], 'function': check_repo},
739 requirements=URL_NAME_REQUIREMENTS)
737 requirements=URL_NAME_REQUIREMENTS)
740 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
738 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
741 controller='admin/repos', action='toggle_locking',
739 controller='admin/repos', action='toggle_locking',
742 conditions={'method': ['GET'], 'function': check_repo},
740 conditions={'method': ['GET'], 'function': check_repo},
743 requirements=URL_NAME_REQUIREMENTS)
741 requirements=URL_NAME_REQUIREMENTS)
744
742
745 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
743 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
746 controller='admin/repos', action='edit_advanced_journal',
744 controller='admin/repos', action='edit_advanced_journal',
747 conditions={'method': ['PUT'], 'function': check_repo},
745 conditions={'method': ['PUT'], 'function': check_repo},
748 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
749
747
750 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
748 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
751 controller='admin/repos', action='edit_advanced_fork',
749 controller='admin/repos', action='edit_advanced_fork',
752 conditions={'method': ['PUT'], 'function': check_repo},
750 conditions={'method': ['PUT'], 'function': check_repo},
753 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
754
752
755 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
753 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
756 controller='admin/repos', action='edit_caches_form',
754 controller='admin/repos', action='edit_caches_form',
757 conditions={'method': ['GET'], 'function': check_repo},
755 conditions={'method': ['GET'], 'function': check_repo},
758 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
759 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
757 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
760 controller='admin/repos', action='edit_caches',
758 controller='admin/repos', action='edit_caches',
761 conditions={'method': ['PUT'], 'function': check_repo},
759 conditions={'method': ['PUT'], 'function': check_repo},
762 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
763
761
764 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
762 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
765 controller='admin/repos', action='edit_remote_form',
763 controller='admin/repos', action='edit_remote_form',
766 conditions={'method': ['GET'], 'function': check_repo},
764 conditions={'method': ['GET'], 'function': check_repo},
767 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
768 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
766 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
769 controller='admin/repos', action='edit_remote',
767 controller='admin/repos', action='edit_remote',
770 conditions={'method': ['PUT'], 'function': check_repo},
768 conditions={'method': ['PUT'], 'function': check_repo},
771 requirements=URL_NAME_REQUIREMENTS)
769 requirements=URL_NAME_REQUIREMENTS)
772
770
773 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
771 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
774 controller='admin/repos', action='edit_statistics_form',
772 controller='admin/repos', action='edit_statistics_form',
775 conditions={'method': ['GET'], 'function': check_repo},
773 conditions={'method': ['GET'], 'function': check_repo},
776 requirements=URL_NAME_REQUIREMENTS)
774 requirements=URL_NAME_REQUIREMENTS)
777 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
775 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
778 controller='admin/repos', action='edit_statistics',
776 controller='admin/repos', action='edit_statistics',
779 conditions={'method': ['PUT'], 'function': check_repo},
777 conditions={'method': ['PUT'], 'function': check_repo},
780 requirements=URL_NAME_REQUIREMENTS)
778 requirements=URL_NAME_REQUIREMENTS)
781 rmap.connect('repo_settings_issuetracker',
779 rmap.connect('repo_settings_issuetracker',
782 '/{repo_name}/settings/issue-tracker',
780 '/{repo_name}/settings/issue-tracker',
783 controller='admin/repos', action='repo_issuetracker',
781 controller='admin/repos', action='repo_issuetracker',
784 conditions={'method': ['GET'], 'function': check_repo},
782 conditions={'method': ['GET'], 'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
783 requirements=URL_NAME_REQUIREMENTS)
786 rmap.connect('repo_issuetracker_test',
784 rmap.connect('repo_issuetracker_test',
787 '/{repo_name}/settings/issue-tracker/test',
785 '/{repo_name}/settings/issue-tracker/test',
788 controller='admin/repos', action='repo_issuetracker_test',
786 controller='admin/repos', action='repo_issuetracker_test',
789 conditions={'method': ['POST'], 'function': check_repo},
787 conditions={'method': ['POST'], 'function': check_repo},
790 requirements=URL_NAME_REQUIREMENTS)
788 requirements=URL_NAME_REQUIREMENTS)
791 rmap.connect('repo_issuetracker_delete',
789 rmap.connect('repo_issuetracker_delete',
792 '/{repo_name}/settings/issue-tracker/delete',
790 '/{repo_name}/settings/issue-tracker/delete',
793 controller='admin/repos', action='repo_issuetracker_delete',
791 controller='admin/repos', action='repo_issuetracker_delete',
794 conditions={'method': ['DELETE'], 'function': check_repo},
792 conditions={'method': ['DELETE'], 'function': check_repo},
795 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
796 rmap.connect('repo_issuetracker_save',
794 rmap.connect('repo_issuetracker_save',
797 '/{repo_name}/settings/issue-tracker/save',
795 '/{repo_name}/settings/issue-tracker/save',
798 controller='admin/repos', action='repo_issuetracker_save',
796 controller='admin/repos', action='repo_issuetracker_save',
799 conditions={'method': ['POST'], 'function': check_repo},
797 conditions={'method': ['POST'], 'function': check_repo},
800 requirements=URL_NAME_REQUIREMENTS)
798 requirements=URL_NAME_REQUIREMENTS)
801 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
799 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
802 controller='admin/repos', action='repo_settings_vcs_update',
800 controller='admin/repos', action='repo_settings_vcs_update',
803 conditions={'method': ['POST'], 'function': check_repo},
801 conditions={'method': ['POST'], 'function': check_repo},
804 requirements=URL_NAME_REQUIREMENTS)
802 requirements=URL_NAME_REQUIREMENTS)
805 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
803 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
806 controller='admin/repos', action='repo_settings_vcs',
804 controller='admin/repos', action='repo_settings_vcs',
807 conditions={'method': ['GET'], 'function': check_repo},
805 conditions={'method': ['GET'], 'function': check_repo},
808 requirements=URL_NAME_REQUIREMENTS)
806 requirements=URL_NAME_REQUIREMENTS)
809 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
807 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
810 controller='admin/repos', action='repo_delete_svn_pattern',
808 controller='admin/repos', action='repo_delete_svn_pattern',
811 conditions={'method': ['DELETE'], 'function': check_repo},
809 conditions={'method': ['DELETE'], 'function': check_repo},
812 requirements=URL_NAME_REQUIREMENTS)
810 requirements=URL_NAME_REQUIREMENTS)
813 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
811 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
814 controller='admin/repos', action='repo_settings_pullrequest',
812 controller='admin/repos', action='repo_settings_pullrequest',
815 conditions={'method': ['GET', 'POST'], 'function': check_repo},
813 conditions={'method': ['GET', 'POST'], 'function': check_repo},
816 requirements=URL_NAME_REQUIREMENTS)
814 requirements=URL_NAME_REQUIREMENTS)
817
815
818 # still working url for backward compat.
816 # still working url for backward compat.
819 rmap.connect('raw_changeset_home_depraced',
817 rmap.connect('raw_changeset_home_depraced',
820 '/{repo_name}/raw-changeset/{revision}',
818 '/{repo_name}/raw-changeset/{revision}',
821 controller='changeset', action='changeset_raw',
819 controller='changeset', action='changeset_raw',
822 revision='tip', conditions={'function': check_repo},
820 revision='tip', conditions={'function': check_repo},
823 requirements=URL_NAME_REQUIREMENTS)
821 requirements=URL_NAME_REQUIREMENTS)
824
822
825 # new URLs
823 # new URLs
826 rmap.connect('changeset_raw_home',
824 rmap.connect('changeset_raw_home',
827 '/{repo_name}/changeset-diff/{revision}',
825 '/{repo_name}/changeset-diff/{revision}',
828 controller='changeset', action='changeset_raw',
826 controller='changeset', action='changeset_raw',
829 revision='tip', conditions={'function': check_repo},
827 revision='tip', conditions={'function': check_repo},
830 requirements=URL_NAME_REQUIREMENTS)
828 requirements=URL_NAME_REQUIREMENTS)
831
829
832 rmap.connect('changeset_patch_home',
830 rmap.connect('changeset_patch_home',
833 '/{repo_name}/changeset-patch/{revision}',
831 '/{repo_name}/changeset-patch/{revision}',
834 controller='changeset', action='changeset_patch',
832 controller='changeset', action='changeset_patch',
835 revision='tip', conditions={'function': check_repo},
833 revision='tip', conditions={'function': check_repo},
836 requirements=URL_NAME_REQUIREMENTS)
834 requirements=URL_NAME_REQUIREMENTS)
837
835
838 rmap.connect('changeset_download_home',
836 rmap.connect('changeset_download_home',
839 '/{repo_name}/changeset-download/{revision}',
837 '/{repo_name}/changeset-download/{revision}',
840 controller='changeset', action='changeset_download',
838 controller='changeset', action='changeset_download',
841 revision='tip', conditions={'function': check_repo},
839 revision='tip', conditions={'function': check_repo},
842 requirements=URL_NAME_REQUIREMENTS)
840 requirements=URL_NAME_REQUIREMENTS)
843
841
844 rmap.connect('changeset_comment',
842 rmap.connect('changeset_comment',
845 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
843 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
846 controller='changeset', revision='tip', action='comment',
844 controller='changeset', revision='tip', action='comment',
847 conditions={'function': check_repo},
845 conditions={'function': check_repo},
848 requirements=URL_NAME_REQUIREMENTS)
846 requirements=URL_NAME_REQUIREMENTS)
849
847
850 rmap.connect('changeset_comment_preview',
848 rmap.connect('changeset_comment_preview',
851 '/{repo_name}/changeset/comment/preview', jsroute=True,
849 '/{repo_name}/changeset/comment/preview', jsroute=True,
852 controller='changeset', action='preview_comment',
850 controller='changeset', action='preview_comment',
853 conditions={'function': check_repo, 'method': ['POST']},
851 conditions={'function': check_repo, 'method': ['POST']},
854 requirements=URL_NAME_REQUIREMENTS)
852 requirements=URL_NAME_REQUIREMENTS)
855
853
856 rmap.connect('changeset_comment_delete',
854 rmap.connect('changeset_comment_delete',
857 '/{repo_name}/changeset/comment/{comment_id}/delete',
855 '/{repo_name}/changeset/comment/{comment_id}/delete',
858 controller='changeset', action='delete_comment',
856 controller='changeset', action='delete_comment',
859 conditions={'function': check_repo, 'method': ['DELETE']},
857 conditions={'function': check_repo, 'method': ['DELETE']},
860 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
858 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
861
859
862 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
860 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
863 controller='changeset', action='changeset_info',
861 controller='changeset', action='changeset_info',
864 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
862 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
865
863
866 rmap.connect('compare_home',
864 rmap.connect('compare_home',
867 '/{repo_name}/compare',
865 '/{repo_name}/compare',
868 controller='compare', action='index',
866 controller='compare', action='index',
869 conditions={'function': check_repo},
867 conditions={'function': check_repo},
870 requirements=URL_NAME_REQUIREMENTS)
868 requirements=URL_NAME_REQUIREMENTS)
871
869
872 rmap.connect('compare_url',
870 rmap.connect('compare_url',
873 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
871 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
874 controller='compare', action='compare',
872 controller='compare', action='compare',
875 conditions={'function': check_repo},
873 conditions={'function': check_repo},
876 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877
875
878 rmap.connect('pullrequest_home',
876 rmap.connect('pullrequest_home',
879 '/{repo_name}/pull-request/new', controller='pullrequests',
877 '/{repo_name}/pull-request/new', controller='pullrequests',
880 action='index', conditions={'function': check_repo,
878 action='index', conditions={'function': check_repo,
881 'method': ['GET']},
879 'method': ['GET']},
882 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
880 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
883
881
884 rmap.connect('pullrequest',
882 rmap.connect('pullrequest',
885 '/{repo_name}/pull-request/new', controller='pullrequests',
883 '/{repo_name}/pull-request/new', controller='pullrequests',
886 action='create', conditions={'function': check_repo,
884 action='create', conditions={'function': check_repo,
887 'method': ['POST']},
885 'method': ['POST']},
888 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
886 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
889
887
890 rmap.connect('pullrequest_repo_refs',
888 rmap.connect('pullrequest_repo_refs',
891 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
889 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
892 controller='pullrequests',
890 controller='pullrequests',
893 action='get_repo_refs',
891 action='get_repo_refs',
894 conditions={'function': check_repo, 'method': ['GET']},
892 conditions={'function': check_repo, 'method': ['GET']},
895 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
896
894
897 rmap.connect('pullrequest_repo_destinations',
895 rmap.connect('pullrequest_repo_destinations',
898 '/{repo_name}/pull-request/repo-destinations',
896 '/{repo_name}/pull-request/repo-destinations',
899 controller='pullrequests',
897 controller='pullrequests',
900 action='get_repo_destinations',
898 action='get_repo_destinations',
901 conditions={'function': check_repo, 'method': ['GET']},
899 conditions={'function': check_repo, 'method': ['GET']},
902 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
903
901
904 rmap.connect('pullrequest_show',
902 rmap.connect('pullrequest_show',
905 '/{repo_name}/pull-request/{pull_request_id}',
903 '/{repo_name}/pull-request/{pull_request_id}',
906 controller='pullrequests',
904 controller='pullrequests',
907 action='show', conditions={'function': check_repo,
905 action='show', conditions={'function': check_repo,
908 'method': ['GET']},
906 'method': ['GET']},
909 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
907 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
910
908
911 rmap.connect('pullrequest_update',
909 rmap.connect('pullrequest_update',
912 '/{repo_name}/pull-request/{pull_request_id}',
910 '/{repo_name}/pull-request/{pull_request_id}',
913 controller='pullrequests',
911 controller='pullrequests',
914 action='update', conditions={'function': check_repo,
912 action='update', conditions={'function': check_repo,
915 'method': ['PUT']},
913 'method': ['PUT']},
916 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
914 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
917
915
918 rmap.connect('pullrequest_merge',
916 rmap.connect('pullrequest_merge',
919 '/{repo_name}/pull-request/{pull_request_id}',
917 '/{repo_name}/pull-request/{pull_request_id}',
920 controller='pullrequests',
918 controller='pullrequests',
921 action='merge', conditions={'function': check_repo,
919 action='merge', conditions={'function': check_repo,
922 'method': ['POST']},
920 'method': ['POST']},
923 requirements=URL_NAME_REQUIREMENTS)
921 requirements=URL_NAME_REQUIREMENTS)
924
922
925 rmap.connect('pullrequest_delete',
923 rmap.connect('pullrequest_delete',
926 '/{repo_name}/pull-request/{pull_request_id}',
924 '/{repo_name}/pull-request/{pull_request_id}',
927 controller='pullrequests',
925 controller='pullrequests',
928 action='delete', conditions={'function': check_repo,
926 action='delete', conditions={'function': check_repo,
929 'method': ['DELETE']},
927 'method': ['DELETE']},
930 requirements=URL_NAME_REQUIREMENTS)
928 requirements=URL_NAME_REQUIREMENTS)
931
929
932 rmap.connect('pullrequest_show_all',
930 rmap.connect('pullrequest_show_all',
933 '/{repo_name}/pull-request',
931 '/{repo_name}/pull-request',
934 controller='pullrequests',
932 controller='pullrequests',
935 action='show_all', conditions={'function': check_repo,
933 action='show_all', conditions={'function': check_repo,
936 'method': ['GET']},
934 'method': ['GET']},
937 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
935 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
938
936
939 rmap.connect('pullrequest_comment',
937 rmap.connect('pullrequest_comment',
940 '/{repo_name}/pull-request-comment/{pull_request_id}',
938 '/{repo_name}/pull-request-comment/{pull_request_id}',
941 controller='pullrequests',
939 controller='pullrequests',
942 action='comment', conditions={'function': check_repo,
940 action='comment', conditions={'function': check_repo,
943 'method': ['POST']},
941 'method': ['POST']},
944 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
942 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
945
943
946 rmap.connect('pullrequest_comment_delete',
944 rmap.connect('pullrequest_comment_delete',
947 '/{repo_name}/pull-request-comment/{comment_id}/delete',
945 '/{repo_name}/pull-request-comment/{comment_id}/delete',
948 controller='pullrequests', action='delete_comment',
946 controller='pullrequests', action='delete_comment',
949 conditions={'function': check_repo, 'method': ['DELETE']},
947 conditions={'function': check_repo, 'method': ['DELETE']},
950 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
948 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
951
949
952 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
950 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
953 controller='summary', conditions={'function': check_repo},
951 controller='summary', conditions={'function': check_repo},
954 requirements=URL_NAME_REQUIREMENTS)
952 requirements=URL_NAME_REQUIREMENTS)
955
953
956 rmap.connect('branches_home', '/{repo_name}/branches',
954 rmap.connect('branches_home', '/{repo_name}/branches',
957 controller='branches', conditions={'function': check_repo},
955 controller='branches', conditions={'function': check_repo},
958 requirements=URL_NAME_REQUIREMENTS)
956 requirements=URL_NAME_REQUIREMENTS)
959
957
960 rmap.connect('tags_home', '/{repo_name}/tags',
958 rmap.connect('tags_home', '/{repo_name}/tags',
961 controller='tags', conditions={'function': check_repo},
959 controller='tags', conditions={'function': check_repo},
962 requirements=URL_NAME_REQUIREMENTS)
960 requirements=URL_NAME_REQUIREMENTS)
963
961
964 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
962 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
965 controller='bookmarks', conditions={'function': check_repo},
963 controller='bookmarks', conditions={'function': check_repo},
966 requirements=URL_NAME_REQUIREMENTS)
964 requirements=URL_NAME_REQUIREMENTS)
967
965
968 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
966 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
969 controller='changelog', conditions={'function': check_repo},
967 controller='changelog', conditions={'function': check_repo},
970 requirements=URL_NAME_REQUIREMENTS)
968 requirements=URL_NAME_REQUIREMENTS)
971
969
972 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
970 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
973 controller='changelog', action='changelog_summary',
971 controller='changelog', action='changelog_summary',
974 conditions={'function': check_repo},
972 conditions={'function': check_repo},
975 requirements=URL_NAME_REQUIREMENTS)
973 requirements=URL_NAME_REQUIREMENTS)
976
974
977 rmap.connect('changelog_file_home',
975 rmap.connect('changelog_file_home',
978 '/{repo_name}/changelog/{revision}/{f_path}',
976 '/{repo_name}/changelog/{revision}/{f_path}',
979 controller='changelog', f_path=None,
977 controller='changelog', f_path=None,
980 conditions={'function': check_repo},
978 conditions={'function': check_repo},
981 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
979 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
982
980
983 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
981 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
984 controller='changelog', action='changelog_elements',
982 controller='changelog', action='changelog_elements',
985 conditions={'function': check_repo},
983 conditions={'function': check_repo},
986 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
984 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
987
985
988 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
986 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
989 controller='files', revision='tip', f_path='',
987 controller='files', revision='tip', f_path='',
990 conditions={'function': check_repo},
988 conditions={'function': check_repo},
991 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
989 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
992
990
993 rmap.connect('files_home_simple_catchrev',
991 rmap.connect('files_home_simple_catchrev',
994 '/{repo_name}/files/{revision}',
992 '/{repo_name}/files/{revision}',
995 controller='files', revision='tip', f_path='',
993 controller='files', revision='tip', f_path='',
996 conditions={'function': check_repo},
994 conditions={'function': check_repo},
997 requirements=URL_NAME_REQUIREMENTS)
995 requirements=URL_NAME_REQUIREMENTS)
998
996
999 rmap.connect('files_home_simple_catchall',
997 rmap.connect('files_home_simple_catchall',
1000 '/{repo_name}/files',
998 '/{repo_name}/files',
1001 controller='files', revision='tip', f_path='',
999 controller='files', revision='tip', f_path='',
1002 conditions={'function': check_repo},
1000 conditions={'function': check_repo},
1003 requirements=URL_NAME_REQUIREMENTS)
1001 requirements=URL_NAME_REQUIREMENTS)
1004
1002
1005 rmap.connect('files_history_home',
1003 rmap.connect('files_history_home',
1006 '/{repo_name}/history/{revision}/{f_path}',
1004 '/{repo_name}/history/{revision}/{f_path}',
1007 controller='files', action='history', revision='tip', f_path='',
1005 controller='files', action='history', revision='tip', f_path='',
1008 conditions={'function': check_repo},
1006 conditions={'function': check_repo},
1009 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1007 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1010
1008
1011 rmap.connect('files_authors_home',
1009 rmap.connect('files_authors_home',
1012 '/{repo_name}/authors/{revision}/{f_path}',
1010 '/{repo_name}/authors/{revision}/{f_path}',
1013 controller='files', action='authors', revision='tip', f_path='',
1011 controller='files', action='authors', revision='tip', f_path='',
1014 conditions={'function': check_repo},
1012 conditions={'function': check_repo},
1015 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1013 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1016
1014
1017 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1015 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1018 controller='files', action='diff', f_path='',
1016 controller='files', action='diff', f_path='',
1019 conditions={'function': check_repo},
1017 conditions={'function': check_repo},
1020 requirements=URL_NAME_REQUIREMENTS)
1018 requirements=URL_NAME_REQUIREMENTS)
1021
1019
1022 rmap.connect('files_diff_2way_home',
1020 rmap.connect('files_diff_2way_home',
1023 '/{repo_name}/diff-2way/{f_path}',
1021 '/{repo_name}/diff-2way/{f_path}',
1024 controller='files', action='diff_2way', f_path='',
1022 controller='files', action='diff_2way', f_path='',
1025 conditions={'function': check_repo},
1023 conditions={'function': check_repo},
1026 requirements=URL_NAME_REQUIREMENTS)
1024 requirements=URL_NAME_REQUIREMENTS)
1027
1025
1028 rmap.connect('files_rawfile_home',
1026 rmap.connect('files_rawfile_home',
1029 '/{repo_name}/rawfile/{revision}/{f_path}',
1027 '/{repo_name}/rawfile/{revision}/{f_path}',
1030 controller='files', action='rawfile', revision='tip',
1028 controller='files', action='rawfile', revision='tip',
1031 f_path='', conditions={'function': check_repo},
1029 f_path='', conditions={'function': check_repo},
1032 requirements=URL_NAME_REQUIREMENTS)
1030 requirements=URL_NAME_REQUIREMENTS)
1033
1031
1034 rmap.connect('files_raw_home',
1032 rmap.connect('files_raw_home',
1035 '/{repo_name}/raw/{revision}/{f_path}',
1033 '/{repo_name}/raw/{revision}/{f_path}',
1036 controller='files', action='raw', revision='tip', f_path='',
1034 controller='files', action='raw', revision='tip', f_path='',
1037 conditions={'function': check_repo},
1035 conditions={'function': check_repo},
1038 requirements=URL_NAME_REQUIREMENTS)
1036 requirements=URL_NAME_REQUIREMENTS)
1039
1037
1040 rmap.connect('files_render_home',
1038 rmap.connect('files_render_home',
1041 '/{repo_name}/render/{revision}/{f_path}',
1039 '/{repo_name}/render/{revision}/{f_path}',
1042 controller='files', action='index', revision='tip', f_path='',
1040 controller='files', action='index', revision='tip', f_path='',
1043 rendered=True, conditions={'function': check_repo},
1041 rendered=True, conditions={'function': check_repo},
1044 requirements=URL_NAME_REQUIREMENTS)
1042 requirements=URL_NAME_REQUIREMENTS)
1045
1043
1046 rmap.connect('files_annotate_home',
1044 rmap.connect('files_annotate_home',
1047 '/{repo_name}/annotate/{revision}/{f_path}',
1045 '/{repo_name}/annotate/{revision}/{f_path}',
1048 controller='files', action='index', revision='tip',
1046 controller='files', action='index', revision='tip',
1049 f_path='', annotate=True, conditions={'function': check_repo},
1047 f_path='', annotate=True, conditions={'function': check_repo},
1050 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1048 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1051
1049
1052 rmap.connect('files_annotate_previous',
1050 rmap.connect('files_annotate_previous',
1053 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1051 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1054 controller='files', action='annotate_previous', revision='tip',
1052 controller='files', action='annotate_previous', revision='tip',
1055 f_path='', annotate=True, conditions={'function': check_repo},
1053 f_path='', annotate=True, conditions={'function': check_repo},
1056 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1054 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1057
1055
1058 rmap.connect('files_edit',
1056 rmap.connect('files_edit',
1059 '/{repo_name}/edit/{revision}/{f_path}',
1057 '/{repo_name}/edit/{revision}/{f_path}',
1060 controller='files', action='edit', revision='tip',
1058 controller='files', action='edit', revision='tip',
1061 f_path='',
1059 f_path='',
1062 conditions={'function': check_repo, 'method': ['POST']},
1060 conditions={'function': check_repo, 'method': ['POST']},
1063 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1064
1062
1065 rmap.connect('files_edit_home',
1063 rmap.connect('files_edit_home',
1066 '/{repo_name}/edit/{revision}/{f_path}',
1064 '/{repo_name}/edit/{revision}/{f_path}',
1067 controller='files', action='edit_home', revision='tip',
1065 controller='files', action='edit_home', revision='tip',
1068 f_path='', conditions={'function': check_repo},
1066 f_path='', conditions={'function': check_repo},
1069 requirements=URL_NAME_REQUIREMENTS)
1067 requirements=URL_NAME_REQUIREMENTS)
1070
1068
1071 rmap.connect('files_add',
1069 rmap.connect('files_add',
1072 '/{repo_name}/add/{revision}/{f_path}',
1070 '/{repo_name}/add/{revision}/{f_path}',
1073 controller='files', action='add', revision='tip',
1071 controller='files', action='add', revision='tip',
1074 f_path='',
1072 f_path='',
1075 conditions={'function': check_repo, 'method': ['POST']},
1073 conditions={'function': check_repo, 'method': ['POST']},
1076 requirements=URL_NAME_REQUIREMENTS)
1074 requirements=URL_NAME_REQUIREMENTS)
1077
1075
1078 rmap.connect('files_add_home',
1076 rmap.connect('files_add_home',
1079 '/{repo_name}/add/{revision}/{f_path}',
1077 '/{repo_name}/add/{revision}/{f_path}',
1080 controller='files', action='add_home', revision='tip',
1078 controller='files', action='add_home', revision='tip',
1081 f_path='', conditions={'function': check_repo},
1079 f_path='', conditions={'function': check_repo},
1082 requirements=URL_NAME_REQUIREMENTS)
1080 requirements=URL_NAME_REQUIREMENTS)
1083
1081
1084 rmap.connect('files_delete',
1082 rmap.connect('files_delete',
1085 '/{repo_name}/delete/{revision}/{f_path}',
1083 '/{repo_name}/delete/{revision}/{f_path}',
1086 controller='files', action='delete', revision='tip',
1084 controller='files', action='delete', revision='tip',
1087 f_path='',
1085 f_path='',
1088 conditions={'function': check_repo, 'method': ['POST']},
1086 conditions={'function': check_repo, 'method': ['POST']},
1089 requirements=URL_NAME_REQUIREMENTS)
1087 requirements=URL_NAME_REQUIREMENTS)
1090
1088
1091 rmap.connect('files_delete_home',
1089 rmap.connect('files_delete_home',
1092 '/{repo_name}/delete/{revision}/{f_path}',
1090 '/{repo_name}/delete/{revision}/{f_path}',
1093 controller='files', action='delete_home', revision='tip',
1091 controller='files', action='delete_home', revision='tip',
1094 f_path='', conditions={'function': check_repo},
1092 f_path='', conditions={'function': check_repo},
1095 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS)
1096
1094
1097 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1095 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1098 controller='files', action='archivefile',
1096 controller='files', action='archivefile',
1099 conditions={'function': check_repo},
1097 conditions={'function': check_repo},
1100 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1098 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1101
1099
1102 rmap.connect('files_nodelist_home',
1100 rmap.connect('files_nodelist_home',
1103 '/{repo_name}/nodelist/{revision}/{f_path}',
1101 '/{repo_name}/nodelist/{revision}/{f_path}',
1104 controller='files', action='nodelist',
1102 controller='files', action='nodelist',
1105 conditions={'function': check_repo},
1103 conditions={'function': check_repo},
1106 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1107
1105
1108 rmap.connect('files_nodetree_full',
1106 rmap.connect('files_nodetree_full',
1109 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1107 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1110 controller='files', action='nodetree_full',
1108 controller='files', action='nodetree_full',
1111 conditions={'function': check_repo},
1109 conditions={'function': check_repo},
1112 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1113
1111
1114 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1112 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1115 controller='forks', action='fork_create',
1113 controller='forks', action='fork_create',
1116 conditions={'function': check_repo, 'method': ['POST']},
1114 conditions={'function': check_repo, 'method': ['POST']},
1117 requirements=URL_NAME_REQUIREMENTS)
1115 requirements=URL_NAME_REQUIREMENTS)
1118
1116
1119 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1117 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1120 controller='forks', action='fork',
1118 controller='forks', action='fork',
1121 conditions={'function': check_repo},
1119 conditions={'function': check_repo},
1122 requirements=URL_NAME_REQUIREMENTS)
1120 requirements=URL_NAME_REQUIREMENTS)
1123
1121
1124 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1122 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1125 controller='forks', action='forks',
1123 controller='forks', action='forks',
1126 conditions={'function': check_repo},
1124 conditions={'function': check_repo},
1127 requirements=URL_NAME_REQUIREMENTS)
1125 requirements=URL_NAME_REQUIREMENTS)
1128
1126
1129 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1127 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1130 controller='followers', action='followers',
1128 controller='followers', action='followers',
1131 conditions={'function': check_repo},
1129 conditions={'function': check_repo},
1132 requirements=URL_NAME_REQUIREMENTS)
1130 requirements=URL_NAME_REQUIREMENTS)
1133
1131
1134 # must be here for proper group/repo catching pattern
1132 # must be here for proper group/repo catching pattern
1135 _connect_with_slash(
1133 _connect_with_slash(
1136 rmap, 'repo_group_home', '/{group_name}',
1134 rmap, 'repo_group_home', '/{group_name}',
1137 controller='home', action='index_repo_group',
1135 controller='home', action='index_repo_group',
1138 conditions={'function': check_group},
1136 conditions={'function': check_group},
1139 requirements=URL_NAME_REQUIREMENTS)
1137 requirements=URL_NAME_REQUIREMENTS)
1140
1138
1141 # catch all, at the end
1139 # catch all, at the end
1142 _connect_with_slash(
1140 _connect_with_slash(
1143 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1141 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1144 controller='summary', action='index',
1142 controller='summary', action='index',
1145 conditions={'function': check_repo},
1143 conditions={'function': check_repo},
1146 requirements=URL_NAME_REQUIREMENTS)
1144 requirements=URL_NAME_REQUIREMENTS)
1147
1145
1148 return rmap
1146 return rmap
1149
1147
1150
1148
1151 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1149 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1152 """
1150 """
1153 Connect a route with an optional trailing slash in `path`.
1151 Connect a route with an optional trailing slash in `path`.
1154 """
1152 """
1155 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1153 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1156 mapper.connect(name, path, *args, **kwargs)
1154 mapper.connect(name, path, *args, **kwargs)
@@ -1,418 +1,418 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27 import datetime
27 import datetime
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
31 from pyramid.threadlocal import get_current_registry
32 from pylons import request, tmpl_context as c, url, session
32 from pylons import request, tmpl_context as c, url, session
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36
36
37 from rhodecode import forms
37 from rhodecode import forms
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib import auth
39 from rhodecode.lib import auth
40 from rhodecode.lib.auth import (
40 from rhodecode.lib.auth import (
41 LoginRequired, NotAnonymous, AuthUser)
41 LoginRequired, NotAnonymous, AuthUser)
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils import jsonify
44 from rhodecode.lib.utils2 import safe_int, md5, str2bool
44 from rhodecode.lib.utils2 import safe_int, md5, str2bool
45 from rhodecode.lib.ext_json import json
45 from rhodecode.lib.ext_json import json
46 from rhodecode.lib.channelstream import channelstream_request, \
46 from rhodecode.lib.channelstream import channelstream_request, \
47 ChannelstreamException
47 ChannelstreamException
48
48
49 from rhodecode.model.validation_schema.schemas import user_schema
49 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.db import (
50 from rhodecode.model.db import (
51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 from rhodecode.model.forms import UserForm
52 from rhodecode.model.forms import UserForm
53 from rhodecode.model.scm import RepoList
53 from rhodecode.model.scm import RepoList
54 from rhodecode.model.user import UserModel
54 from rhodecode.model.user import UserModel
55 from rhodecode.model.repo import RepoModel
55 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57 from rhodecode.model.pull_request import PullRequestModel
57 from rhodecode.model.pull_request import PullRequestModel
58 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class MyAccountController(BaseController):
63 class MyAccountController(BaseController):
64 """REST Controller styled on the Atom Publishing Protocol"""
64 """REST Controller styled on the Atom Publishing Protocol"""
65 # To properly map this controller, ensure your config/routing.py
65 # To properly map this controller, ensure your config/routing.py
66 # file has a resource setup:
66 # file has a resource setup:
67 # map.resource('setting', 'settings', controller='admin/settings',
67 # map.resource('setting', 'settings', controller='admin/settings',
68 # path_prefix='/admin', name_prefix='admin_')
68 # path_prefix='/admin', name_prefix='admin_')
69
69
70 @LoginRequired()
70 @LoginRequired()
71 @NotAnonymous()
71 @NotAnonymous()
72 def __before__(self):
72 def __before__(self):
73 super(MyAccountController, self).__before__()
73 super(MyAccountController, self).__before__()
74
74
75 def __load_data(self):
75 def __load_data(self):
76 c.user = User.get(c.rhodecode_user.user_id)
76 c.user = User.get(c.rhodecode_user.user_id)
77 if c.user.username == User.DEFAULT_USER:
77 if c.user.username == User.DEFAULT_USER:
78 h.flash(_("You can't edit this user since it's"
78 h.flash(_("You can't edit this user since it's"
79 " crucial for entire application"), category='warning')
79 " crucial for entire application"), category='warning')
80 return redirect(url('users'))
80 return redirect(h.route_path('users'))
81
81
82 c.auth_user = AuthUser(
82 c.auth_user = AuthUser(
83 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
83 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
84
84
85 def _load_my_repos_data(self, watched=False):
85 def _load_my_repos_data(self, watched=False):
86 if watched:
86 if watched:
87 admin = False
87 admin = False
88 follows_repos = Session().query(UserFollowing)\
88 follows_repos = Session().query(UserFollowing)\
89 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
89 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
90 .options(joinedload(UserFollowing.follows_repository))\
90 .options(joinedload(UserFollowing.follows_repository))\
91 .all()
91 .all()
92 repo_list = [x.follows_repository for x in follows_repos]
92 repo_list = [x.follows_repository for x in follows_repos]
93 else:
93 else:
94 admin = True
94 admin = True
95 repo_list = Repository.get_all_repos(
95 repo_list = Repository.get_all_repos(
96 user_id=c.rhodecode_user.user_id)
96 user_id=c.rhodecode_user.user_id)
97 repo_list = RepoList(repo_list, perm_set=[
97 repo_list = RepoList(repo_list, perm_set=[
98 'repository.read', 'repository.write', 'repository.admin'])
98 'repository.read', 'repository.write', 'repository.admin'])
99
99
100 repos_data = RepoModel().get_repos_as_dict(
100 repos_data = RepoModel().get_repos_as_dict(
101 repo_list=repo_list, admin=admin)
101 repo_list=repo_list, admin=admin)
102 # json used to render the grid
102 # json used to render the grid
103 return json.dumps(repos_data)
103 return json.dumps(repos_data)
104
104
105 @auth.CSRFRequired()
105 @auth.CSRFRequired()
106 def my_account_update(self):
106 def my_account_update(self):
107 """
107 """
108 POST /_admin/my_account Updates info of my account
108 POST /_admin/my_account Updates info of my account
109 """
109 """
110 # url('my_account')
110 # url('my_account')
111 c.active = 'profile_edit'
111 c.active = 'profile_edit'
112 self.__load_data()
112 self.__load_data()
113 c.perm_user = c.auth_user
113 c.perm_user = c.auth_user
114 c.extern_type = c.user.extern_type
114 c.extern_type = c.user.extern_type
115 c.extern_name = c.user.extern_name
115 c.extern_name = c.user.extern_name
116
116
117 defaults = c.user.get_dict()
117 defaults = c.user.get_dict()
118 update = False
118 update = False
119 _form = UserForm(edit=True,
119 _form = UserForm(edit=True,
120 old_data={'user_id': c.rhodecode_user.user_id,
120 old_data={'user_id': c.rhodecode_user.user_id,
121 'email': c.rhodecode_user.email})()
121 'email': c.rhodecode_user.email})()
122 form_result = {}
122 form_result = {}
123 try:
123 try:
124 post_data = dict(request.POST)
124 post_data = dict(request.POST)
125 post_data['new_password'] = ''
125 post_data['new_password'] = ''
126 post_data['password_confirmation'] = ''
126 post_data['password_confirmation'] = ''
127 form_result = _form.to_python(post_data)
127 form_result = _form.to_python(post_data)
128 # skip updating those attrs for my account
128 # skip updating those attrs for my account
129 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
129 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
130 'new_password', 'password_confirmation']
130 'new_password', 'password_confirmation']
131 # TODO: plugin should define if username can be updated
131 # TODO: plugin should define if username can be updated
132 if c.extern_type != "rhodecode":
132 if c.extern_type != "rhodecode":
133 # forbid updating username for external accounts
133 # forbid updating username for external accounts
134 skip_attrs.append('username')
134 skip_attrs.append('username')
135
135
136 UserModel().update_user(
136 UserModel().update_user(
137 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
137 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
138 h.flash(_('Your account was updated successfully'),
138 h.flash(_('Your account was updated successfully'),
139 category='success')
139 category='success')
140 Session().commit()
140 Session().commit()
141 update = True
141 update = True
142
142
143 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
144 return htmlfill.render(
144 return htmlfill.render(
145 render('admin/my_account/my_account.mako'),
145 render('admin/my_account/my_account.mako'),
146 defaults=errors.value,
146 defaults=errors.value,
147 errors=errors.error_dict or {},
147 errors=errors.error_dict or {},
148 prefix_error=False,
148 prefix_error=False,
149 encoding="UTF-8",
149 encoding="UTF-8",
150 force_defaults=False)
150 force_defaults=False)
151 except Exception:
151 except Exception:
152 log.exception("Exception updating user")
152 log.exception("Exception updating user")
153 h.flash(_('Error occurred during update of user %s')
153 h.flash(_('Error occurred during update of user %s')
154 % form_result.get('username'), category='error')
154 % form_result.get('username'), category='error')
155
155
156 if update:
156 if update:
157 return redirect('my_account')
157 return redirect('my_account')
158
158
159 return htmlfill.render(
159 return htmlfill.render(
160 render('admin/my_account/my_account.mako'),
160 render('admin/my_account/my_account.mako'),
161 defaults=defaults,
161 defaults=defaults,
162 encoding="UTF-8",
162 encoding="UTF-8",
163 force_defaults=False
163 force_defaults=False
164 )
164 )
165
165
166 def my_account(self):
166 def my_account(self):
167 """
167 """
168 GET /_admin/my_account Displays info about my account
168 GET /_admin/my_account Displays info about my account
169 """
169 """
170 # url('my_account')
170 # url('my_account')
171 c.active = 'profile'
171 c.active = 'profile'
172 self.__load_data()
172 self.__load_data()
173
173
174 defaults = c.user.get_dict()
174 defaults = c.user.get_dict()
175 return htmlfill.render(
175 return htmlfill.render(
176 render('admin/my_account/my_account.mako'),
176 render('admin/my_account/my_account.mako'),
177 defaults=defaults, encoding="UTF-8", force_defaults=False)
177 defaults=defaults, encoding="UTF-8", force_defaults=False)
178
178
179 def my_account_edit(self):
179 def my_account_edit(self):
180 """
180 """
181 GET /_admin/my_account/edit Displays edit form of my account
181 GET /_admin/my_account/edit Displays edit form of my account
182 """
182 """
183 c.active = 'profile_edit'
183 c.active = 'profile_edit'
184 self.__load_data()
184 self.__load_data()
185 c.perm_user = c.auth_user
185 c.perm_user = c.auth_user
186 c.extern_type = c.user.extern_type
186 c.extern_type = c.user.extern_type
187 c.extern_name = c.user.extern_name
187 c.extern_name = c.user.extern_name
188
188
189 defaults = c.user.get_dict()
189 defaults = c.user.get_dict()
190 return htmlfill.render(
190 return htmlfill.render(
191 render('admin/my_account/my_account.mako'),
191 render('admin/my_account/my_account.mako'),
192 defaults=defaults,
192 defaults=defaults,
193 encoding="UTF-8",
193 encoding="UTF-8",
194 force_defaults=False
194 force_defaults=False
195 )
195 )
196
196
197 @auth.CSRFRequired(except_methods=['GET'])
197 @auth.CSRFRequired(except_methods=['GET'])
198 def my_account_password(self):
198 def my_account_password(self):
199 c.active = 'password'
199 c.active = 'password'
200 self.__load_data()
200 self.__load_data()
201 c.extern_type = c.user.extern_type
201 c.extern_type = c.user.extern_type
202
202
203 schema = user_schema.ChangePasswordSchema().bind(
203 schema = user_schema.ChangePasswordSchema().bind(
204 username=c.rhodecode_user.username)
204 username=c.rhodecode_user.username)
205
205
206 form = forms.Form(schema,
206 form = forms.Form(schema,
207 buttons=(forms.buttons.save, forms.buttons.reset))
207 buttons=(forms.buttons.save, forms.buttons.reset))
208
208
209 if request.method == 'POST' and c.extern_type == 'rhodecode':
209 if request.method == 'POST' and c.extern_type == 'rhodecode':
210 controls = request.POST.items()
210 controls = request.POST.items()
211 try:
211 try:
212 valid_data = form.validate(controls)
212 valid_data = form.validate(controls)
213 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
213 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
214 instance = c.rhodecode_user.get_instance()
214 instance = c.rhodecode_user.get_instance()
215 instance.update_userdata(force_password_change=False)
215 instance.update_userdata(force_password_change=False)
216 Session().commit()
216 Session().commit()
217 except forms.ValidationFailure as e:
217 except forms.ValidationFailure as e:
218 request.session.flash(
218 request.session.flash(
219 _('Error occurred during update of user password'),
219 _('Error occurred during update of user password'),
220 queue='error')
220 queue='error')
221 form = e
221 form = e
222 except Exception:
222 except Exception:
223 log.exception("Exception updating password")
223 log.exception("Exception updating password")
224 request.session.flash(
224 request.session.flash(
225 _('Error occurred during update of user password'),
225 _('Error occurred during update of user password'),
226 queue='error')
226 queue='error')
227 else:
227 else:
228 session.setdefault('rhodecode_user', {}).update(
228 session.setdefault('rhodecode_user', {}).update(
229 {'password': md5(instance.password)})
229 {'password': md5(instance.password)})
230 session.save()
230 session.save()
231 request.session.flash(
231 request.session.flash(
232 _("Successfully updated password"), queue='success')
232 _("Successfully updated password"), queue='success')
233 return redirect(url('my_account_password'))
233 return redirect(url('my_account_password'))
234
234
235 c.form = form
235 c.form = form
236 return render('admin/my_account/my_account.mako')
236 return render('admin/my_account/my_account.mako')
237
237
238 def my_account_repos(self):
238 def my_account_repos(self):
239 c.active = 'repos'
239 c.active = 'repos'
240 self.__load_data()
240 self.__load_data()
241
241
242 # json used to render the grid
242 # json used to render the grid
243 c.data = self._load_my_repos_data()
243 c.data = self._load_my_repos_data()
244 return render('admin/my_account/my_account.mako')
244 return render('admin/my_account/my_account.mako')
245
245
246 def my_account_watched(self):
246 def my_account_watched(self):
247 c.active = 'watched'
247 c.active = 'watched'
248 self.__load_data()
248 self.__load_data()
249
249
250 # json used to render the grid
250 # json used to render the grid
251 c.data = self._load_my_repos_data(watched=True)
251 c.data = self._load_my_repos_data(watched=True)
252 return render('admin/my_account/my_account.mako')
252 return render('admin/my_account/my_account.mako')
253
253
254 def my_account_perms(self):
254 def my_account_perms(self):
255 c.active = 'perms'
255 c.active = 'perms'
256 self.__load_data()
256 self.__load_data()
257 c.perm_user = c.auth_user
257 c.perm_user = c.auth_user
258
258
259 return render('admin/my_account/my_account.mako')
259 return render('admin/my_account/my_account.mako')
260
260
261 def my_account_emails(self):
261 def my_account_emails(self):
262 c.active = 'emails'
262 c.active = 'emails'
263 self.__load_data()
263 self.__load_data()
264
264
265 c.user_email_map = UserEmailMap.query()\
265 c.user_email_map = UserEmailMap.query()\
266 .filter(UserEmailMap.user == c.user).all()
266 .filter(UserEmailMap.user == c.user).all()
267 return render('admin/my_account/my_account.mako')
267 return render('admin/my_account/my_account.mako')
268
268
269 @auth.CSRFRequired()
269 @auth.CSRFRequired()
270 def my_account_emails_add(self):
270 def my_account_emails_add(self):
271 email = request.POST.get('new_email')
271 email = request.POST.get('new_email')
272
272
273 try:
273 try:
274 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
274 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
275 Session().commit()
275 Session().commit()
276 h.flash(_("Added new email address `%s` for user account") % email,
276 h.flash(_("Added new email address `%s` for user account") % email,
277 category='success')
277 category='success')
278 except formencode.Invalid as error:
278 except formencode.Invalid as error:
279 msg = error.error_dict['email']
279 msg = error.error_dict['email']
280 h.flash(msg, category='error')
280 h.flash(msg, category='error')
281 except Exception:
281 except Exception:
282 log.exception("Exception in my_account_emails")
282 log.exception("Exception in my_account_emails")
283 h.flash(_('An error occurred during email saving'),
283 h.flash(_('An error occurred during email saving'),
284 category='error')
284 category='error')
285 return redirect(url('my_account_emails'))
285 return redirect(url('my_account_emails'))
286
286
287 @auth.CSRFRequired()
287 @auth.CSRFRequired()
288 def my_account_emails_delete(self):
288 def my_account_emails_delete(self):
289 email_id = request.POST.get('del_email_id')
289 email_id = request.POST.get('del_email_id')
290 user_model = UserModel()
290 user_model = UserModel()
291 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
291 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
292 Session().commit()
292 Session().commit()
293 h.flash(_("Removed email address from user account"),
293 h.flash(_("Removed email address from user account"),
294 category='success')
294 category='success')
295 return redirect(url('my_account_emails'))
295 return redirect(url('my_account_emails'))
296
296
297 def _extract_ordering(self, request):
297 def _extract_ordering(self, request):
298 column_index = safe_int(request.GET.get('order[0][column]'))
298 column_index = safe_int(request.GET.get('order[0][column]'))
299 order_dir = request.GET.get('order[0][dir]', 'desc')
299 order_dir = request.GET.get('order[0][dir]', 'desc')
300 order_by = request.GET.get(
300 order_by = request.GET.get(
301 'columns[%s][data][sort]' % column_index, 'name_raw')
301 'columns[%s][data][sort]' % column_index, 'name_raw')
302 return order_by, order_dir
302 return order_by, order_dir
303
303
304 def _get_pull_requests_list(self, statuses):
304 def _get_pull_requests_list(self, statuses):
305 start = safe_int(request.GET.get('start'), 0)
305 start = safe_int(request.GET.get('start'), 0)
306 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
306 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
307 order_by, order_dir = self._extract_ordering(request)
307 order_by, order_dir = self._extract_ordering(request)
308
308
309 pull_requests = PullRequestModel().get_im_participating_in(
309 pull_requests = PullRequestModel().get_im_participating_in(
310 user_id=c.rhodecode_user.user_id,
310 user_id=c.rhodecode_user.user_id,
311 statuses=statuses,
311 statuses=statuses,
312 offset=start, length=length, order_by=order_by,
312 offset=start, length=length, order_by=order_by,
313 order_dir=order_dir)
313 order_dir=order_dir)
314
314
315 pull_requests_total_count = PullRequestModel().count_im_participating_in(
315 pull_requests_total_count = PullRequestModel().count_im_participating_in(
316 user_id=c.rhodecode_user.user_id, statuses=statuses)
316 user_id=c.rhodecode_user.user_id, statuses=statuses)
317
317
318 from rhodecode.lib.utils import PartialRenderer
318 from rhodecode.lib.utils import PartialRenderer
319 _render = PartialRenderer('data_table/_dt_elements.mako')
319 _render = PartialRenderer('data_table/_dt_elements.mako')
320 data = []
320 data = []
321 for pr in pull_requests:
321 for pr in pull_requests:
322 repo_id = pr.target_repo_id
322 repo_id = pr.target_repo_id
323 comments = CommentsModel().get_all_comments(
323 comments = CommentsModel().get_all_comments(
324 repo_id, pull_request=pr)
324 repo_id, pull_request=pr)
325 owned = pr.user_id == c.rhodecode_user.user_id
325 owned = pr.user_id == c.rhodecode_user.user_id
326 status = pr.calculated_review_status()
326 status = pr.calculated_review_status()
327
327
328 data.append({
328 data.append({
329 'target_repo': _render('pullrequest_target_repo',
329 'target_repo': _render('pullrequest_target_repo',
330 pr.target_repo.repo_name),
330 pr.target_repo.repo_name),
331 'name': _render('pullrequest_name',
331 'name': _render('pullrequest_name',
332 pr.pull_request_id, pr.target_repo.repo_name,
332 pr.pull_request_id, pr.target_repo.repo_name,
333 short=True),
333 short=True),
334 'name_raw': pr.pull_request_id,
334 'name_raw': pr.pull_request_id,
335 'status': _render('pullrequest_status', status),
335 'status': _render('pullrequest_status', status),
336 'title': _render(
336 'title': _render(
337 'pullrequest_title', pr.title, pr.description),
337 'pullrequest_title', pr.title, pr.description),
338 'description': h.escape(pr.description),
338 'description': h.escape(pr.description),
339 'updated_on': _render('pullrequest_updated_on',
339 'updated_on': _render('pullrequest_updated_on',
340 h.datetime_to_time(pr.updated_on)),
340 h.datetime_to_time(pr.updated_on)),
341 'updated_on_raw': h.datetime_to_time(pr.updated_on),
341 'updated_on_raw': h.datetime_to_time(pr.updated_on),
342 'created_on': _render('pullrequest_updated_on',
342 'created_on': _render('pullrequest_updated_on',
343 h.datetime_to_time(pr.created_on)),
343 h.datetime_to_time(pr.created_on)),
344 'created_on_raw': h.datetime_to_time(pr.created_on),
344 'created_on_raw': h.datetime_to_time(pr.created_on),
345 'author': _render('pullrequest_author',
345 'author': _render('pullrequest_author',
346 pr.author.full_contact, ),
346 pr.author.full_contact, ),
347 'author_raw': pr.author.full_name,
347 'author_raw': pr.author.full_name,
348 'comments': _render('pullrequest_comments', len(comments)),
348 'comments': _render('pullrequest_comments', len(comments)),
349 'comments_raw': len(comments),
349 'comments_raw': len(comments),
350 'closed': pr.is_closed(),
350 'closed': pr.is_closed(),
351 'owned': owned
351 'owned': owned
352 })
352 })
353 # json used to render the grid
353 # json used to render the grid
354 data = ({
354 data = ({
355 'data': data,
355 'data': data,
356 'recordsTotal': pull_requests_total_count,
356 'recordsTotal': pull_requests_total_count,
357 'recordsFiltered': pull_requests_total_count,
357 'recordsFiltered': pull_requests_total_count,
358 })
358 })
359 return data
359 return data
360
360
361 def my_account_pullrequests(self):
361 def my_account_pullrequests(self):
362 c.active = 'pullrequests'
362 c.active = 'pullrequests'
363 self.__load_data()
363 self.__load_data()
364 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
364 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
365
365
366 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
366 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
367 if c.show_closed:
367 if c.show_closed:
368 statuses += [PullRequest.STATUS_CLOSED]
368 statuses += [PullRequest.STATUS_CLOSED]
369 data = self._get_pull_requests_list(statuses)
369 data = self._get_pull_requests_list(statuses)
370 if not request.is_xhr:
370 if not request.is_xhr:
371 c.data_participate = json.dumps(data['data'])
371 c.data_participate = json.dumps(data['data'])
372 c.records_total_participate = data['recordsTotal']
372 c.records_total_participate = data['recordsTotal']
373 return render('admin/my_account/my_account.mako')
373 return render('admin/my_account/my_account.mako')
374 else:
374 else:
375 return json.dumps(data)
375 return json.dumps(data)
376
376
377 def my_notifications(self):
377 def my_notifications(self):
378 c.active = 'notifications'
378 c.active = 'notifications'
379 return render('admin/my_account/my_account.mako')
379 return render('admin/my_account/my_account.mako')
380
380
381 @auth.CSRFRequired()
381 @auth.CSRFRequired()
382 @jsonify
382 @jsonify
383 def my_notifications_toggle_visibility(self):
383 def my_notifications_toggle_visibility(self):
384 user = c.rhodecode_user.get_instance()
384 user = c.rhodecode_user.get_instance()
385 new_status = not user.user_data.get('notification_status', True)
385 new_status = not user.user_data.get('notification_status', True)
386 user.update_userdata(notification_status=new_status)
386 user.update_userdata(notification_status=new_status)
387 Session().commit()
387 Session().commit()
388 return user.user_data['notification_status']
388 return user.user_data['notification_status']
389
389
390 @auth.CSRFRequired()
390 @auth.CSRFRequired()
391 @jsonify
391 @jsonify
392 def my_account_notifications_test_channelstream(self):
392 def my_account_notifications_test_channelstream(self):
393 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
393 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
394 c.rhodecode_user.username, datetime.datetime.now())
394 c.rhodecode_user.username, datetime.datetime.now())
395 payload = {
395 payload = {
396 'type': 'message',
396 'type': 'message',
397 'timestamp': datetime.datetime.utcnow(),
397 'timestamp': datetime.datetime.utcnow(),
398 'user': 'system',
398 'user': 'system',
399 #'channel': 'broadcast',
399 #'channel': 'broadcast',
400 'pm_users': [c.rhodecode_user.username],
400 'pm_users': [c.rhodecode_user.username],
401 'message': {
401 'message': {
402 'message': message,
402 'message': message,
403 'level': 'info',
403 'level': 'info',
404 'topic': '/notifications'
404 'topic': '/notifications'
405 }
405 }
406 }
406 }
407
407
408 registry = get_current_registry()
408 registry = get_current_registry()
409 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
409 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
410 channelstream_config = rhodecode_plugins.get('channelstream', {})
410 channelstream_config = rhodecode_plugins.get('channelstream', {})
411
411
412 try:
412 try:
413 channelstream_request(channelstream_config, [payload], '/message')
413 channelstream_request(channelstream_config, [payload], '/message')
414 except ChannelstreamException as e:
414 except ChannelstreamException as e:
415 log.exception('Failed to send channelstream data')
415 log.exception('Failed to send channelstream data')
416 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
416 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
417 return {"response": 'Channelstream data sent. '
417 return {"response": 'Channelstream data sent. '
418 'You should see a new live message now.'}
418 'You should see a new live message now.'}
@@ -1,677 +1,630 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Users crud controller for pylons
22 Users crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 from formencode import htmlfill
28 from formencode import htmlfill
29 from pylons import request, tmpl_context as c, url, config
29 from pylons import request, tmpl_context as c, url, config
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.lib.exceptions import (
34 from rhodecode.lib.exceptions import (
35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 UserOwnsUserGroupsException, UserCreationError)
36 UserOwnsUserGroupsException, UserCreationError)
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import auth
38 from rhodecode.lib import auth
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.auth_token import AuthTokenModel
43
43
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
46 from rhodecode.model.forms import (
46 from rhodecode.model.forms import (
47 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
47 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.permission import PermissionModel
52 from rhodecode.lib.utils import action_logger
52 from rhodecode.lib.utils import action_logger
53 from rhodecode.lib.ext_json import json
54 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
53 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
55
54
56 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
57
56
58
57
59 class UsersController(BaseController):
58 class UsersController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
61
60
62 @LoginRequired()
61 @LoginRequired()
63 def __before__(self):
62 def __before__(self):
64 super(UsersController, self).__before__()
63 super(UsersController, self).__before__()
65 c.available_permissions = config['available_permissions']
64 c.available_permissions = config['available_permissions']
66 c.allowed_languages = [
65 c.allowed_languages = [
67 ('en', 'English (en)'),
66 ('en', 'English (en)'),
68 ('de', 'German (de)'),
67 ('de', 'German (de)'),
69 ('fr', 'French (fr)'),
68 ('fr', 'French (fr)'),
70 ('it', 'Italian (it)'),
69 ('it', 'Italian (it)'),
71 ('ja', 'Japanese (ja)'),
70 ('ja', 'Japanese (ja)'),
72 ('pl', 'Polish (pl)'),
71 ('pl', 'Polish (pl)'),
73 ('pt', 'Portuguese (pt)'),
72 ('pt', 'Portuguese (pt)'),
74 ('ru', 'Russian (ru)'),
73 ('ru', 'Russian (ru)'),
75 ('zh', 'Chinese (zh)'),
74 ('zh', 'Chinese (zh)'),
76 ]
75 ]
77 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
78
77
79 @HasPermissionAllDecorator('hg.admin')
80 def index(self):
81 """GET /users: All items in the collection"""
82 # url('users')
83
84 from rhodecode.lib.utils import PartialRenderer
85 _render = PartialRenderer('data_table/_dt_elements.mako')
86
87 def username(user_id, username):
88 return _render("user_name", user_id, username)
89
90 def user_actions(user_id, username):
91 return _render("user_actions", user_id, username)
92
93 # json generate
94 c.users_list = User.query()\
95 .filter(User.username != User.DEFAULT_USER) \
96 .all()
97
98 users_data = []
99 for user in c.users_list:
100 users_data.append({
101 "username": h.gravatar_with_user(user.username),
102 "username_raw": user.username,
103 "email": user.email,
104 "first_name": h.escape(user.name),
105 "last_name": h.escape(user.lastname),
106 "last_login": h.format_date(user.last_login),
107 "last_login_raw": datetime_to_time(user.last_login),
108 "last_activity": h.format_date(
109 h.time_to_datetime(user.user_data.get('last_activity', 0))),
110 "last_activity_raw": user.user_data.get('last_activity', 0),
111 "active": h.bool2icon(user.active),
112 "active_raw": user.active,
113 "admin": h.bool2icon(user.admin),
114 "admin_raw": user.admin,
115 "extern_type": user.extern_type,
116 "extern_name": user.extern_name,
117 "action": user_actions(user.user_id, user.username),
118 })
119
120
121 c.data = json.dumps(users_data)
122 return render('admin/users/users.mako')
123
124 def _get_personal_repo_group_template_vars(self):
78 def _get_personal_repo_group_template_vars(self):
125 DummyUser = AttributeDict({
79 DummyUser = AttributeDict({
126 'username': '${username}',
80 'username': '${username}',
127 'user_id': '${user_id}',
81 'user_id': '${user_id}',
128 })
82 })
129 c.default_create_repo_group = RepoGroupModel() \
83 c.default_create_repo_group = RepoGroupModel() \
130 .get_default_create_personal_repo_group()
84 .get_default_create_personal_repo_group()
131 c.personal_repo_group_name = RepoGroupModel() \
85 c.personal_repo_group_name = RepoGroupModel() \
132 .get_personal_group_name(DummyUser)
86 .get_personal_group_name(DummyUser)
133
87
134 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
135 @auth.CSRFRequired()
89 @auth.CSRFRequired()
136 def create(self):
90 def create(self):
137 """POST /users: Create a new item"""
91 """POST /users: Create a new item"""
138 # url('users')
139 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
92 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
140 user_model = UserModel()
93 user_model = UserModel()
141 user_form = UserForm()()
94 user_form = UserForm()()
142 try:
95 try:
143 form_result = user_form.to_python(dict(request.POST))
96 form_result = user_form.to_python(dict(request.POST))
144 user = user_model.create(form_result)
97 user = user_model.create(form_result)
145 Session().flush()
98 Session().flush()
146 username = form_result['username']
99 username = form_result['username']
147 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
100 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
148 None, self.ip_addr, self.sa)
101 None, self.ip_addr, self.sa)
149
102
150 user_link = h.link_to(h.escape(username),
103 user_link = h.link_to(h.escape(username),
151 url('edit_user',
104 url('edit_user',
152 user_id=user.user_id))
105 user_id=user.user_id))
153 h.flash(h.literal(_('Created user %(user_link)s')
106 h.flash(h.literal(_('Created user %(user_link)s')
154 % {'user_link': user_link}), category='success')
107 % {'user_link': user_link}), category='success')
155 Session().commit()
108 Session().commit()
156 except formencode.Invalid as errors:
109 except formencode.Invalid as errors:
157 self._get_personal_repo_group_template_vars()
110 self._get_personal_repo_group_template_vars()
158 return htmlfill.render(
111 return htmlfill.render(
159 render('admin/users/user_add.mako'),
112 render('admin/users/user_add.mako'),
160 defaults=errors.value,
113 defaults=errors.value,
161 errors=errors.error_dict or {},
114 errors=errors.error_dict or {},
162 prefix_error=False,
115 prefix_error=False,
163 encoding="UTF-8",
116 encoding="UTF-8",
164 force_defaults=False)
117 force_defaults=False)
165 except UserCreationError as e:
118 except UserCreationError as e:
166 h.flash(e, 'error')
119 h.flash(e, 'error')
167 except Exception:
120 except Exception:
168 log.exception("Exception creation of user")
121 log.exception("Exception creation of user")
169 h.flash(_('Error occurred during creation of user %s')
122 h.flash(_('Error occurred during creation of user %s')
170 % request.POST.get('username'), category='error')
123 % request.POST.get('username'), category='error')
171 return redirect(url('users'))
124 return redirect(h.route_path('users'))
172
125
173 @HasPermissionAllDecorator('hg.admin')
126 @HasPermissionAllDecorator('hg.admin')
174 def new(self):
127 def new(self):
175 """GET /users/new: Form to create a new item"""
128 """GET /users/new: Form to create a new item"""
176 # url('new_user')
129 # url('new_user')
177 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
178 self._get_personal_repo_group_template_vars()
131 self._get_personal_repo_group_template_vars()
179 return render('admin/users/user_add.mako')
132 return render('admin/users/user_add.mako')
180
133
181 @HasPermissionAllDecorator('hg.admin')
134 @HasPermissionAllDecorator('hg.admin')
182 @auth.CSRFRequired()
135 @auth.CSRFRequired()
183 def update(self, user_id):
136 def update(self, user_id):
184 """PUT /users/user_id: Update an existing item"""
137 """PUT /users/user_id: Update an existing item"""
185 # Forms posted to this method should contain a hidden field:
138 # Forms posted to this method should contain a hidden field:
186 # <input type="hidden" name="_method" value="PUT" />
139 # <input type="hidden" name="_method" value="PUT" />
187 # Or using helpers:
140 # Or using helpers:
188 # h.form(url('update_user', user_id=ID),
141 # h.form(url('update_user', user_id=ID),
189 # method='put')
142 # method='put')
190 # url('user', user_id=ID)
143 # url('user', user_id=ID)
191 user_id = safe_int(user_id)
144 user_id = safe_int(user_id)
192 c.user = User.get_or_404(user_id)
145 c.user = User.get_or_404(user_id)
193 c.active = 'profile'
146 c.active = 'profile'
194 c.extern_type = c.user.extern_type
147 c.extern_type = c.user.extern_type
195 c.extern_name = c.user.extern_name
148 c.extern_name = c.user.extern_name
196 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
149 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
197 available_languages = [x[0] for x in c.allowed_languages]
150 available_languages = [x[0] for x in c.allowed_languages]
198 _form = UserForm(edit=True, available_languages=available_languages,
151 _form = UserForm(edit=True, available_languages=available_languages,
199 old_data={'user_id': user_id,
152 old_data={'user_id': user_id,
200 'email': c.user.email})()
153 'email': c.user.email})()
201 form_result = {}
154 form_result = {}
202 try:
155 try:
203 form_result = _form.to_python(dict(request.POST))
156 form_result = _form.to_python(dict(request.POST))
204 skip_attrs = ['extern_type', 'extern_name']
157 skip_attrs = ['extern_type', 'extern_name']
205 # TODO: plugin should define if username can be updated
158 # TODO: plugin should define if username can be updated
206 if c.extern_type != "rhodecode":
159 if c.extern_type != "rhodecode":
207 # forbid updating username for external accounts
160 # forbid updating username for external accounts
208 skip_attrs.append('username')
161 skip_attrs.append('username')
209
162
210 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
163 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
211 usr = form_result['username']
164 usr = form_result['username']
212 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
165 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
213 None, self.ip_addr, self.sa)
166 None, self.ip_addr, self.sa)
214 h.flash(_('User updated successfully'), category='success')
167 h.flash(_('User updated successfully'), category='success')
215 Session().commit()
168 Session().commit()
216 except formencode.Invalid as errors:
169 except formencode.Invalid as errors:
217 defaults = errors.value
170 defaults = errors.value
218 e = errors.error_dict or {}
171 e = errors.error_dict or {}
219
172
220 return htmlfill.render(
173 return htmlfill.render(
221 render('admin/users/user_edit.mako'),
174 render('admin/users/user_edit.mako'),
222 defaults=defaults,
175 defaults=defaults,
223 errors=e,
176 errors=e,
224 prefix_error=False,
177 prefix_error=False,
225 encoding="UTF-8",
178 encoding="UTF-8",
226 force_defaults=False)
179 force_defaults=False)
227 except UserCreationError as e:
180 except UserCreationError as e:
228 h.flash(e, 'error')
181 h.flash(e, 'error')
229 except Exception:
182 except Exception:
230 log.exception("Exception updating user")
183 log.exception("Exception updating user")
231 h.flash(_('Error occurred during update of user %s')
184 h.flash(_('Error occurred during update of user %s')
232 % form_result.get('username'), category='error')
185 % form_result.get('username'), category='error')
233 return redirect(url('edit_user', user_id=user_id))
186 return redirect(url('edit_user', user_id=user_id))
234
187
235 @HasPermissionAllDecorator('hg.admin')
188 @HasPermissionAllDecorator('hg.admin')
236 @auth.CSRFRequired()
189 @auth.CSRFRequired()
237 def delete(self, user_id):
190 def delete(self, user_id):
238 """DELETE /users/user_id: Delete an existing item"""
191 """DELETE /users/user_id: Delete an existing item"""
239 # Forms posted to this method should contain a hidden field:
192 # Forms posted to this method should contain a hidden field:
240 # <input type="hidden" name="_method" value="DELETE" />
193 # <input type="hidden" name="_method" value="DELETE" />
241 # Or using helpers:
194 # Or using helpers:
242 # h.form(url('delete_user', user_id=ID),
195 # h.form(url('delete_user', user_id=ID),
243 # method='delete')
196 # method='delete')
244 # url('user', user_id=ID)
197 # url('user', user_id=ID)
245 user_id = safe_int(user_id)
198 user_id = safe_int(user_id)
246 c.user = User.get_or_404(user_id)
199 c.user = User.get_or_404(user_id)
247
200
248 _repos = c.user.repositories
201 _repos = c.user.repositories
249 _repo_groups = c.user.repository_groups
202 _repo_groups = c.user.repository_groups
250 _user_groups = c.user.user_groups
203 _user_groups = c.user.user_groups
251
204
252 handle_repos = None
205 handle_repos = None
253 handle_repo_groups = None
206 handle_repo_groups = None
254 handle_user_groups = None
207 handle_user_groups = None
255 # dummy call for flash of handle
208 # dummy call for flash of handle
256 set_handle_flash_repos = lambda: None
209 set_handle_flash_repos = lambda: None
257 set_handle_flash_repo_groups = lambda: None
210 set_handle_flash_repo_groups = lambda: None
258 set_handle_flash_user_groups = lambda: None
211 set_handle_flash_user_groups = lambda: None
259
212
260 if _repos and request.POST.get('user_repos'):
213 if _repos and request.POST.get('user_repos'):
261 do = request.POST['user_repos']
214 do = request.POST['user_repos']
262 if do == 'detach':
215 if do == 'detach':
263 handle_repos = 'detach'
216 handle_repos = 'detach'
264 set_handle_flash_repos = lambda: h.flash(
217 set_handle_flash_repos = lambda: h.flash(
265 _('Detached %s repositories') % len(_repos),
218 _('Detached %s repositories') % len(_repos),
266 category='success')
219 category='success')
267 elif do == 'delete':
220 elif do == 'delete':
268 handle_repos = 'delete'
221 handle_repos = 'delete'
269 set_handle_flash_repos = lambda: h.flash(
222 set_handle_flash_repos = lambda: h.flash(
270 _('Deleted %s repositories') % len(_repos),
223 _('Deleted %s repositories') % len(_repos),
271 category='success')
224 category='success')
272
225
273 if _repo_groups and request.POST.get('user_repo_groups'):
226 if _repo_groups and request.POST.get('user_repo_groups'):
274 do = request.POST['user_repo_groups']
227 do = request.POST['user_repo_groups']
275 if do == 'detach':
228 if do == 'detach':
276 handle_repo_groups = 'detach'
229 handle_repo_groups = 'detach'
277 set_handle_flash_repo_groups = lambda: h.flash(
230 set_handle_flash_repo_groups = lambda: h.flash(
278 _('Detached %s repository groups') % len(_repo_groups),
231 _('Detached %s repository groups') % len(_repo_groups),
279 category='success')
232 category='success')
280 elif do == 'delete':
233 elif do == 'delete':
281 handle_repo_groups = 'delete'
234 handle_repo_groups = 'delete'
282 set_handle_flash_repo_groups = lambda: h.flash(
235 set_handle_flash_repo_groups = lambda: h.flash(
283 _('Deleted %s repository groups') % len(_repo_groups),
236 _('Deleted %s repository groups') % len(_repo_groups),
284 category='success')
237 category='success')
285
238
286 if _user_groups and request.POST.get('user_user_groups'):
239 if _user_groups and request.POST.get('user_user_groups'):
287 do = request.POST['user_user_groups']
240 do = request.POST['user_user_groups']
288 if do == 'detach':
241 if do == 'detach':
289 handle_user_groups = 'detach'
242 handle_user_groups = 'detach'
290 set_handle_flash_user_groups = lambda: h.flash(
243 set_handle_flash_user_groups = lambda: h.flash(
291 _('Detached %s user groups') % len(_user_groups),
244 _('Detached %s user groups') % len(_user_groups),
292 category='success')
245 category='success')
293 elif do == 'delete':
246 elif do == 'delete':
294 handle_user_groups = 'delete'
247 handle_user_groups = 'delete'
295 set_handle_flash_user_groups = lambda: h.flash(
248 set_handle_flash_user_groups = lambda: h.flash(
296 _('Deleted %s user groups') % len(_user_groups),
249 _('Deleted %s user groups') % len(_user_groups),
297 category='success')
250 category='success')
298
251
299 try:
252 try:
300 UserModel().delete(c.user, handle_repos=handle_repos,
253 UserModel().delete(c.user, handle_repos=handle_repos,
301 handle_repo_groups=handle_repo_groups,
254 handle_repo_groups=handle_repo_groups,
302 handle_user_groups=handle_user_groups)
255 handle_user_groups=handle_user_groups)
303 Session().commit()
256 Session().commit()
304 set_handle_flash_repos()
257 set_handle_flash_repos()
305 set_handle_flash_repo_groups()
258 set_handle_flash_repo_groups()
306 set_handle_flash_user_groups()
259 set_handle_flash_user_groups()
307 h.flash(_('Successfully deleted user'), category='success')
260 h.flash(_('Successfully deleted user'), category='success')
308 except (UserOwnsReposException, UserOwnsRepoGroupsException,
261 except (UserOwnsReposException, UserOwnsRepoGroupsException,
309 UserOwnsUserGroupsException, DefaultUserException) as e:
262 UserOwnsUserGroupsException, DefaultUserException) as e:
310 h.flash(e, category='warning')
263 h.flash(e, category='warning')
311 except Exception:
264 except Exception:
312 log.exception("Exception during deletion of user")
265 log.exception("Exception during deletion of user")
313 h.flash(_('An error occurred during deletion of user'),
266 h.flash(_('An error occurred during deletion of user'),
314 category='error')
267 category='error')
315 return redirect(url('users'))
268 return redirect(h.route_path('users'))
316
269
317 @HasPermissionAllDecorator('hg.admin')
270 @HasPermissionAllDecorator('hg.admin')
318 @auth.CSRFRequired()
271 @auth.CSRFRequired()
319 def reset_password(self, user_id):
272 def reset_password(self, user_id):
320 """
273 """
321 toggle reset password flag for this user
274 toggle reset password flag for this user
322
275
323 :param user_id:
276 :param user_id:
324 """
277 """
325 user_id = safe_int(user_id)
278 user_id = safe_int(user_id)
326 c.user = User.get_or_404(user_id)
279 c.user = User.get_or_404(user_id)
327 try:
280 try:
328 old_value = c.user.user_data.get('force_password_change')
281 old_value = c.user.user_data.get('force_password_change')
329 c.user.update_userdata(force_password_change=not old_value)
282 c.user.update_userdata(force_password_change=not old_value)
330 Session().commit()
283 Session().commit()
331 if old_value:
284 if old_value:
332 msg = _('Force password change disabled for user')
285 msg = _('Force password change disabled for user')
333 else:
286 else:
334 msg = _('Force password change enabled for user')
287 msg = _('Force password change enabled for user')
335 h.flash(msg, category='success')
288 h.flash(msg, category='success')
336 except Exception:
289 except Exception:
337 log.exception("Exception during password reset for user")
290 log.exception("Exception during password reset for user")
338 h.flash(_('An error occurred during password reset for user'),
291 h.flash(_('An error occurred during password reset for user'),
339 category='error')
292 category='error')
340
293
341 return redirect(url('edit_user_advanced', user_id=user_id))
294 return redirect(url('edit_user_advanced', user_id=user_id))
342
295
343 @HasPermissionAllDecorator('hg.admin')
296 @HasPermissionAllDecorator('hg.admin')
344 @auth.CSRFRequired()
297 @auth.CSRFRequired()
345 def create_personal_repo_group(self, user_id):
298 def create_personal_repo_group(self, user_id):
346 """
299 """
347 Create personal repository group for this user
300 Create personal repository group for this user
348
301
349 :param user_id:
302 :param user_id:
350 """
303 """
351 from rhodecode.model.repo_group import RepoGroupModel
304 from rhodecode.model.repo_group import RepoGroupModel
352
305
353 user_id = safe_int(user_id)
306 user_id = safe_int(user_id)
354 c.user = User.get_or_404(user_id)
307 c.user = User.get_or_404(user_id)
355 personal_repo_group = RepoGroup.get_user_personal_repo_group(
308 personal_repo_group = RepoGroup.get_user_personal_repo_group(
356 c.user.user_id)
309 c.user.user_id)
357 if personal_repo_group:
310 if personal_repo_group:
358 return redirect(url('edit_user_advanced', user_id=user_id))
311 return redirect(url('edit_user_advanced', user_id=user_id))
359
312
360 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
313 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
361 c.user)
314 c.user)
362 named_personal_group = RepoGroup.get_by_group_name(
315 named_personal_group = RepoGroup.get_by_group_name(
363 personal_repo_group_name)
316 personal_repo_group_name)
364 try:
317 try:
365
318
366 if named_personal_group and named_personal_group.user_id == c.user.user_id:
319 if named_personal_group and named_personal_group.user_id == c.user.user_id:
367 # migrate the same named group, and mark it as personal
320 # migrate the same named group, and mark it as personal
368 named_personal_group.personal = True
321 named_personal_group.personal = True
369 Session().add(named_personal_group)
322 Session().add(named_personal_group)
370 Session().commit()
323 Session().commit()
371 msg = _('Linked repository group `%s` as personal' % (
324 msg = _('Linked repository group `%s` as personal' % (
372 personal_repo_group_name,))
325 personal_repo_group_name,))
373 h.flash(msg, category='success')
326 h.flash(msg, category='success')
374 elif not named_personal_group:
327 elif not named_personal_group:
375 RepoGroupModel().create_personal_repo_group(c.user)
328 RepoGroupModel().create_personal_repo_group(c.user)
376
329
377 msg = _('Created repository group `%s`' % (
330 msg = _('Created repository group `%s`' % (
378 personal_repo_group_name,))
331 personal_repo_group_name,))
379 h.flash(msg, category='success')
332 h.flash(msg, category='success')
380 else:
333 else:
381 msg = _('Repository group `%s` is already taken' % (
334 msg = _('Repository group `%s` is already taken' % (
382 personal_repo_group_name,))
335 personal_repo_group_name,))
383 h.flash(msg, category='warning')
336 h.flash(msg, category='warning')
384 except Exception:
337 except Exception:
385 log.exception("Exception during repository group creation")
338 log.exception("Exception during repository group creation")
386 msg = _(
339 msg = _(
387 'An error occurred during repository group creation for user')
340 'An error occurred during repository group creation for user')
388 h.flash(msg, category='error')
341 h.flash(msg, category='error')
389 Session().rollback()
342 Session().rollback()
390
343
391 return redirect(url('edit_user_advanced', user_id=user_id))
344 return redirect(url('edit_user_advanced', user_id=user_id))
392
345
393 @HasPermissionAllDecorator('hg.admin')
346 @HasPermissionAllDecorator('hg.admin')
394 def show(self, user_id):
347 def show(self, user_id):
395 """GET /users/user_id: Show a specific item"""
348 """GET /users/user_id: Show a specific item"""
396 # url('user', user_id=ID)
349 # url('user', user_id=ID)
397 User.get_or_404(-1)
350 User.get_or_404(-1)
398
351
399 @HasPermissionAllDecorator('hg.admin')
352 @HasPermissionAllDecorator('hg.admin')
400 def edit(self, user_id):
353 def edit(self, user_id):
401 """GET /users/user_id/edit: Form to edit an existing item"""
354 """GET /users/user_id/edit: Form to edit an existing item"""
402 # url('edit_user', user_id=ID)
355 # url('edit_user', user_id=ID)
403 user_id = safe_int(user_id)
356 user_id = safe_int(user_id)
404 c.user = User.get_or_404(user_id)
357 c.user = User.get_or_404(user_id)
405 if c.user.username == User.DEFAULT_USER:
358 if c.user.username == User.DEFAULT_USER:
406 h.flash(_("You can't edit this user"), category='warning')
359 h.flash(_("You can't edit this user"), category='warning')
407 return redirect(url('users'))
360 return redirect(h.route_path('users'))
408
361
409 c.active = 'profile'
362 c.active = 'profile'
410 c.extern_type = c.user.extern_type
363 c.extern_type = c.user.extern_type
411 c.extern_name = c.user.extern_name
364 c.extern_name = c.user.extern_name
412 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
365 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
413
366
414 defaults = c.user.get_dict()
367 defaults = c.user.get_dict()
415 defaults.update({'language': c.user.user_data.get('language')})
368 defaults.update({'language': c.user.user_data.get('language')})
416 return htmlfill.render(
369 return htmlfill.render(
417 render('admin/users/user_edit.mako'),
370 render('admin/users/user_edit.mako'),
418 defaults=defaults,
371 defaults=defaults,
419 encoding="UTF-8",
372 encoding="UTF-8",
420 force_defaults=False)
373 force_defaults=False)
421
374
422 @HasPermissionAllDecorator('hg.admin')
375 @HasPermissionAllDecorator('hg.admin')
423 def edit_advanced(self, user_id):
376 def edit_advanced(self, user_id):
424 user_id = safe_int(user_id)
377 user_id = safe_int(user_id)
425 user = c.user = User.get_or_404(user_id)
378 user = c.user = User.get_or_404(user_id)
426 if user.username == User.DEFAULT_USER:
379 if user.username == User.DEFAULT_USER:
427 h.flash(_("You can't edit this user"), category='warning')
380 h.flash(_("You can't edit this user"), category='warning')
428 return redirect(url('users'))
381 return redirect(h.route_path('users'))
429
382
430 c.active = 'advanced'
383 c.active = 'advanced'
431 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
384 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
432 c.personal_repo_group = c.perm_user.personal_repo_group
385 c.personal_repo_group = c.perm_user.personal_repo_group
433 c.personal_repo_group_name = RepoGroupModel()\
386 c.personal_repo_group_name = RepoGroupModel()\
434 .get_personal_group_name(user)
387 .get_personal_group_name(user)
435 c.first_admin = User.get_first_super_admin()
388 c.first_admin = User.get_first_super_admin()
436 defaults = user.get_dict()
389 defaults = user.get_dict()
437
390
438 # Interim workaround if the user participated on any pull requests as a
391 # Interim workaround if the user participated on any pull requests as a
439 # reviewer.
392 # reviewer.
440 has_review = bool(PullRequestReviewers.query().filter(
393 has_review = bool(PullRequestReviewers.query().filter(
441 PullRequestReviewers.user_id == user_id).first())
394 PullRequestReviewers.user_id == user_id).first())
442 c.can_delete_user = not has_review
395 c.can_delete_user = not has_review
443 c.can_delete_user_message = _(
396 c.can_delete_user_message = _(
444 'The user participates as reviewer in pull requests and '
397 'The user participates as reviewer in pull requests and '
445 'cannot be deleted. You can set the user to '
398 'cannot be deleted. You can set the user to '
446 '"inactive" instead of deleting it.') if has_review else ''
399 '"inactive" instead of deleting it.') if has_review else ''
447
400
448 return htmlfill.render(
401 return htmlfill.render(
449 render('admin/users/user_edit.mako'),
402 render('admin/users/user_edit.mako'),
450 defaults=defaults,
403 defaults=defaults,
451 encoding="UTF-8",
404 encoding="UTF-8",
452 force_defaults=False)
405 force_defaults=False)
453
406
454 @HasPermissionAllDecorator('hg.admin')
407 @HasPermissionAllDecorator('hg.admin')
455 def edit_global_perms(self, user_id):
408 def edit_global_perms(self, user_id):
456 user_id = safe_int(user_id)
409 user_id = safe_int(user_id)
457 c.user = User.get_or_404(user_id)
410 c.user = User.get_or_404(user_id)
458 if c.user.username == User.DEFAULT_USER:
411 if c.user.username == User.DEFAULT_USER:
459 h.flash(_("You can't edit this user"), category='warning')
412 h.flash(_("You can't edit this user"), category='warning')
460 return redirect(url('users'))
413 return redirect(h.route_path('users'))
461
414
462 c.active = 'global_perms'
415 c.active = 'global_perms'
463
416
464 c.default_user = User.get_default_user()
417 c.default_user = User.get_default_user()
465 defaults = c.user.get_dict()
418 defaults = c.user.get_dict()
466 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
419 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
467 defaults.update(c.default_user.get_default_perms())
420 defaults.update(c.default_user.get_default_perms())
468 defaults.update(c.user.get_default_perms())
421 defaults.update(c.user.get_default_perms())
469
422
470 return htmlfill.render(
423 return htmlfill.render(
471 render('admin/users/user_edit.mako'),
424 render('admin/users/user_edit.mako'),
472 defaults=defaults,
425 defaults=defaults,
473 encoding="UTF-8",
426 encoding="UTF-8",
474 force_defaults=False)
427 force_defaults=False)
475
428
476 @HasPermissionAllDecorator('hg.admin')
429 @HasPermissionAllDecorator('hg.admin')
477 @auth.CSRFRequired()
430 @auth.CSRFRequired()
478 def update_global_perms(self, user_id):
431 def update_global_perms(self, user_id):
479 """PUT /users_perm/user_id: Update an existing item"""
432 """PUT /users_perm/user_id: Update an existing item"""
480 # url('user_perm', user_id=ID, method='put')
433 # url('user_perm', user_id=ID, method='put')
481 user_id = safe_int(user_id)
434 user_id = safe_int(user_id)
482 user = User.get_or_404(user_id)
435 user = User.get_or_404(user_id)
483 c.active = 'global_perms'
436 c.active = 'global_perms'
484 try:
437 try:
485 # first stage that verifies the checkbox
438 # first stage that verifies the checkbox
486 _form = UserIndividualPermissionsForm()
439 _form = UserIndividualPermissionsForm()
487 form_result = _form.to_python(dict(request.POST))
440 form_result = _form.to_python(dict(request.POST))
488 inherit_perms = form_result['inherit_default_permissions']
441 inherit_perms = form_result['inherit_default_permissions']
489 user.inherit_default_permissions = inherit_perms
442 user.inherit_default_permissions = inherit_perms
490 Session().add(user)
443 Session().add(user)
491
444
492 if not inherit_perms:
445 if not inherit_perms:
493 # only update the individual ones if we un check the flag
446 # only update the individual ones if we un check the flag
494 _form = UserPermissionsForm(
447 _form = UserPermissionsForm(
495 [x[0] for x in c.repo_create_choices],
448 [x[0] for x in c.repo_create_choices],
496 [x[0] for x in c.repo_create_on_write_choices],
449 [x[0] for x in c.repo_create_on_write_choices],
497 [x[0] for x in c.repo_group_create_choices],
450 [x[0] for x in c.repo_group_create_choices],
498 [x[0] for x in c.user_group_create_choices],
451 [x[0] for x in c.user_group_create_choices],
499 [x[0] for x in c.fork_choices],
452 [x[0] for x in c.fork_choices],
500 [x[0] for x in c.inherit_default_permission_choices])()
453 [x[0] for x in c.inherit_default_permission_choices])()
501
454
502 form_result = _form.to_python(dict(request.POST))
455 form_result = _form.to_python(dict(request.POST))
503 form_result.update({'perm_user_id': user.user_id})
456 form_result.update({'perm_user_id': user.user_id})
504
457
505 PermissionModel().update_user_permissions(form_result)
458 PermissionModel().update_user_permissions(form_result)
506
459
507 Session().commit()
460 Session().commit()
508 h.flash(_('User global permissions updated successfully'),
461 h.flash(_('User global permissions updated successfully'),
509 category='success')
462 category='success')
510
463
511 Session().commit()
464 Session().commit()
512 except formencode.Invalid as errors:
465 except formencode.Invalid as errors:
513 defaults = errors.value
466 defaults = errors.value
514 c.user = user
467 c.user = user
515 return htmlfill.render(
468 return htmlfill.render(
516 render('admin/users/user_edit.mako'),
469 render('admin/users/user_edit.mako'),
517 defaults=defaults,
470 defaults=defaults,
518 errors=errors.error_dict or {},
471 errors=errors.error_dict or {},
519 prefix_error=False,
472 prefix_error=False,
520 encoding="UTF-8",
473 encoding="UTF-8",
521 force_defaults=False)
474 force_defaults=False)
522 except Exception:
475 except Exception:
523 log.exception("Exception during permissions saving")
476 log.exception("Exception during permissions saving")
524 h.flash(_('An error occurred during permissions saving'),
477 h.flash(_('An error occurred during permissions saving'),
525 category='error')
478 category='error')
526 return redirect(url('edit_user_global_perms', user_id=user_id))
479 return redirect(url('edit_user_global_perms', user_id=user_id))
527
480
528 @HasPermissionAllDecorator('hg.admin')
481 @HasPermissionAllDecorator('hg.admin')
529 def edit_perms_summary(self, user_id):
482 def edit_perms_summary(self, user_id):
530 user_id = safe_int(user_id)
483 user_id = safe_int(user_id)
531 c.user = User.get_or_404(user_id)
484 c.user = User.get_or_404(user_id)
532 if c.user.username == User.DEFAULT_USER:
485 if c.user.username == User.DEFAULT_USER:
533 h.flash(_("You can't edit this user"), category='warning')
486 h.flash(_("You can't edit this user"), category='warning')
534 return redirect(url('users'))
487 return redirect(h.route_path('users'))
535
488
536 c.active = 'perms_summary'
489 c.active = 'perms_summary'
537 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
490 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
538
491
539 return render('admin/users/user_edit.mako')
492 return render('admin/users/user_edit.mako')
540
493
541 @HasPermissionAllDecorator('hg.admin')
494 @HasPermissionAllDecorator('hg.admin')
542 def edit_emails(self, user_id):
495 def edit_emails(self, user_id):
543 user_id = safe_int(user_id)
496 user_id = safe_int(user_id)
544 c.user = User.get_or_404(user_id)
497 c.user = User.get_or_404(user_id)
545 if c.user.username == User.DEFAULT_USER:
498 if c.user.username == User.DEFAULT_USER:
546 h.flash(_("You can't edit this user"), category='warning')
499 h.flash(_("You can't edit this user"), category='warning')
547 return redirect(url('users'))
500 return redirect(h.route_path('users'))
548
501
549 c.active = 'emails'
502 c.active = 'emails'
550 c.user_email_map = UserEmailMap.query() \
503 c.user_email_map = UserEmailMap.query() \
551 .filter(UserEmailMap.user == c.user).all()
504 .filter(UserEmailMap.user == c.user).all()
552
505
553 defaults = c.user.get_dict()
506 defaults = c.user.get_dict()
554 return htmlfill.render(
507 return htmlfill.render(
555 render('admin/users/user_edit.mako'),
508 render('admin/users/user_edit.mako'),
556 defaults=defaults,
509 defaults=defaults,
557 encoding="UTF-8",
510 encoding="UTF-8",
558 force_defaults=False)
511 force_defaults=False)
559
512
560 @HasPermissionAllDecorator('hg.admin')
513 @HasPermissionAllDecorator('hg.admin')
561 @auth.CSRFRequired()
514 @auth.CSRFRequired()
562 def add_email(self, user_id):
515 def add_email(self, user_id):
563 """POST /user_emails:Add an existing item"""
516 """POST /user_emails:Add an existing item"""
564 # url('user_emails', user_id=ID, method='put')
517 # url('user_emails', user_id=ID, method='put')
565 user_id = safe_int(user_id)
518 user_id = safe_int(user_id)
566 c.user = User.get_or_404(user_id)
519 c.user = User.get_or_404(user_id)
567
520
568 email = request.POST.get('new_email')
521 email = request.POST.get('new_email')
569 user_model = UserModel()
522 user_model = UserModel()
570
523
571 try:
524 try:
572 user_model.add_extra_email(user_id, email)
525 user_model.add_extra_email(user_id, email)
573 Session().commit()
526 Session().commit()
574 h.flash(_("Added new email address `%s` for user account") % email,
527 h.flash(_("Added new email address `%s` for user account") % email,
575 category='success')
528 category='success')
576 except formencode.Invalid as error:
529 except formencode.Invalid as error:
577 msg = error.error_dict['email']
530 msg = error.error_dict['email']
578 h.flash(msg, category='error')
531 h.flash(msg, category='error')
579 except Exception:
532 except Exception:
580 log.exception("Exception during email saving")
533 log.exception("Exception during email saving")
581 h.flash(_('An error occurred during email saving'),
534 h.flash(_('An error occurred during email saving'),
582 category='error')
535 category='error')
583 return redirect(url('edit_user_emails', user_id=user_id))
536 return redirect(url('edit_user_emails', user_id=user_id))
584
537
585 @HasPermissionAllDecorator('hg.admin')
538 @HasPermissionAllDecorator('hg.admin')
586 @auth.CSRFRequired()
539 @auth.CSRFRequired()
587 def delete_email(self, user_id):
540 def delete_email(self, user_id):
588 """DELETE /user_emails_delete/user_id: Delete an existing item"""
541 """DELETE /user_emails_delete/user_id: Delete an existing item"""
589 # url('user_emails_delete', user_id=ID, method='delete')
542 # url('user_emails_delete', user_id=ID, method='delete')
590 user_id = safe_int(user_id)
543 user_id = safe_int(user_id)
591 c.user = User.get_or_404(user_id)
544 c.user = User.get_or_404(user_id)
592 email_id = request.POST.get('del_email_id')
545 email_id = request.POST.get('del_email_id')
593 user_model = UserModel()
546 user_model = UserModel()
594 user_model.delete_extra_email(user_id, email_id)
547 user_model.delete_extra_email(user_id, email_id)
595 Session().commit()
548 Session().commit()
596 h.flash(_("Removed email address from user account"), category='success')
549 h.flash(_("Removed email address from user account"), category='success')
597 return redirect(url('edit_user_emails', user_id=user_id))
550 return redirect(url('edit_user_emails', user_id=user_id))
598
551
599 @HasPermissionAllDecorator('hg.admin')
552 @HasPermissionAllDecorator('hg.admin')
600 def edit_ips(self, user_id):
553 def edit_ips(self, user_id):
601 user_id = safe_int(user_id)
554 user_id = safe_int(user_id)
602 c.user = User.get_or_404(user_id)
555 c.user = User.get_or_404(user_id)
603 if c.user.username == User.DEFAULT_USER:
556 if c.user.username == User.DEFAULT_USER:
604 h.flash(_("You can't edit this user"), category='warning')
557 h.flash(_("You can't edit this user"), category='warning')
605 return redirect(url('users'))
558 return redirect(h.route_path('users'))
606
559
607 c.active = 'ips'
560 c.active = 'ips'
608 c.user_ip_map = UserIpMap.query() \
561 c.user_ip_map = UserIpMap.query() \
609 .filter(UserIpMap.user == c.user).all()
562 .filter(UserIpMap.user == c.user).all()
610
563
611 c.inherit_default_ips = c.user.inherit_default_permissions
564 c.inherit_default_ips = c.user.inherit_default_permissions
612 c.default_user_ip_map = UserIpMap.query() \
565 c.default_user_ip_map = UserIpMap.query() \
613 .filter(UserIpMap.user == User.get_default_user()).all()
566 .filter(UserIpMap.user == User.get_default_user()).all()
614
567
615 defaults = c.user.get_dict()
568 defaults = c.user.get_dict()
616 return htmlfill.render(
569 return htmlfill.render(
617 render('admin/users/user_edit.mako'),
570 render('admin/users/user_edit.mako'),
618 defaults=defaults,
571 defaults=defaults,
619 encoding="UTF-8",
572 encoding="UTF-8",
620 force_defaults=False)
573 force_defaults=False)
621
574
622 @HasPermissionAllDecorator('hg.admin')
575 @HasPermissionAllDecorator('hg.admin')
623 @auth.CSRFRequired()
576 @auth.CSRFRequired()
624 def add_ip(self, user_id):
577 def add_ip(self, user_id):
625 """POST /user_ips:Add an existing item"""
578 """POST /user_ips:Add an existing item"""
626 # url('user_ips', user_id=ID, method='put')
579 # url('user_ips', user_id=ID, method='put')
627
580
628 user_id = safe_int(user_id)
581 user_id = safe_int(user_id)
629 c.user = User.get_or_404(user_id)
582 c.user = User.get_or_404(user_id)
630 user_model = UserModel()
583 user_model = UserModel()
631 try:
584 try:
632 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
585 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
633 except Exception as e:
586 except Exception as e:
634 ip_list = []
587 ip_list = []
635 log.exception("Exception during ip saving")
588 log.exception("Exception during ip saving")
636 h.flash(_('An error occurred during ip saving:%s' % (e,)),
589 h.flash(_('An error occurred during ip saving:%s' % (e,)),
637 category='error')
590 category='error')
638
591
639 desc = request.POST.get('description')
592 desc = request.POST.get('description')
640 added = []
593 added = []
641 for ip in ip_list:
594 for ip in ip_list:
642 try:
595 try:
643 user_model.add_extra_ip(user_id, ip, desc)
596 user_model.add_extra_ip(user_id, ip, desc)
644 Session().commit()
597 Session().commit()
645 added.append(ip)
598 added.append(ip)
646 except formencode.Invalid as error:
599 except formencode.Invalid as error:
647 msg = error.error_dict['ip']
600 msg = error.error_dict['ip']
648 h.flash(msg, category='error')
601 h.flash(msg, category='error')
649 except Exception:
602 except Exception:
650 log.exception("Exception during ip saving")
603 log.exception("Exception during ip saving")
651 h.flash(_('An error occurred during ip saving'),
604 h.flash(_('An error occurred during ip saving'),
652 category='error')
605 category='error')
653 if added:
606 if added:
654 h.flash(
607 h.flash(
655 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
608 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
656 category='success')
609 category='success')
657 if 'default_user' in request.POST:
610 if 'default_user' in request.POST:
658 return redirect(url('admin_permissions_ips'))
611 return redirect(url('admin_permissions_ips'))
659 return redirect(url('edit_user_ips', user_id=user_id))
612 return redirect(url('edit_user_ips', user_id=user_id))
660
613
661 @HasPermissionAllDecorator('hg.admin')
614 @HasPermissionAllDecorator('hg.admin')
662 @auth.CSRFRequired()
615 @auth.CSRFRequired()
663 def delete_ip(self, user_id):
616 def delete_ip(self, user_id):
664 """DELETE /user_ips_delete/user_id: Delete an existing item"""
617 """DELETE /user_ips_delete/user_id: Delete an existing item"""
665 # url('user_ips_delete', user_id=ID, method='delete')
618 # url('user_ips_delete', user_id=ID, method='delete')
666 user_id = safe_int(user_id)
619 user_id = safe_int(user_id)
667 c.user = User.get_or_404(user_id)
620 c.user = User.get_or_404(user_id)
668
621
669 ip_id = request.POST.get('del_ip_id')
622 ip_id = request.POST.get('del_ip_id')
670 user_model = UserModel()
623 user_model = UserModel()
671 user_model.delete_extra_ip(user_id, ip_id)
624 user_model.delete_extra_ip(user_id, ip_id)
672 Session().commit()
625 Session().commit()
673 h.flash(_("Removed ip address from user whitelist"), category='success')
626 h.flash(_("Removed ip address from user whitelist"), category='success')
674
627
675 if 'default_user' in request.POST:
628 if 'default_user' in request.POST:
676 return redirect(url('admin_permissions_ips'))
629 return redirect(url('admin_permissions_ips'))
677 return redirect(url('edit_user_ips', user_id=user_id))
630 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,147 +1,147 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add user')}
5 ${_('Add user')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10 <%def name="breadcrumbs_links()">
10 <%def name="breadcrumbs_links()">
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('Users'),h.url('users'))}
13 ${h.link_to(_('Users'),h.route_path('users'))}
14 &raquo;
14 &raquo;
15 ${_('Add User')}
15 ${_('Add User')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='admin')}
19 ${self.menu_items(active='admin')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <!-- end box / title -->
28 <!-- end box / title -->
29 ${h.secure_form(url('users'))}
29 ${h.secure_form(url('users'))}
30 <div class="form">
30 <div class="form">
31 <!-- fields -->
31 <!-- fields -->
32 <div class="fields">
32 <div class="fields">
33 <div class="field">
33 <div class="field">
34 <div class="label">
34 <div class="label">
35 <label for="username">${_('Username')}:</label>
35 <label for="username">${_('Username')}:</label>
36 </div>
36 </div>
37 <div class="input">
37 <div class="input">
38 ${h.text('username', class_='medium')}
38 ${h.text('username', class_='medium')}
39 </div>
39 </div>
40 </div>
40 </div>
41
41
42 <div class="field">
42 <div class="field">
43 <div class="label">
43 <div class="label">
44 <label for="password">${_('Password')}:</label>
44 <label for="password">${_('Password')}:</label>
45 </div>
45 </div>
46 <div class="input">
46 <div class="input">
47 ${h.password('password', class_='medium')}
47 ${h.password('password', class_='medium')}
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51 <div class="field">
51 <div class="field">
52 <div class="label">
52 <div class="label">
53 <label for="password_confirmation">${_('Password confirmation')}:</label>
53 <label for="password_confirmation">${_('Password confirmation')}:</label>
54 </div>
54 </div>
55 <div class="input">
55 <div class="input">
56 ${h.password('password_confirmation',autocomplete="off", class_='medium')}
56 ${h.password('password_confirmation',autocomplete="off", class_='medium')}
57 <div class="info-block">
57 <div class="info-block">
58 <a id="generate_password" href="#">
58 <a id="generate_password" href="#">
59 <i class="icon-lock"></i> ${_('Generate password')}
59 <i class="icon-lock"></i> ${_('Generate password')}
60 </a>
60 </a>
61 <span id="generate_password_preview"></span>
61 <span id="generate_password_preview"></span>
62 </div>
62 </div>
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="firstname">${_('First Name')}:</label>
68 <label for="firstname">${_('First Name')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.text('firstname', class_='medium')}
71 ${h.text('firstname', class_='medium')}
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="lastname">${_('Last Name')}:</label>
77 <label for="lastname">${_('Last Name')}:</label>
78 </div>
78 </div>
79 <div class="input">
79 <div class="input">
80 ${h.text('lastname', class_='medium')}
80 ${h.text('lastname', class_='medium')}
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="email">${_('Email')}:</label>
86 <label for="email">${_('Email')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 ${h.text('email', class_='medium')}
89 ${h.text('email', class_='medium')}
90 ${h.hidden('extern_name', c.default_extern_type)}
90 ${h.hidden('extern_name', c.default_extern_type)}
91 ${h.hidden('extern_type', c.default_extern_type)}
91 ${h.hidden('extern_type', c.default_extern_type)}
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="field">
95 <div class="field">
96 <div class="label label-checkbox">
96 <div class="label label-checkbox">
97 <label for="active">${_('Active')}:</label>
97 <label for="active">${_('Active')}:</label>
98 </div>
98 </div>
99 <div class="checkboxes">
99 <div class="checkboxes">
100 ${h.checkbox('active',value=True,checked='checked')}
100 ${h.checkbox('active',value=True,checked='checked')}
101 </div>
101 </div>
102 </div>
102 </div>
103
103
104 <div class="field">
104 <div class="field">
105 <div class="label label-checkbox">
105 <div class="label label-checkbox">
106 <label for="password_change">${_('Password change')}:</label>
106 <label for="password_change">${_('Password change')}:</label>
107 </div>
107 </div>
108 <div class="checkboxes">
108 <div class="checkboxes">
109 ${h.checkbox('password_change',value=True)}
109 ${h.checkbox('password_change',value=True)}
110 <span class="help-block">${_('Force user to change his password on the next login')}</span>
110 <span class="help-block">${_('Force user to change his password on the next login')}</span>
111 </div>
111 </div>
112 </div>
112 </div>
113
113
114 <div class="field">
114 <div class="field">
115 <div class="label label-checkbox">
115 <div class="label label-checkbox">
116 <label for="create_repo_group">${_('Add personal repository group')}:</label>
116 <label for="create_repo_group">${_('Add personal repository group')}:</label>
117 </div>
117 </div>
118 <div class="checkboxes">
118 <div class="checkboxes">
119 ${h.checkbox('create_repo_group',value=True, checked=c.default_create_repo_group)}
119 ${h.checkbox('create_repo_group',value=True, checked=c.default_create_repo_group)}
120 <span class="help-block">
120 <span class="help-block">
121 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}<br/>
121 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}<br/>
122 ${_('User will be automatically set as this group owner.')}
122 ${_('User will be automatically set as this group owner.')}
123 </span>
123 </span>
124 </div>
124 </div>
125 </div>
125 </div>
126
126
127 <div class="buttons">
127 <div class="buttons">
128 ${h.submit('save',_('Save'),class_="btn")}
128 ${h.submit('save',_('Save'),class_="btn")}
129 </div>
129 </div>
130 </div>
130 </div>
131 </div>
131 </div>
132 ${h.end_form()}
132 ${h.end_form()}
133 </div>
133 </div>
134 <script>
134 <script>
135 $(document).ready(function(){
135 $(document).ready(function(){
136 $('#username').focus();
136 $('#username').focus();
137
137
138 $('#generate_password').on('click', function(e){
138 $('#generate_password').on('click', function(e){
139 var tmpl = "(${_('generated password:')} {0})";
139 var tmpl = "(${_('generated password:')} {0})";
140 var new_passwd = generatePassword(12);
140 var new_passwd = generatePassword(12);
141 $('#generate_password_preview').html(tmpl.format(new_passwd));
141 $('#generate_password_preview').html(tmpl.format(new_passwd));
142 $('#password').val(new_passwd);
142 $('#password').val(new_passwd);
143 $('#password_confirmation').val(new_passwd);
143 $('#password_confirmation').val(new_passwd);
144 })
144 })
145 })
145 })
146 </script>
146 </script>
147 </%def>
147 </%def>
@@ -1,49 +1,49 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s user settings') % c.user.username}
5 ${_('%s user settings') % c.user.username}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Users'),h.url('users'))}
14 ${h.link_to(_('Users'),h.route_path('users'))}
15 &raquo;
15 &raquo;
16 ${c.user.username}
16 ${c.user.username}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box user_settings">
24 <div class="box user_settings">
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28
28
29 ##main
29 ##main
30 <div class="sidebar-col-wrapper">
30 <div class="sidebar-col-wrapper">
31 <div class="sidebar">
31 <div class="sidebar">
32 <ul class="nav nav-pills nav-stacked">
32 <ul class="nav nav-pills nav-stacked">
33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
34 <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>
34 <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>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
36 <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>
36 <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>
37 <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>
37 <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>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
39 <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>
39 <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>
40 </ul>
40 </ul>
41 </div>
41 </div>
42
42
43 <div class="main-content-full-width">
43 <div class="main-content-full-width">
44 <%include file="/admin/users/user_edit_${c.active}.mako"/>
44 <%include file="/admin/users/user_edit_${c.active}.mako"/>
45 </div>
45 </div>
46 </div>
46 </div>
47 </div>
47 </div>
48
48
49 </%def>
49 </%def>
@@ -1,142 +1,117 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Users administration')}
5 ${_('Users administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span>
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 <ul class="links">
25 <ul class="links">
26 <li>
26 <li>
27 <a href="${h.url('new_user')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
27 <a href="${h.url('new_user')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
28 </li>
28 </li>
29 </ul>
29 </ul>
30 </div>
30 </div>
31
31
32 <div id="repos_list_wrap">
32 <div id="repos_list_wrap">
33 <table id="user_list_table" class="display"></table>
33 <table id="user_list_table" class="display"></table>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script type="text/javascript">
38
38 $(document).ready(function() {
39 $(document).ready(function() {
39
40
40 var get_datatable_count = function(){
41 var getDatatableCount = function(){
41 var datatable = $('#user_list_table').dataTable();
42 var table = $('#user_list_table').dataTable();
42 var api = datatable.api();
43 var page = table.api().page.info();
43 var total = api.page.info().recordsDisplay;
44 var active = page.recordsDisplay;
44 var active = datatable.fnGetFilteredData();
45 var total = page.recordsTotal;
45 var _text = _gettext("{0} active out of {1} users").format(active, total);
46 $('#user_count').text(_text);
47 };
48
49 // custom filter that filters by username OR email
50 $.fn.dataTable.ext.search.push(
51 function( settings, data, dataIndex ) {
52 var query = $('#q_filter').val();
53
54 var username = data[0];
55 var email = data[1];
56 var first_name = data[2];
57 var last_name = data[3];
58
46
59 var query_str = username + " " +
47 var _text = _gettext("{0} out of {1} users").format(active, total);
60 email + " " +
48 $('#user_count').text(_text);
61 first_name + " " +
62 last_name;
63
64 if ((query_str).indexOf(query) !== -1) {
65 return true;
66 }
67 return false;
68 }
69 );
70 // filtered data plugin
71 $.fn.dataTableExt.oApi.fnGetFilteredData = function ( oSettings ) {
72 var res = [];
73 for ( var i=0, iLen=oSettings.fnRecordsDisplay() ; i<iLen ; i++ ) {
74 var record = oSettings.aoData[i]._aData;
75 if(record['active_raw']){
76 res.push(record);
77 }
78 }
79 return res.length;
80 };
49 };
81
50
82 // user list
51 // user list
83 $('#user_list_table').DataTable({
52 $('#user_list_table').DataTable({
84 data: ${c.data|n},
53 processing: false,
54 serverSide: true,
55 ajax: "${h.route_path('users_data')}",
85 dom: 'rtp',
56 dom: 'rtp',
86 pageLength: ${c.visual.admin_grid_items},
57 pageLength: ${c.visual.admin_grid_items},
87 order: [[ 1, "asc" ]],
58 order: [[ 0, "asc" ]],
88 columns: [
59 columns: [
89 { data: {"_": "username",
60 { data: {"_": "username",
90 "sort": "username_raw"}, title: "${_('Username')}", className: "td-user" },
61 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
91 { data: {"_": "email",
62 { data: {"_": "email",
92 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
63 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
93 { data: {"_": "first_name",
64 { data: {"_": "first_name",
94 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
65 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
95 { data: {"_": "last_name",
66 { data: {"_": "last_name",
96 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
67 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
97 { data: {"_": "last_activity",
68 { data: {"_": "last_activity",
98 "sort": "last_activity_raw",
69 "sort": "last_activity",
99 "type": Number}, title: "${_('Last activity')}", className: "td-time" },
70 "type": Number}, title: "${_('Last activity')}", className: "td-time", orderable: false },
100 { data: {"_": "active",
71 { data: {"_": "active",
101 "sort": "active_raw"}, title: "${_('Active')}", className: "td-active" },
72 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
102 { data: {"_": "admin",
73 { data: {"_": "admin",
103 "sort": "admin_raw"}, title: "${_('Admin')}", className: "td-admin" },
74 "sort": "admin"}, title: "${_('Admin')}", className: "td-admin" },
104 { data: {"_": "extern_type",
75 { data: {"_": "extern_type",
105 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
76 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
106 { data: {"_": "action",
77 { data: {"_": "action",
107 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
78 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
108 ],
79 ],
109 language: {
80 language: {
110 paginate: DEFAULT_GRID_PAGINATION,
81 paginate: DEFAULT_GRID_PAGINATION,
111 emptyTable: _gettext("No users available yet.")
82 emptyTable: _gettext("No users available yet.")
112 },
83 },
113 "initComplete": function( settings, json ) {
84
114 get_datatable_count();
115 },
116 "createdRow": function ( row, data, index ) {
85 "createdRow": function ( row, data, index ) {
117 if (!data['active_raw']){
86 if (!data['active_raw']){
118 $(row).addClass('closed')
87 $(row).addClass('closed')
119 }
88 }
120 }
89 }
121 });
90 });
122
91
123 // update the counter when doing search
92 $('#user_list_table').on('xhr.dt', function(e, settings, json, xhr){
124 $('#user_list_table').on( 'search.dt', function (e,settings) {
93 $('#user_list_table').css('opacity', 1);
125 get_datatable_count();
94 });
95
96 $('#user_list_table').on('preXhr.dt', function(e, settings, data){
97 $('#user_list_table').css('opacity', 0.3);
126 });
98 });
127
99
128 // filter, filter both grids
100 // refresh counters on draw
129 $('#q_filter').on( 'keyup', function () {
101 $('#user_list_table').on('draw.dt', function(){
130 var user_api = $('#user_list_table').dataTable().api();
102 getDatatableCount();
131 user_api
132 .draw();
133 });
103 });
134
104
135 // refilter table if page load via back button
105 // filter
136 $("#q_filter").trigger('keyup');
106 $('#q_filter').on('keyup',
107 $.debounce(250, function() {
108 $('#user_list_table').DataTable().search(
109 $('#q_filter').val()
110 ).draw();
111 })
112 );
137
113
138 });
114 });
139
140 </script>
115 </script>
141
116
142 </%def>
117 </%def>
@@ -1,601 +1,601 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.url('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${title}">
93 <dd title="${title}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.author_string(email)}">
137 <div class="rc-user tooltip" title="${h.author_string(email)}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}">
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 </li>
233 </li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
241 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
242 </a>
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 <ul class="submenu">
247 <ul class="submenu">
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 %endif
250 %endif
251 %if c.rhodecode_db_repo.fork:
251 %if c.rhodecode_db_repo.fork:
252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 ${_('Compare fork')}</a></li>
253 ${_('Compare fork')}</a></li>
254 %endif
254 %endif
255
255
256 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
256 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
257
257
258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 %if c.rhodecode_db_repo.locked[0]:
259 %if c.rhodecode_db_repo.locked[0]:
260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 %else:
261 %else:
262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 %endif
263 %endif
264 %endif
264 %endif
265 %if c.rhodecode_user.username != h.DEFAULT_USER:
265 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 %endif
269 %endif
270 %endif
270 %endif
271 </ul>
271 </ul>
272 </li>
272 </li>
273 </ul>
273 </ul>
274 </div>
274 </div>
275 <div class="clear"></div>
275 <div class="clear"></div>
276 </div>
276 </div>
277 <!--- END CONTEXT BAR -->
277 <!--- END CONTEXT BAR -->
278
278
279 </%def>
279 </%def>
280
280
281 <%def name="usermenu(active=False)">
281 <%def name="usermenu(active=False)">
282 ## USER MENU
282 ## USER MENU
283 <li id="quick_login_li" class="${'active' if active else ''}">
283 <li id="quick_login_li" class="${'active' if active else ''}">
284 <a id="quick_login_link" class="menulink childs">
284 <a id="quick_login_link" class="menulink childs">
285 ${gravatar(c.rhodecode_user.email, 20)}
285 ${gravatar(c.rhodecode_user.email, 20)}
286 <span class="user">
286 <span class="user">
287 %if c.rhodecode_user.username != h.DEFAULT_USER:
287 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 %else:
289 %else:
290 <span>${_('Sign in')}</span>
290 <span>${_('Sign in')}</span>
291 %endif
291 %endif
292 </span>
292 </span>
293 </a>
293 </a>
294
294
295 <div class="user-menu submenu">
295 <div class="user-menu submenu">
296 <div id="quick_login">
296 <div id="quick_login">
297 %if c.rhodecode_user.username == h.DEFAULT_USER:
297 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 <h4>${_('Sign in to your account')}</h4>
298 <h4>${_('Sign in to your account')}</h4>
299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 <div class="form form-vertical">
300 <div class="form form-vertical">
301 <div class="fields">
301 <div class="fields">
302 <div class="field">
302 <div class="field">
303 <div class="label">
303 <div class="label">
304 <label for="username">${_('Username')}:</label>
304 <label for="username">${_('Username')}:</label>
305 </div>
305 </div>
306 <div class="input">
306 <div class="input">
307 ${h.text('username',class_='focus',tabindex=1)}
307 ${h.text('username',class_='focus',tabindex=1)}
308 </div>
308 </div>
309
309
310 </div>
310 </div>
311 <div class="field">
311 <div class="field">
312 <div class="label">
312 <div class="label">
313 <label for="password">${_('Password')}:</label>
313 <label for="password">${_('Password')}:</label>
314 %if h.HasPermissionAny('hg.password_reset.enabled')():
314 %if h.HasPermissionAny('hg.password_reset.enabled')():
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
316 %endif
316 %endif
317 </div>
317 </div>
318 <div class="input">
318 <div class="input">
319 ${h.password('password',class_='focus',tabindex=2)}
319 ${h.password('password',class_='focus',tabindex=2)}
320 </div>
320 </div>
321 </div>
321 </div>
322 <div class="buttons">
322 <div class="buttons">
323 <div class="register">
323 <div class="register">
324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
326 %endif
326 %endif
327 </div>
327 </div>
328 <div class="submit">
328 <div class="submit">
329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
330 </div>
330 </div>
331 </div>
331 </div>
332 </div>
332 </div>
333 </div>
333 </div>
334 ${h.end_form()}
334 ${h.end_form()}
335 %else:
335 %else:
336 <div class="">
336 <div class="">
337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
339 <div class="email">${c.rhodecode_user.email}</div>
339 <div class="email">${c.rhodecode_user.email}</div>
340 </div>
340 </div>
341 <div class="">
341 <div class="">
342 <ol class="links">
342 <ol class="links">
343 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
344 % if c.rhodecode_user.personal_repo_group:
344 % if c.rhodecode_user.personal_repo_group:
345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
346 % endif
346 % endif
347 <li class="logout">
347 <li class="logout">
348 ${h.secure_form(h.route_path('logout'))}
348 ${h.secure_form(h.route_path('logout'))}
349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
350 ${h.end_form()}
350 ${h.end_form()}
351 </li>
351 </li>
352 </ol>
352 </ol>
353 </div>
353 </div>
354 %endif
354 %endif
355 </div>
355 </div>
356 </div>
356 </div>
357 %if c.rhodecode_user.username != h.DEFAULT_USER:
357 %if c.rhodecode_user.username != h.DEFAULT_USER:
358 <div class="pill_container">
358 <div class="pill_container">
359 % if c.unread_notifications == 0:
359 % if c.unread_notifications == 0:
360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
361 % else:
361 % else:
362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
363 % endif
363 % endif
364 </div>
364 </div>
365 % endif
365 % endif
366 </li>
366 </li>
367 </%def>
367 </%def>
368
368
369 <%def name="menu_items(active=None)">
369 <%def name="menu_items(active=None)">
370 <%
370 <%
371 def is_active(selected):
371 def is_active(selected):
372 if selected == active:
372 if selected == active:
373 return "active"
373 return "active"
374 return ""
374 return ""
375 %>
375 %>
376 <ul id="quick" class="main_nav navigation horizontal-list">
376 <ul id="quick" class="main_nav navigation horizontal-list">
377 <!-- repo switcher -->
377 <!-- repo switcher -->
378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
379 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 <input id="repo_switcher" name="repo_switcher" type="hidden">
380 </li>
380 </li>
381
381
382 ## ROOT MENU
382 ## ROOT MENU
383 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 %if c.rhodecode_user.username != h.DEFAULT_USER:
384 <li class="${is_active('journal')}">
384 <li class="${is_active('journal')}">
385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
386 <div class="menulabel">${_('Journal')}</div>
386 <div class="menulabel">${_('Journal')}</div>
387 </a>
387 </a>
388 </li>
388 </li>
389 %else:
389 %else:
390 <li class="${is_active('journal')}">
390 <li class="${is_active('journal')}">
391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
392 <div class="menulabel">${_('Public journal')}</div>
392 <div class="menulabel">${_('Public journal')}</div>
393 </a>
393 </a>
394 </li>
394 </li>
395 %endif
395 %endif
396 <li class="${is_active('gists')}">
396 <li class="${is_active('gists')}">
397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
398 <div class="menulabel">${_('Gists')}</div>
398 <div class="menulabel">${_('Gists')}</div>
399 </a>
399 </a>
400 </li>
400 </li>
401 <li class="${is_active('search')}">
401 <li class="${is_active('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
403 <div class="menulabel">${_('Search')}</div>
403 <div class="menulabel">${_('Search')}</div>
404 </a>
404 </a>
405 </li>
405 </li>
406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
407 <li class="${is_active('admin')}">
407 <li class="${is_active('admin')}">
408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 </a>
410 </a>
411 ${admin_menu()}
411 ${admin_menu()}
412 </li>
412 </li>
413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
414 <li class="${is_active('admin')}">
414 <li class="${is_active('admin')}">
415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 </a>
417 </a>
418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
419 c.rhodecode_user.repository_groups_admin,
419 c.rhodecode_user.repository_groups_admin,
420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
421 </li>
421 </li>
422 % endif
422 % endif
423 % if c.debug_style:
423 % if c.debug_style:
424 <li class="${is_active('debug_style')}">
424 <li class="${is_active('debug_style')}">
425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
426 <div class="menulabel">${_('Style')}</div>
426 <div class="menulabel">${_('Style')}</div>
427 </a>
427 </a>
428 </li>
428 </li>
429 % endif
429 % endif
430 ## render extra user menu
430 ## render extra user menu
431 ${usermenu(active=(active=='my_account'))}
431 ${usermenu(active=(active=='my_account'))}
432 </ul>
432 </ul>
433
433
434 <script type="text/javascript">
434 <script type="text/javascript">
435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
436
436
437 /*format the look of items in the list*/
437 /*format the look of items in the list*/
438 var format = function(state, escapeMarkup){
438 var format = function(state, escapeMarkup){
439 if (!state.id){
439 if (!state.id){
440 return state.text; // optgroup
440 return state.text; // optgroup
441 }
441 }
442 var obj_dict = state.obj;
442 var obj_dict = state.obj;
443 var tmpl = '';
443 var tmpl = '';
444
444
445 if(obj_dict && state.type == 'repo'){
445 if(obj_dict && state.type == 'repo'){
446 if(obj_dict['repo_type'] === 'hg'){
446 if(obj_dict['repo_type'] === 'hg'){
447 tmpl += '<i class="icon-hg"></i> ';
447 tmpl += '<i class="icon-hg"></i> ';
448 }
448 }
449 else if(obj_dict['repo_type'] === 'git'){
449 else if(obj_dict['repo_type'] === 'git'){
450 tmpl += '<i class="icon-git"></i> ';
450 tmpl += '<i class="icon-git"></i> ';
451 }
451 }
452 else if(obj_dict['repo_type'] === 'svn'){
452 else if(obj_dict['repo_type'] === 'svn'){
453 tmpl += '<i class="icon-svn"></i> ';
453 tmpl += '<i class="icon-svn"></i> ';
454 }
454 }
455 if(obj_dict['private']){
455 if(obj_dict['private']){
456 tmpl += '<i class="icon-lock" ></i> ';
456 tmpl += '<i class="icon-lock" ></i> ';
457 }
457 }
458 else if(visual_show_public_icon){
458 else if(visual_show_public_icon){
459 tmpl += '<i class="icon-unlock-alt"></i> ';
459 tmpl += '<i class="icon-unlock-alt"></i> ';
460 }
460 }
461 }
461 }
462 if(obj_dict && state.type == 'commit') {
462 if(obj_dict && state.type == 'commit') {
463 tmpl += '<i class="icon-tag"></i>';
463 tmpl += '<i class="icon-tag"></i>';
464 }
464 }
465 if(obj_dict && state.type == 'group'){
465 if(obj_dict && state.type == 'group'){
466 tmpl += '<i class="icon-folder-close"></i> ';
466 tmpl += '<i class="icon-folder-close"></i> ';
467 }
467 }
468 tmpl += escapeMarkup(state.text);
468 tmpl += escapeMarkup(state.text);
469 return tmpl;
469 return tmpl;
470 };
470 };
471
471
472 var formatResult = function(result, container, query, escapeMarkup) {
472 var formatResult = function(result, container, query, escapeMarkup) {
473 return format(result, escapeMarkup);
473 return format(result, escapeMarkup);
474 };
474 };
475
475
476 var formatSelection = function(data, container, escapeMarkup) {
476 var formatSelection = function(data, container, escapeMarkup) {
477 return format(data, escapeMarkup);
477 return format(data, escapeMarkup);
478 };
478 };
479
479
480 $("#repo_switcher").select2({
480 $("#repo_switcher").select2({
481 cachedDataSource: {},
481 cachedDataSource: {},
482 minimumInputLength: 2,
482 minimumInputLength: 2,
483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
484 dropdownAutoWidth: true,
484 dropdownAutoWidth: true,
485 formatResult: formatResult,
485 formatResult: formatResult,
486 formatSelection: formatSelection,
486 formatSelection: formatSelection,
487 containerCssClass: "repo-switcher",
487 containerCssClass: "repo-switcher",
488 dropdownCssClass: "repo-switcher-dropdown",
488 dropdownCssClass: "repo-switcher-dropdown",
489 escapeMarkup: function(m){
489 escapeMarkup: function(m){
490 // don't escape our custom placeholder
490 // don't escape our custom placeholder
491 if(m.substr(0,23) == '<div class="menulabel">'){
491 if(m.substr(0,23) == '<div class="menulabel">'){
492 return m;
492 return m;
493 }
493 }
494
494
495 return Select2.util.escapeMarkup(m);
495 return Select2.util.escapeMarkup(m);
496 },
496 },
497 query: $.debounce(250, function(query){
497 query: $.debounce(250, function(query){
498 self = this;
498 self = this;
499 var cacheKey = query.term;
499 var cacheKey = query.term;
500 var cachedData = self.cachedDataSource[cacheKey];
500 var cachedData = self.cachedDataSource[cacheKey];
501
501
502 if (cachedData) {
502 if (cachedData) {
503 query.callback({results: cachedData.results});
503 query.callback({results: cachedData.results});
504 } else {
504 } else {
505 $.ajax({
505 $.ajax({
506 url: "${h.url('goto_switcher_data')}",
506 url: "${h.url('goto_switcher_data')}",
507 data: {'query': query.term},
507 data: {'query': query.term},
508 dataType: 'json',
508 dataType: 'json',
509 type: 'GET',
509 type: 'GET',
510 success: function(data) {
510 success: function(data) {
511 self.cachedDataSource[cacheKey] = data;
511 self.cachedDataSource[cacheKey] = data;
512 query.callback({results: data.results});
512 query.callback({results: data.results});
513 },
513 },
514 error: function(data, textStatus, errorThrown) {
514 error: function(data, textStatus, errorThrown) {
515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 }
516 }
517 })
517 })
518 }
518 }
519 })
519 })
520 });
520 });
521
521
522 $("#repo_switcher").on('select2-selecting', function(e){
522 $("#repo_switcher").on('select2-selecting', function(e){
523 e.preventDefault();
523 e.preventDefault();
524 window.location = e.choice.url;
524 window.location = e.choice.url;
525 });
525 });
526
526
527 </script>
527 </script>
528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
529 </%def>
529 </%def>
530
530
531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
532 <div class="modal-dialog">
532 <div class="modal-dialog">
533 <div class="modal-content">
533 <div class="modal-content">
534 <div class="modal-header">
534 <div class="modal-header">
535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
537 </div>
537 </div>
538 <div class="modal-body">
538 <div class="modal-body">
539 <div class="block-left">
539 <div class="block-left">
540 <table class="keyboard-mappings">
540 <table class="keyboard-mappings">
541 <tbody>
541 <tbody>
542 <tr>
542 <tr>
543 <th></th>
543 <th></th>
544 <th>${_('Site-wide shortcuts')}</th>
544 <th>${_('Site-wide shortcuts')}</th>
545 </tr>
545 </tr>
546 <%
546 <%
547 elems = [
547 elems = [
548 ('/', 'Open quick search box'),
548 ('/', 'Open quick search box'),
549 ('g h', 'Goto home page'),
549 ('g h', 'Goto home page'),
550 ('g g', 'Goto my private gists page'),
550 ('g g', 'Goto my private gists page'),
551 ('g G', 'Goto my public gists page'),
551 ('g G', 'Goto my public gists page'),
552 ('n r', 'New repository page'),
552 ('n r', 'New repository page'),
553 ('n g', 'New gist page'),
553 ('n g', 'New gist page'),
554 ]
554 ]
555 %>
555 %>
556 %for key, desc in elems:
556 %for key, desc in elems:
557 <tr>
557 <tr>
558 <td class="keys">
558 <td class="keys">
559 <span class="key tag">${key}</span>
559 <span class="key tag">${key}</span>
560 </td>
560 </td>
561 <td>${desc}</td>
561 <td>${desc}</td>
562 </tr>
562 </tr>
563 %endfor
563 %endfor
564 </tbody>
564 </tbody>
565 </table>
565 </table>
566 </div>
566 </div>
567 <div class="block-left">
567 <div class="block-left">
568 <table class="keyboard-mappings">
568 <table class="keyboard-mappings">
569 <tbody>
569 <tbody>
570 <tr>
570 <tr>
571 <th></th>
571 <th></th>
572 <th>${_('Repositories')}</th>
572 <th>${_('Repositories')}</th>
573 </tr>
573 </tr>
574 <%
574 <%
575 elems = [
575 elems = [
576 ('g s', 'Goto summary page'),
576 ('g s', 'Goto summary page'),
577 ('g c', 'Goto changelog page'),
577 ('g c', 'Goto changelog page'),
578 ('g f', 'Goto files page'),
578 ('g f', 'Goto files page'),
579 ('g F', 'Goto files page with file search activated'),
579 ('g F', 'Goto files page with file search activated'),
580 ('g p', 'Goto pull requests page'),
580 ('g p', 'Goto pull requests page'),
581 ('g o', 'Goto repository settings'),
581 ('g o', 'Goto repository settings'),
582 ('g O', 'Goto repository permissions settings'),
582 ('g O', 'Goto repository permissions settings'),
583 ]
583 ]
584 %>
584 %>
585 %for key, desc in elems:
585 %for key, desc in elems:
586 <tr>
586 <tr>
587 <td class="keys">
587 <td class="keys">
588 <span class="key tag">${key}</span>
588 <span class="key tag">${key}</span>
589 </td>
589 </td>
590 <td>${desc}</td>
590 <td>${desc}</td>
591 </tr>
591 </tr>
592 %endfor
592 %endfor
593 </tbody>
593 </tbody>
594 </table>
594 </table>
595 </div>
595 </div>
596 </div>
596 </div>
597 <div class="modal-footer">
597 <div class="modal-footer">
598 </div>
598 </div>
599 </div><!-- /.modal-content -->
599 </div><!-- /.modal-content -->
600 </div><!-- /.modal-dialog -->
600 </div><!-- /.modal-dialog -->
601 </div><!-- /.modal -->
601 </div><!-- /.modal -->
@@ -1,561 +1,575 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model import validators
26 from rhodecode.model import validators
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.user import UserModel
29 from rhodecode.model.user import UserModel
30 from rhodecode.tests import (
30 from rhodecode.tests import (
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
33 from rhodecode.tests.fixture import Fixture
33 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.utils import AssertResponse
34 from rhodecode.tests.utils import AssertResponse
35
35
36 fixture = Fixture()
36 fixture = Fixture()
37
37
38
38
39 def route_path(name, params=None, **kwargs):
40 import urllib
41 from rhodecode.apps._base import ADMIN_PREFIX
42
43 base_url = {
44 'users_data':
45 ADMIN_PREFIX + '/users_data',
46 }[name].format(**kwargs)
47
48 if params:
49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 return base_url
51
52
39 class TestAdminUsersController(TestController):
53 class TestAdminUsersController(TestController):
40 test_user_1 = 'testme'
54 test_user_1 = 'testme'
41 destroy_users = set()
55 destroy_users = set()
42
56
43 @classmethod
57 @classmethod
44 def teardown_method(cls, method):
58 def teardown_method(cls, method):
45 fixture.destroy_users(cls.destroy_users)
59 fixture.destroy_users(cls.destroy_users)
46
60
47 def test_create(self):
61 def test_create(self, xhr_header):
48 self.log_user()
62 self.log_user()
49 username = 'newtestuser'
63 username = 'newtestuser'
50 password = 'test12'
64 password = 'test12'
51 password_confirmation = password
65 password_confirmation = password
52 name = 'name'
66 name = 'name'
53 lastname = 'lastname'
67 lastname = 'lastname'
54 email = 'mail@mail.com'
68 email = 'mail@mail.com'
55
69
56 response = self.app.get(url('new_user'))
70 self.app.get(url('new_user'))
57
71
58 response = self.app.post(url('users'), params={
72 response = self.app.post(url('users'), params={
59 'username': username,
73 'username': username,
60 'password': password,
74 'password': password,
61 'password_confirmation': password_confirmation,
75 'password_confirmation': password_confirmation,
62 'firstname': name,
76 'firstname': name,
63 'active': True,
77 'active': True,
64 'lastname': lastname,
78 'lastname': lastname,
65 'extern_name': 'rhodecode',
79 'extern_name': 'rhodecode',
66 'extern_type': 'rhodecode',
80 'extern_type': 'rhodecode',
67 'email': email,
81 'email': email,
68 'csrf_token': self.csrf_token,
82 'csrf_token': self.csrf_token,
69 })
83 })
70 user_link = link_to(
84 user_link = link_to(
71 username,
85 username,
72 url('edit_user', user_id=User.get_by_username(username).user_id))
86 url('edit_user', user_id=User.get_by_username(username).user_id))
73 assert_session_flash(response, 'Created user %s' % (user_link,))
87 assert_session_flash(response, 'Created user %s' % (user_link,))
74 self.destroy_users.add(username)
88 self.destroy_users.add(username)
75
89
76 new_user = User.query().filter(User.username == username).one()
90 new_user = User.query().filter(User.username == username).one()
77
91
78 assert new_user.username == username
92 assert new_user.username == username
79 assert check_password(password, new_user.password)
93 assert check_password(password, new_user.password)
80 assert new_user.name == name
94 assert new_user.name == name
81 assert new_user.lastname == lastname
95 assert new_user.lastname == lastname
82 assert new_user.email == email
96 assert new_user.email == email
83
97
84 response.follow()
98 response = self.app.get(route_path('users_data'),
85 response = response.follow()
99 extra_environ=xhr_header)
86 response.mustcontain(username)
100 response.mustcontain(username)
87
101
88 def test_create_err(self):
102 def test_create_err(self):
89 self.log_user()
103 self.log_user()
90 username = 'new_user'
104 username = 'new_user'
91 password = ''
105 password = ''
92 name = 'name'
106 name = 'name'
93 lastname = 'lastname'
107 lastname = 'lastname'
94 email = 'errmail.com'
108 email = 'errmail.com'
95
109
96 response = self.app.get(url('new_user'))
110 self.app.get(url('new_user'))
97
111
98 response = self.app.post(url('users'), params={
112 response = self.app.post(url('users'), params={
99 'username': username,
113 'username': username,
100 'password': password,
114 'password': password,
101 'name': name,
115 'name': name,
102 'active': False,
116 'active': False,
103 'lastname': lastname,
117 'lastname': lastname,
104 'email': email,
118 'email': email,
105 'csrf_token': self.csrf_token,
119 'csrf_token': self.csrf_token,
106 })
120 })
107
121
108 msg = validators.ValidUsername(
122 msg = validators.ValidUsername(
109 False, {})._messages['system_invalid_username']
123 False, {})._messages['system_invalid_username']
110 msg = h.html_escape(msg % {'username': 'new_user'})
124 msg = h.html_escape(msg % {'username': 'new_user'})
111 response.mustcontain('<span class="error-message">%s</span>' % msg)
125 response.mustcontain('<span class="error-message">%s</span>' % msg)
112 response.mustcontain(
126 response.mustcontain(
113 '<span class="error-message">Please enter a value</span>')
127 '<span class="error-message">Please enter a value</span>')
114 response.mustcontain(
128 response.mustcontain(
115 '<span class="error-message">An email address must contain a'
129 '<span class="error-message">An email address must contain a'
116 ' single @</span>')
130 ' single @</span>')
117
131
118 def get_user():
132 def get_user():
119 Session().query(User).filter(User.username == username).one()
133 Session().query(User).filter(User.username == username).one()
120
134
121 with pytest.raises(NoResultFound):
135 with pytest.raises(NoResultFound):
122 get_user()
136 get_user()
123
137
124 def test_new(self):
138 def test_new(self):
125 self.log_user()
139 self.log_user()
126 self.app.get(url('new_user'))
140 self.app.get(url('new_user'))
127
141
128 @pytest.mark.parametrize("name, attrs", [
142 @pytest.mark.parametrize("name, attrs", [
129 ('firstname', {'firstname': 'new_username'}),
143 ('firstname', {'firstname': 'new_username'}),
130 ('lastname', {'lastname': 'new_username'}),
144 ('lastname', {'lastname': 'new_username'}),
131 ('admin', {'admin': True}),
145 ('admin', {'admin': True}),
132 ('admin', {'admin': False}),
146 ('admin', {'admin': False}),
133 ('extern_type', {'extern_type': 'ldap'}),
147 ('extern_type', {'extern_type': 'ldap'}),
134 ('extern_type', {'extern_type': None}),
148 ('extern_type', {'extern_type': None}),
135 ('extern_name', {'extern_name': 'test'}),
149 ('extern_name', {'extern_name': 'test'}),
136 ('extern_name', {'extern_name': None}),
150 ('extern_name', {'extern_name': None}),
137 ('active', {'active': False}),
151 ('active', {'active': False}),
138 ('active', {'active': True}),
152 ('active', {'active': True}),
139 ('email', {'email': 'some@email.com'}),
153 ('email', {'email': 'some@email.com'}),
140 ('language', {'language': 'de'}),
154 ('language', {'language': 'de'}),
141 ('language', {'language': 'en'}),
155 ('language', {'language': 'en'}),
142 # ('new_password', {'new_password': 'foobar123',
156 # ('new_password', {'new_password': 'foobar123',
143 # 'password_confirmation': 'foobar123'})
157 # 'password_confirmation': 'foobar123'})
144 ])
158 ])
145 def test_update(self, name, attrs):
159 def test_update(self, name, attrs):
146 self.log_user()
160 self.log_user()
147 usr = fixture.create_user(self.test_user_1, password='qweqwe',
161 usr = fixture.create_user(self.test_user_1, password='qweqwe',
148 email='testme@rhodecode.org',
162 email='testme@rhodecode.org',
149 extern_type='rhodecode',
163 extern_type='rhodecode',
150 extern_name=self.test_user_1,
164 extern_name=self.test_user_1,
151 skip_if_exists=True)
165 skip_if_exists=True)
152 Session().commit()
166 Session().commit()
153 self.destroy_users.add(self.test_user_1)
167 self.destroy_users.add(self.test_user_1)
154 params = usr.get_api_data()
168 params = usr.get_api_data()
155 cur_lang = params['language'] or 'en'
169 cur_lang = params['language'] or 'en'
156 params.update({
170 params.update({
157 'password_confirmation': '',
171 'password_confirmation': '',
158 'new_password': '',
172 'new_password': '',
159 'language': cur_lang,
173 'language': cur_lang,
160 '_method': 'put',
174 '_method': 'put',
161 'csrf_token': self.csrf_token,
175 'csrf_token': self.csrf_token,
162 })
176 })
163 params.update({'new_password': ''})
177 params.update({'new_password': ''})
164 params.update(attrs)
178 params.update(attrs)
165 if name == 'email':
179 if name == 'email':
166 params['emails'] = [attrs['email']]
180 params['emails'] = [attrs['email']]
167 elif name == 'extern_type':
181 elif name == 'extern_type':
168 # cannot update this via form, expected value is original one
182 # cannot update this via form, expected value is original one
169 params['extern_type'] = "rhodecode"
183 params['extern_type'] = "rhodecode"
170 elif name == 'extern_name':
184 elif name == 'extern_name':
171 # cannot update this via form, expected value is original one
185 # cannot update this via form, expected value is original one
172 params['extern_name'] = self.test_user_1
186 params['extern_name'] = self.test_user_1
173 # special case since this user is not
187 # special case since this user is not
174 # logged in yet his data is not filled
188 # logged in yet his data is not filled
175 # so we use creation data
189 # so we use creation data
176
190
177 response = self.app.post(url('user', user_id=usr.user_id), params)
191 response = self.app.post(url('user', user_id=usr.user_id), params)
178 assert response.status_int == 302
192 assert response.status_int == 302
179 assert_session_flash(response, 'User updated successfully')
193 assert_session_flash(response, 'User updated successfully')
180
194
181 updated_user = User.get_by_username(self.test_user_1)
195 updated_user = User.get_by_username(self.test_user_1)
182 updated_params = updated_user.get_api_data()
196 updated_params = updated_user.get_api_data()
183 updated_params.update({'password_confirmation': ''})
197 updated_params.update({'password_confirmation': ''})
184 updated_params.update({'new_password': ''})
198 updated_params.update({'new_password': ''})
185
199
186 del params['_method']
200 del params['_method']
187 del params['csrf_token']
201 del params['csrf_token']
188 assert params == updated_params
202 assert params == updated_params
189
203
190 def test_update_and_migrate_password(
204 def test_update_and_migrate_password(
191 self, autologin_user, real_crypto_backend):
205 self, autologin_user, real_crypto_backend):
192 from rhodecode.lib import auth
206 from rhodecode.lib import auth
193
207
194 # create new user, with sha256 password
208 # create new user, with sha256 password
195 temp_user = 'test_admin_sha256'
209 temp_user = 'test_admin_sha256'
196 user = fixture.create_user(temp_user)
210 user = fixture.create_user(temp_user)
197 user.password = auth._RhodeCodeCryptoSha256().hash_create(
211 user.password = auth._RhodeCodeCryptoSha256().hash_create(
198 b'test123')
212 b'test123')
199 Session().add(user)
213 Session().add(user)
200 Session().commit()
214 Session().commit()
201 self.destroy_users.add('test_admin_sha256')
215 self.destroy_users.add('test_admin_sha256')
202
216
203 params = user.get_api_data()
217 params = user.get_api_data()
204
218
205 params.update({
219 params.update({
206 'password_confirmation': 'qweqwe123',
220 'password_confirmation': 'qweqwe123',
207 'new_password': 'qweqwe123',
221 'new_password': 'qweqwe123',
208 'language': 'en',
222 'language': 'en',
209 '_method': 'put',
223 '_method': 'put',
210 'csrf_token': autologin_user.csrf_token,
224 'csrf_token': autologin_user.csrf_token,
211 })
225 })
212
226
213 response = self.app.post(url('user', user_id=user.user_id), params)
227 response = self.app.post(url('user', user_id=user.user_id), params)
214 assert response.status_int == 302
228 assert response.status_int == 302
215 assert_session_flash(response, 'User updated successfully')
229 assert_session_flash(response, 'User updated successfully')
216
230
217 # new password should be bcrypted, after log-in and transfer
231 # new password should be bcrypted, after log-in and transfer
218 user = User.get_by_username(temp_user)
232 user = User.get_by_username(temp_user)
219 assert user.password.startswith('$')
233 assert user.password.startswith('$')
220
234
221 updated_user = User.get_by_username(temp_user)
235 updated_user = User.get_by_username(temp_user)
222 updated_params = updated_user.get_api_data()
236 updated_params = updated_user.get_api_data()
223 updated_params.update({'password_confirmation': 'qweqwe123'})
237 updated_params.update({'password_confirmation': 'qweqwe123'})
224 updated_params.update({'new_password': 'qweqwe123'})
238 updated_params.update({'new_password': 'qweqwe123'})
225
239
226 del params['_method']
240 del params['_method']
227 del params['csrf_token']
241 del params['csrf_token']
228 assert params == updated_params
242 assert params == updated_params
229
243
230 def test_delete(self):
244 def test_delete(self):
231 self.log_user()
245 self.log_user()
232 username = 'newtestuserdeleteme'
246 username = 'newtestuserdeleteme'
233
247
234 fixture.create_user(name=username)
248 fixture.create_user(name=username)
235
249
236 new_user = Session().query(User)\
250 new_user = Session().query(User)\
237 .filter(User.username == username).one()
251 .filter(User.username == username).one()
238 response = self.app.post(url('user', user_id=new_user.user_id),
252 response = self.app.post(url('user', user_id=new_user.user_id),
239 params={'_method': 'delete',
253 params={'_method': 'delete',
240 'csrf_token': self.csrf_token})
254 'csrf_token': self.csrf_token})
241
255
242 assert_session_flash(response, 'Successfully deleted user')
256 assert_session_flash(response, 'Successfully deleted user')
243
257
244 def test_delete_owner_of_repository(self):
258 def test_delete_owner_of_repository(self):
245 self.log_user()
259 self.log_user()
246 username = 'newtestuserdeleteme_repo_owner'
260 username = 'newtestuserdeleteme_repo_owner'
247 obj_name = 'test_repo'
261 obj_name = 'test_repo'
248 usr = fixture.create_user(name=username)
262 usr = fixture.create_user(name=username)
249 self.destroy_users.add(username)
263 self.destroy_users.add(username)
250 fixture.create_repo(obj_name, cur_user=usr.username)
264 fixture.create_repo(obj_name, cur_user=usr.username)
251
265
252 new_user = Session().query(User)\
266 new_user = Session().query(User)\
253 .filter(User.username == username).one()
267 .filter(User.username == username).one()
254 response = self.app.post(url('user', user_id=new_user.user_id),
268 response = self.app.post(url('user', user_id=new_user.user_id),
255 params={'_method': 'delete',
269 params={'_method': 'delete',
256 'csrf_token': self.csrf_token})
270 'csrf_token': self.csrf_token})
257
271
258 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
272 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
259 'Switch owners or remove those repositories:%s' % (username,
273 'Switch owners or remove those repositories:%s' % (username,
260 obj_name)
274 obj_name)
261 assert_session_flash(response, msg)
275 assert_session_flash(response, msg)
262 fixture.destroy_repo(obj_name)
276 fixture.destroy_repo(obj_name)
263
277
264 def test_delete_owner_of_repository_detaching(self):
278 def test_delete_owner_of_repository_detaching(self):
265 self.log_user()
279 self.log_user()
266 username = 'newtestuserdeleteme_repo_owner_detach'
280 username = 'newtestuserdeleteme_repo_owner_detach'
267 obj_name = 'test_repo'
281 obj_name = 'test_repo'
268 usr = fixture.create_user(name=username)
282 usr = fixture.create_user(name=username)
269 self.destroy_users.add(username)
283 self.destroy_users.add(username)
270 fixture.create_repo(obj_name, cur_user=usr.username)
284 fixture.create_repo(obj_name, cur_user=usr.username)
271
285
272 new_user = Session().query(User)\
286 new_user = Session().query(User)\
273 .filter(User.username == username).one()
287 .filter(User.username == username).one()
274 response = self.app.post(url('user', user_id=new_user.user_id),
288 response = self.app.post(url('user', user_id=new_user.user_id),
275 params={'_method': 'delete',
289 params={'_method': 'delete',
276 'user_repos': 'detach',
290 'user_repos': 'detach',
277 'csrf_token': self.csrf_token})
291 'csrf_token': self.csrf_token})
278
292
279 msg = 'Detached 1 repositories'
293 msg = 'Detached 1 repositories'
280 assert_session_flash(response, msg)
294 assert_session_flash(response, msg)
281 fixture.destroy_repo(obj_name)
295 fixture.destroy_repo(obj_name)
282
296
283 def test_delete_owner_of_repository_deleting(self):
297 def test_delete_owner_of_repository_deleting(self):
284 self.log_user()
298 self.log_user()
285 username = 'newtestuserdeleteme_repo_owner_delete'
299 username = 'newtestuserdeleteme_repo_owner_delete'
286 obj_name = 'test_repo'
300 obj_name = 'test_repo'
287 usr = fixture.create_user(name=username)
301 usr = fixture.create_user(name=username)
288 self.destroy_users.add(username)
302 self.destroy_users.add(username)
289 fixture.create_repo(obj_name, cur_user=usr.username)
303 fixture.create_repo(obj_name, cur_user=usr.username)
290
304
291 new_user = Session().query(User)\
305 new_user = Session().query(User)\
292 .filter(User.username == username).one()
306 .filter(User.username == username).one()
293 response = self.app.post(url('user', user_id=new_user.user_id),
307 response = self.app.post(url('user', user_id=new_user.user_id),
294 params={'_method': 'delete',
308 params={'_method': 'delete',
295 'user_repos': 'delete',
309 'user_repos': 'delete',
296 'csrf_token': self.csrf_token})
310 'csrf_token': self.csrf_token})
297
311
298 msg = 'Deleted 1 repositories'
312 msg = 'Deleted 1 repositories'
299 assert_session_flash(response, msg)
313 assert_session_flash(response, msg)
300
314
301 def test_delete_owner_of_repository_group(self):
315 def test_delete_owner_of_repository_group(self):
302 self.log_user()
316 self.log_user()
303 username = 'newtestuserdeleteme_repo_group_owner'
317 username = 'newtestuserdeleteme_repo_group_owner'
304 obj_name = 'test_group'
318 obj_name = 'test_group'
305 usr = fixture.create_user(name=username)
319 usr = fixture.create_user(name=username)
306 self.destroy_users.add(username)
320 self.destroy_users.add(username)
307 fixture.create_repo_group(obj_name, cur_user=usr.username)
321 fixture.create_repo_group(obj_name, cur_user=usr.username)
308
322
309 new_user = Session().query(User)\
323 new_user = Session().query(User)\
310 .filter(User.username == username).one()
324 .filter(User.username == username).one()
311 response = self.app.post(url('user', user_id=new_user.user_id),
325 response = self.app.post(url('user', user_id=new_user.user_id),
312 params={'_method': 'delete',
326 params={'_method': 'delete',
313 'csrf_token': self.csrf_token})
327 'csrf_token': self.csrf_token})
314
328
315 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
329 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
316 'Switch owners or remove those repository groups:%s' % (username,
330 'Switch owners or remove those repository groups:%s' % (username,
317 obj_name)
331 obj_name)
318 assert_session_flash(response, msg)
332 assert_session_flash(response, msg)
319 fixture.destroy_repo_group(obj_name)
333 fixture.destroy_repo_group(obj_name)
320
334
321 def test_delete_owner_of_repository_group_detaching(self):
335 def test_delete_owner_of_repository_group_detaching(self):
322 self.log_user()
336 self.log_user()
323 username = 'newtestuserdeleteme_repo_group_owner_detach'
337 username = 'newtestuserdeleteme_repo_group_owner_detach'
324 obj_name = 'test_group'
338 obj_name = 'test_group'
325 usr = fixture.create_user(name=username)
339 usr = fixture.create_user(name=username)
326 self.destroy_users.add(username)
340 self.destroy_users.add(username)
327 fixture.create_repo_group(obj_name, cur_user=usr.username)
341 fixture.create_repo_group(obj_name, cur_user=usr.username)
328
342
329 new_user = Session().query(User)\
343 new_user = Session().query(User)\
330 .filter(User.username == username).one()
344 .filter(User.username == username).one()
331 response = self.app.post(url('user', user_id=new_user.user_id),
345 response = self.app.post(url('user', user_id=new_user.user_id),
332 params={'_method': 'delete',
346 params={'_method': 'delete',
333 'user_repo_groups': 'delete',
347 'user_repo_groups': 'delete',
334 'csrf_token': self.csrf_token})
348 'csrf_token': self.csrf_token})
335
349
336 msg = 'Deleted 1 repository groups'
350 msg = 'Deleted 1 repository groups'
337 assert_session_flash(response, msg)
351 assert_session_flash(response, msg)
338
352
339 def test_delete_owner_of_repository_group_deleting(self):
353 def test_delete_owner_of_repository_group_deleting(self):
340 self.log_user()
354 self.log_user()
341 username = 'newtestuserdeleteme_repo_group_owner_delete'
355 username = 'newtestuserdeleteme_repo_group_owner_delete'
342 obj_name = 'test_group'
356 obj_name = 'test_group'
343 usr = fixture.create_user(name=username)
357 usr = fixture.create_user(name=username)
344 self.destroy_users.add(username)
358 self.destroy_users.add(username)
345 fixture.create_repo_group(obj_name, cur_user=usr.username)
359 fixture.create_repo_group(obj_name, cur_user=usr.username)
346
360
347 new_user = Session().query(User)\
361 new_user = Session().query(User)\
348 .filter(User.username == username).one()
362 .filter(User.username == username).one()
349 response = self.app.post(url('user', user_id=new_user.user_id),
363 response = self.app.post(url('user', user_id=new_user.user_id),
350 params={'_method': 'delete',
364 params={'_method': 'delete',
351 'user_repo_groups': 'detach',
365 'user_repo_groups': 'detach',
352 'csrf_token': self.csrf_token})
366 'csrf_token': self.csrf_token})
353
367
354 msg = 'Detached 1 repository groups'
368 msg = 'Detached 1 repository groups'
355 assert_session_flash(response, msg)
369 assert_session_flash(response, msg)
356 fixture.destroy_repo_group(obj_name)
370 fixture.destroy_repo_group(obj_name)
357
371
358 def test_delete_owner_of_user_group(self):
372 def test_delete_owner_of_user_group(self):
359 self.log_user()
373 self.log_user()
360 username = 'newtestuserdeleteme_user_group_owner'
374 username = 'newtestuserdeleteme_user_group_owner'
361 obj_name = 'test_user_group'
375 obj_name = 'test_user_group'
362 usr = fixture.create_user(name=username)
376 usr = fixture.create_user(name=username)
363 self.destroy_users.add(username)
377 self.destroy_users.add(username)
364 fixture.create_user_group(obj_name, cur_user=usr.username)
378 fixture.create_user_group(obj_name, cur_user=usr.username)
365
379
366 new_user = Session().query(User)\
380 new_user = Session().query(User)\
367 .filter(User.username == username).one()
381 .filter(User.username == username).one()
368 response = self.app.post(url('user', user_id=new_user.user_id),
382 response = self.app.post(url('user', user_id=new_user.user_id),
369 params={'_method': 'delete',
383 params={'_method': 'delete',
370 'csrf_token': self.csrf_token})
384 'csrf_token': self.csrf_token})
371
385
372 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
386 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
373 'Switch owners or remove those user groups:%s' % (username,
387 'Switch owners or remove those user groups:%s' % (username,
374 obj_name)
388 obj_name)
375 assert_session_flash(response, msg)
389 assert_session_flash(response, msg)
376 fixture.destroy_user_group(obj_name)
390 fixture.destroy_user_group(obj_name)
377
391
378 def test_delete_owner_of_user_group_detaching(self):
392 def test_delete_owner_of_user_group_detaching(self):
379 self.log_user()
393 self.log_user()
380 username = 'newtestuserdeleteme_user_group_owner_detaching'
394 username = 'newtestuserdeleteme_user_group_owner_detaching'
381 obj_name = 'test_user_group'
395 obj_name = 'test_user_group'
382 usr = fixture.create_user(name=username)
396 usr = fixture.create_user(name=username)
383 self.destroy_users.add(username)
397 self.destroy_users.add(username)
384 fixture.create_user_group(obj_name, cur_user=usr.username)
398 fixture.create_user_group(obj_name, cur_user=usr.username)
385
399
386 new_user = Session().query(User)\
400 new_user = Session().query(User)\
387 .filter(User.username == username).one()
401 .filter(User.username == username).one()
388 try:
402 try:
389 response = self.app.post(url('user', user_id=new_user.user_id),
403 response = self.app.post(url('user', user_id=new_user.user_id),
390 params={'_method': 'delete',
404 params={'_method': 'delete',
391 'user_user_groups': 'detach',
405 'user_user_groups': 'detach',
392 'csrf_token': self.csrf_token})
406 'csrf_token': self.csrf_token})
393
407
394 msg = 'Detached 1 user groups'
408 msg = 'Detached 1 user groups'
395 assert_session_flash(response, msg)
409 assert_session_flash(response, msg)
396 finally:
410 finally:
397 fixture.destroy_user_group(obj_name)
411 fixture.destroy_user_group(obj_name)
398
412
399 def test_delete_owner_of_user_group_deleting(self):
413 def test_delete_owner_of_user_group_deleting(self):
400 self.log_user()
414 self.log_user()
401 username = 'newtestuserdeleteme_user_group_owner_deleting'
415 username = 'newtestuserdeleteme_user_group_owner_deleting'
402 obj_name = 'test_user_group'
416 obj_name = 'test_user_group'
403 usr = fixture.create_user(name=username)
417 usr = fixture.create_user(name=username)
404 self.destroy_users.add(username)
418 self.destroy_users.add(username)
405 fixture.create_user_group(obj_name, cur_user=usr.username)
419 fixture.create_user_group(obj_name, cur_user=usr.username)
406
420
407 new_user = Session().query(User)\
421 new_user = Session().query(User)\
408 .filter(User.username == username).one()
422 .filter(User.username == username).one()
409 response = self.app.post(url('user', user_id=new_user.user_id),
423 response = self.app.post(url('user', user_id=new_user.user_id),
410 params={'_method': 'delete',
424 params={'_method': 'delete',
411 'user_user_groups': 'delete',
425 'user_user_groups': 'delete',
412 'csrf_token': self.csrf_token})
426 'csrf_token': self.csrf_token})
413
427
414 msg = 'Deleted 1 user groups'
428 msg = 'Deleted 1 user groups'
415 assert_session_flash(response, msg)
429 assert_session_flash(response, msg)
416
430
417 def test_edit(self):
431 def test_edit(self):
418 self.log_user()
432 self.log_user()
419 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
433 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
420 self.app.get(url('edit_user', user_id=user.user_id))
434 self.app.get(url('edit_user', user_id=user.user_id))
421
435
422 @pytest.mark.parametrize(
436 @pytest.mark.parametrize(
423 'repo_create, repo_create_write, user_group_create, repo_group_create,'
437 'repo_create, repo_create_write, user_group_create, repo_group_create,'
424 'fork_create, inherit_default_permissions, expect_error,'
438 'fork_create, inherit_default_permissions, expect_error,'
425 'expect_form_error', [
439 'expect_form_error', [
426 ('hg.create.none', 'hg.create.write_on_repogroup.false',
440 ('hg.create.none', 'hg.create.write_on_repogroup.false',
427 'hg.usergroup.create.false', 'hg.repogroup.create.false',
441 'hg.usergroup.create.false', 'hg.repogroup.create.false',
428 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
442 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
429 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
443 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
430 'hg.usergroup.create.false', 'hg.repogroup.create.false',
444 'hg.usergroup.create.false', 'hg.repogroup.create.false',
431 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
445 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
432 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
446 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
433 'hg.usergroup.create.true', 'hg.repogroup.create.true',
447 'hg.usergroup.create.true', 'hg.repogroup.create.true',
434 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
448 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
435 False),
449 False),
436 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
450 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
437 'hg.usergroup.create.true', 'hg.repogroup.create.true',
451 'hg.usergroup.create.true', 'hg.repogroup.create.true',
438 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
452 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
439 True),
453 True),
440 ('', '', '', '', '', '', True, False),
454 ('', '', '', '', '', '', True, False),
441 ])
455 ])
442 def test_global_perms_on_user(
456 def test_global_perms_on_user(
443 self, repo_create, repo_create_write, user_group_create,
457 self, repo_create, repo_create_write, user_group_create,
444 repo_group_create, fork_create, expect_error, expect_form_error,
458 repo_group_create, fork_create, expect_error, expect_form_error,
445 inherit_default_permissions):
459 inherit_default_permissions):
446 self.log_user()
460 self.log_user()
447 user = fixture.create_user('dummy')
461 user = fixture.create_user('dummy')
448 uid = user.user_id
462 uid = user.user_id
449
463
450 # ENABLE REPO CREATE ON A GROUP
464 # ENABLE REPO CREATE ON A GROUP
451 perm_params = {
465 perm_params = {
452 'inherit_default_permissions': False,
466 'inherit_default_permissions': False,
453 'default_repo_create': repo_create,
467 'default_repo_create': repo_create,
454 'default_repo_create_on_write': repo_create_write,
468 'default_repo_create_on_write': repo_create_write,
455 'default_user_group_create': user_group_create,
469 'default_user_group_create': user_group_create,
456 'default_repo_group_create': repo_group_create,
470 'default_repo_group_create': repo_group_create,
457 'default_fork_create': fork_create,
471 'default_fork_create': fork_create,
458 'default_inherit_default_permissions': inherit_default_permissions,
472 'default_inherit_default_permissions': inherit_default_permissions,
459 '_method': 'put',
473 '_method': 'put',
460 'csrf_token': self.csrf_token,
474 'csrf_token': self.csrf_token,
461 }
475 }
462 response = self.app.post(
476 response = self.app.post(
463 url('edit_user_global_perms', user_id=uid),
477 url('edit_user_global_perms', user_id=uid),
464 params=perm_params)
478 params=perm_params)
465
479
466 if expect_form_error:
480 if expect_form_error:
467 assert response.status_int == 200
481 assert response.status_int == 200
468 response.mustcontain('Value must be one of')
482 response.mustcontain('Value must be one of')
469 else:
483 else:
470 if expect_error:
484 if expect_error:
471 msg = 'An error occurred during permissions saving'
485 msg = 'An error occurred during permissions saving'
472 else:
486 else:
473 msg = 'User global permissions updated successfully'
487 msg = 'User global permissions updated successfully'
474 ug = User.get(uid)
488 ug = User.get(uid)
475 del perm_params['_method']
489 del perm_params['_method']
476 del perm_params['inherit_default_permissions']
490 del perm_params['inherit_default_permissions']
477 del perm_params['csrf_token']
491 del perm_params['csrf_token']
478 assert perm_params == ug.get_default_perms()
492 assert perm_params == ug.get_default_perms()
479 assert_session_flash(response, msg)
493 assert_session_flash(response, msg)
480 fixture.destroy_user(uid)
494 fixture.destroy_user(uid)
481
495
482 def test_global_permissions_initial_values(self, user_util):
496 def test_global_permissions_initial_values(self, user_util):
483 self.log_user()
497 self.log_user()
484 user = user_util.create_user()
498 user = user_util.create_user()
485 uid = user.user_id
499 uid = user.user_id
486 response = self.app.get(url('edit_user_global_perms', user_id=uid))
500 response = self.app.get(url('edit_user_global_perms', user_id=uid))
487 default_user = User.get_default_user()
501 default_user = User.get_default_user()
488 default_permissions = default_user.get_default_perms()
502 default_permissions = default_user.get_default_perms()
489 assert_response = AssertResponse(response)
503 assert_response = AssertResponse(response)
490 expected_permissions = (
504 expected_permissions = (
491 'default_repo_create', 'default_repo_create_on_write',
505 'default_repo_create', 'default_repo_create_on_write',
492 'default_fork_create', 'default_repo_group_create',
506 'default_fork_create', 'default_repo_group_create',
493 'default_user_group_create', 'default_inherit_default_permissions')
507 'default_user_group_create', 'default_inherit_default_permissions')
494 for permission in expected_permissions:
508 for permission in expected_permissions:
495 css_selector = '[name={}][checked=checked]'.format(permission)
509 css_selector = '[name={}][checked=checked]'.format(permission)
496 element = assert_response.get_element(css_selector)
510 element = assert_response.get_element(css_selector)
497 assert element.value == default_permissions[permission]
511 assert element.value == default_permissions[permission]
498
512
499 def test_ips(self):
513 def test_ips(self):
500 self.log_user()
514 self.log_user()
501 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
515 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
502 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
516 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
503 response.mustcontain('All IP addresses are allowed')
517 response.mustcontain('All IP addresses are allowed')
504
518
505 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
519 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
506 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
520 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
507 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
521 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
508 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
522 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
509 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
523 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
510 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
524 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
511 ('127_bad_ip', 'foobar', 'foobar', True),
525 ('127_bad_ip', 'foobar', 'foobar', True),
512 ])
526 ])
513 def test_add_ip(self, test_name, ip, ip_range, failure):
527 def test_add_ip(self, test_name, ip, ip_range, failure):
514 self.log_user()
528 self.log_user()
515 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
529 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
516 user_id = user.user_id
530 user_id = user.user_id
517
531
518 response = self.app.post(url('edit_user_ips', user_id=user_id),
532 response = self.app.post(url('edit_user_ips', user_id=user_id),
519 params={'new_ip': ip, '_method': 'put',
533 params={'new_ip': ip, '_method': 'put',
520 'csrf_token': self.csrf_token})
534 'csrf_token': self.csrf_token})
521
535
522 if failure:
536 if failure:
523 assert_session_flash(
537 assert_session_flash(
524 response, 'Please enter a valid IPv4 or IpV6 address')
538 response, 'Please enter a valid IPv4 or IpV6 address')
525 response = self.app.get(url('edit_user_ips', user_id=user_id))
539 response = self.app.get(url('edit_user_ips', user_id=user_id))
526 response.mustcontain(no=[ip])
540 response.mustcontain(no=[ip])
527 response.mustcontain(no=[ip_range])
541 response.mustcontain(no=[ip_range])
528
542
529 else:
543 else:
530 response = self.app.get(url('edit_user_ips', user_id=user_id))
544 response = self.app.get(url('edit_user_ips', user_id=user_id))
531 response.mustcontain(ip)
545 response.mustcontain(ip)
532 response.mustcontain(ip_range)
546 response.mustcontain(ip_range)
533
547
534 # cleanup
548 # cleanup
535 for del_ip in UserIpMap.query().filter(
549 for del_ip in UserIpMap.query().filter(
536 UserIpMap.user_id == user_id).all():
550 UserIpMap.user_id == user_id).all():
537 Session().delete(del_ip)
551 Session().delete(del_ip)
538 Session().commit()
552 Session().commit()
539
553
540 def test_delete_ip(self):
554 def test_delete_ip(self):
541 self.log_user()
555 self.log_user()
542 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
556 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
543 user_id = user.user_id
557 user_id = user.user_id
544 ip = '127.0.0.1/32'
558 ip = '127.0.0.1/32'
545 ip_range = '127.0.0.1 - 127.0.0.1'
559 ip_range = '127.0.0.1 - 127.0.0.1'
546 new_ip = UserModel().add_extra_ip(user_id, ip)
560 new_ip = UserModel().add_extra_ip(user_id, ip)
547 Session().commit()
561 Session().commit()
548 new_ip_id = new_ip.ip_id
562 new_ip_id = new_ip.ip_id
549
563
550 response = self.app.get(url('edit_user_ips', user_id=user_id))
564 response = self.app.get(url('edit_user_ips', user_id=user_id))
551 response.mustcontain(ip)
565 response.mustcontain(ip)
552 response.mustcontain(ip_range)
566 response.mustcontain(ip_range)
553
567
554 self.app.post(url('edit_user_ips', user_id=user_id),
568 self.app.post(url('edit_user_ips', user_id=user_id),
555 params={'_method': 'delete', 'del_ip_id': new_ip_id,
569 params={'_method': 'delete', 'del_ip_id': new_ip_id,
556 'csrf_token': self.csrf_token})
570 'csrf_token': self.csrf_token})
557
571
558 response = self.app.get(url('edit_user_ips', user_id=user_id))
572 response = self.app.get(url('edit_user_ips', user_id=user_id))
559 response.mustcontain('All IP addresses are allowed')
573 response.mustcontain('All IP addresses are allowed')
560 response.mustcontain(no=[ip])
574 response.mustcontain(no=[ip])
561 response.mustcontain(no=[ip_range])
575 response.mustcontain(no=[ip_range])
General Comments 0
You need to be logged in to leave comments. Login now