##// END OF EJS Templates
user-groups: moved the display of user group into a pyramid view
marcink -
r1980:f55ac84b default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,113 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.model.db import UserGroup, User
24 from rhodecode.model.meta import Session
25
26 from rhodecode.tests import (
27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29
30 fixture = Fixture()
31
32
33 def route_path(name, params=None, **kwargs):
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
36
37 base_url = {
38 'user_groups': ADMIN_PREFIX + '/user_groups',
39 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
40 'user_group_members_data': ADMIN_PREFIX + '/user_groups/{user_group_id}/members',
41 }[name].format(**kwargs)
42
43 if params:
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 return base_url
46
47
48 class TestAdminUserGroupsView(TestController):
49
50 def test_show_users(self):
51 self.log_user()
52 self.app.get(route_path('user_groups'))
53
54 def test_show_user_groups_data(self, xhr_header):
55 self.log_user()
56 response = self.app.get(route_path(
57 'user_groups_data'), extra_environ=xhr_header)
58
59 all_user_groups = UserGroup.query().count()
60 assert response.json['recordsTotal'] == all_user_groups
61
62 def test_show_user_groups_data_filtered(self, xhr_header):
63 self.log_user()
64 response = self.app.get(route_path(
65 'user_groups_data', params={'search[value]': 'empty_search'}),
66 extra_environ=xhr_header)
67
68 all_user_groups = UserGroup.query().count()
69 assert response.json['recordsTotal'] == all_user_groups
70 assert response.json['recordsFiltered'] == 0
71
72 def test_usergroup_escape(self, user_util, xhr_header):
73 self.log_user()
74
75 xss_img = '<img src="/image1" onload="alert(\'Hello, World!\');">'
76 user = user_util.create_user()
77 user.name = xss_img
78 user.lastname = xss_img
79 Session().add(user)
80 Session().commit()
81
82 user_group = user_util.create_user_group()
83
84 user_group.users_group_name = xss_img
85 user_group.user_group_description = '<strong onload="alert();">DESC</strong>'
86
87 response = self.app.get(
88 route_path('user_groups_data'), extra_environ=xhr_header)
89
90 response.mustcontain(
91 '&lt;strong onload=&#34;alert();&#34;&gt;DESC&lt;/strong&gt;')
92 response.mustcontain(
93 '&lt;img src=&#34;/image1&#34; onload=&#34;'
94 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
95
96 def test_edit_user_group_autocomplete_empty_members(self, xhr_header, user_util):
97 self.log_user()
98 ug = user_util.create_user_group()
99 response = self.app.get(
100 route_path('user_group_members_data', user_group_id=ug.users_group_id),
101 extra_environ=xhr_header)
102
103 assert response.json == {'members': []}
104
105 def test_edit_user_group_autocomplete_members(self, xhr_header, user_util):
106 self.log_user()
107 members = [u.user_id for u in User.get_all()]
108 ug = user_util.create_user_group(members=members)
109 response = self.app.get(
110 route_path('user_group_members_data', user_group_id=ug.users_group_id),
111 extra_environ=xhr_header)
112
113 assert len(response.json['members']) == len(members)
@@ -0,0 +1,195 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import datetime
23
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.lib.helpers import Page
28 from rhodecode.model.scm import UserGroupList
29 from rhodecode_tools.lib.ext_json import json
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired, NotAnonymous,
34 HasUserGroupPermissionAnyDecorator)
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import PartialRenderer
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.db import User, UserGroup, UserGroupMember, or_, count
42 from rhodecode.model.meta import Session
43
44 log = logging.getLogger(__name__)
45
46
47 class AdminUserGroupsView(BaseAppView, DataGridAppView):
48
49 def load_default_context(self):
50 c = self._get_local_tmpl_context()
51 self._register_global_c(c)
52 return c
53
54 # permission check in data loading of
55 # `user_groups_list_data` via UserGroupList
56 @NotAnonymous()
57 @view_config(
58 route_name='user_groups', request_method='GET',
59 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
60 def user_groups_list(self):
61 c = self.load_default_context()
62 return self._get_template_context(c)
63
64 # permission check inside
65 @NotAnonymous()
66 @view_config(
67 route_name='user_groups_data', request_method='GET',
68 renderer='json_ext', xhr=True)
69 def user_groups_list_data(self):
70 column_map = {
71 'active': 'users_group_active',
72 'description': 'user_group_description',
73 'members': 'members_total',
74 'owner': 'user_username',
75 'sync': 'group_data'
76 }
77 draw, start, limit = self._extract_chunk(self.request)
78 search_q, order_by, order_dir = self._extract_ordering(
79 self.request, column_map=column_map)
80
81 _render = PartialRenderer('data_table/_dt_elements.mako')
82
83 def user_group_name(user_group_id, user_group_name):
84 return _render("user_group_name", user_group_id, user_group_name)
85
86 def user_group_actions(user_group_id, user_group_name):
87 return _render("user_group_actions", user_group_id, user_group_name)
88
89 def user_profile(username):
90 return _render('user_profile', username)
91
92 user_groups_data_total_count = UserGroup.query().count()
93
94 member_count = count(UserGroupMember.user_id)
95 base_q = Session.query(
96 UserGroup.users_group_name,
97 UserGroup.user_group_description,
98 UserGroup.users_group_active,
99 UserGroup.users_group_id,
100 UserGroup.group_data,
101 User,
102 member_count.label('member_count')
103 ) \
104 .outerjoin(UserGroupMember) \
105 .join(User, User.user_id == UserGroup.user_id) \
106 .group_by(UserGroup, User)
107
108 if search_q:
109 like_expression = u'%{}%'.format(safe_unicode(search_q))
110 base_q = base_q.filter(or_(
111 UserGroup.users_group_name.ilike(like_expression),
112 ))
113
114 user_groups_data_total_filtered_count = base_q.count()
115
116 if order_by == 'members_total':
117 sort_col = member_count
118 elif order_by == 'user_username':
119 sort_col = User.username
120 else:
121 sort_col = getattr(UserGroup, order_by, None)
122
123 if isinstance(sort_col, count) or sort_col:
124 if order_dir == 'asc':
125 sort_col = sort_col.asc()
126 else:
127 sort_col = sort_col.desc()
128
129 base_q = base_q.order_by(sort_col)
130 base_q = base_q.offset(start).limit(limit)
131
132 # authenticated access to user groups
133 user_group_list = base_q.all()
134 auth_user_group_list = UserGroupList(
135 user_group_list, perm_set=['usergroup.admin'])
136
137 user_groups_data = []
138 for user_gr in auth_user_group_list:
139 user_groups_data.append({
140 "users_group_name": user_group_name(
141 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
142 "name_raw": h.escape(user_gr.users_group_name),
143 "description": h.escape(user_gr.user_group_description),
144 "members": user_gr.member_count,
145 # NOTE(marcink): because of advanced query we
146 # need to load it like that
147 "sync": UserGroup._load_group_data(
148 user_gr.group_data).get('extern_type'),
149 "active": h.bool2icon(user_gr.users_group_active),
150 "owner": user_profile(user_gr.User.username),
151 "action": user_group_actions(
152 user_gr.users_group_id, user_gr.users_group_name)
153 })
154
155 data = ({
156 'draw': draw,
157 'data': user_groups_data,
158 'recordsTotal': user_groups_data_total_count,
159 'recordsFiltered': user_groups_data_total_filtered_count,
160 })
161
162 return data
163
164 @LoginRequired()
165 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
166 @view_config(
167 route_name='user_group_members_data', request_method='GET',
168 renderer='json_ext', xhr=True)
169 def user_group_members(self):
170 """
171 Return members of given user group
172 """
173 user_group_id = self.request.matchdict['user_group_id']
174 user_group = UserGroup.get_or_404(user_group_id)
175 group_members_obj = sorted((x.user for x in user_group.members),
176 key=lambda u: u.username.lower())
177
178 group_members = [
179 {
180 'id': user.user_id,
181 'first_name': user.first_name,
182 'last_name': user.last_name,
183 'username': user.username,
184 'icon_link': h.gravatar_url(user.email, 30),
185 'value_display': h.person(user.email),
186 'value': user.username,
187 'value_type': 'user',
188 'active': user.active,
189 }
190 for user in group_members_obj
191 ]
192
193 return {
194 'members': group_members
195 }
@@ -1,179 +1,192 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 admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
33 name='admin_audit_logs',
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:\d+}')
38 pattern='/pull_requests/{pull_request_id:\d+}')
39 config.add_route(
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:\d+}')
41 pattern='/pull-requests/{pull_request_id:\d+}')
42 config.add_route(
42 config.add_route(
43 name='pull_requests_global',
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:\d+}')
44 pattern='/pull-request/{pull_request_id:\d+}')
45
45
46 config.add_route(
46 config.add_route(
47 name='admin_settings_open_source',
47 name='admin_settings_open_source',
48 pattern='/settings/open_source')
48 pattern='/settings/open_source')
49 config.add_route(
49 config.add_route(
50 name='admin_settings_vcs_svn_generate_cfg',
50 name='admin_settings_vcs_svn_generate_cfg',
51 pattern='/settings/vcs/svn_generate_cfg')
51 pattern='/settings/vcs/svn_generate_cfg')
52
52
53 config.add_route(
53 config.add_route(
54 name='admin_settings_system',
54 name='admin_settings_system',
55 pattern='/settings/system')
55 pattern='/settings/system')
56 config.add_route(
56 config.add_route(
57 name='admin_settings_system_update',
57 name='admin_settings_system_update',
58 pattern='/settings/system/updates')
58 pattern='/settings/system/updates')
59
59
60 config.add_route(
60 config.add_route(
61 name='admin_settings_sessions',
61 name='admin_settings_sessions',
62 pattern='/settings/sessions')
62 pattern='/settings/sessions')
63 config.add_route(
63 config.add_route(
64 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
65 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
66
66
67 config.add_route(
67 config.add_route(
68 name='admin_settings_process_management',
68 name='admin_settings_process_management',
69 pattern='/settings/process_management')
69 pattern='/settings/process_management')
70 config.add_route(
70 config.add_route(
71 name='admin_settings_process_management_signal',
71 name='admin_settings_process_management_signal',
72 pattern='/settings/process_management/signal')
72 pattern='/settings/process_management/signal')
73
73
74 # global permissions
74 # global permissions
75
75
76 config.add_route(
76 config.add_route(
77 name='admin_permissions_application',
77 name='admin_permissions_application',
78 pattern='/permissions/application')
78 pattern='/permissions/application')
79 config.add_route(
79 config.add_route(
80 name='admin_permissions_application_update',
80 name='admin_permissions_application_update',
81 pattern='/permissions/application/update')
81 pattern='/permissions/application/update')
82
82
83 config.add_route(
83 config.add_route(
84 name='admin_permissions_global',
84 name='admin_permissions_global',
85 pattern='/permissions/global')
85 pattern='/permissions/global')
86 config.add_route(
86 config.add_route(
87 name='admin_permissions_global_update',
87 name='admin_permissions_global_update',
88 pattern='/permissions/global/update')
88 pattern='/permissions/global/update')
89
89
90 config.add_route(
90 config.add_route(
91 name='admin_permissions_object',
91 name='admin_permissions_object',
92 pattern='/permissions/object')
92 pattern='/permissions/object')
93 config.add_route(
93 config.add_route(
94 name='admin_permissions_object_update',
94 name='admin_permissions_object_update',
95 pattern='/permissions/object/update')
95 pattern='/permissions/object/update')
96
96
97 config.add_route(
97 config.add_route(
98 name='admin_permissions_ips',
98 name='admin_permissions_ips',
99 pattern='/permissions/ips')
99 pattern='/permissions/ips')
100
100
101 config.add_route(
101 config.add_route(
102 name='admin_permissions_overview',
102 name='admin_permissions_overview',
103 pattern='/permissions/overview')
103 pattern='/permissions/overview')
104
104
105 config.add_route(
105 config.add_route(
106 name='admin_permissions_auth_token_access',
106 name='admin_permissions_auth_token_access',
107 pattern='/permissions/auth_token_access')
107 pattern='/permissions/auth_token_access')
108
108
109 # users admin
109 # users admin
110 config.add_route(
110 config.add_route(
111 name='users',
111 name='users',
112 pattern='/users')
112 pattern='/users')
113
113
114 config.add_route(
114 config.add_route(
115 name='users_data',
115 name='users_data',
116 pattern='/users_data')
116 pattern='/users_data')
117
117
118 # user auth tokens
118 # user auth tokens
119 config.add_route(
119 config.add_route(
120 name='edit_user_auth_tokens',
120 name='edit_user_auth_tokens',
121 pattern='/users/{user_id:\d+}/edit/auth_tokens')
121 pattern='/users/{user_id:\d+}/edit/auth_tokens')
122 config.add_route(
122 config.add_route(
123 name='edit_user_auth_tokens_add',
123 name='edit_user_auth_tokens_add',
124 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
124 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
125 config.add_route(
125 config.add_route(
126 name='edit_user_auth_tokens_delete',
126 name='edit_user_auth_tokens_delete',
127 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
127 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
128
128
129 # user emails
129 # user emails
130 config.add_route(
130 config.add_route(
131 name='edit_user_emails',
131 name='edit_user_emails',
132 pattern='/users/{user_id:\d+}/edit/emails')
132 pattern='/users/{user_id:\d+}/edit/emails')
133 config.add_route(
133 config.add_route(
134 name='edit_user_emails_add',
134 name='edit_user_emails_add',
135 pattern='/users/{user_id:\d+}/edit/emails/new')
135 pattern='/users/{user_id:\d+}/edit/emails/new')
136 config.add_route(
136 config.add_route(
137 name='edit_user_emails_delete',
137 name='edit_user_emails_delete',
138 pattern='/users/{user_id:\d+}/edit/emails/delete')
138 pattern='/users/{user_id:\d+}/edit/emails/delete')
139
139
140 # user IPs
140 # user IPs
141 config.add_route(
141 config.add_route(
142 name='edit_user_ips',
142 name='edit_user_ips',
143 pattern='/users/{user_id:\d+}/edit/ips')
143 pattern='/users/{user_id:\d+}/edit/ips')
144 config.add_route(
144 config.add_route(
145 name='edit_user_ips_add',
145 name='edit_user_ips_add',
146 pattern='/users/{user_id:\d+}/edit/ips/new')
146 pattern='/users/{user_id:\d+}/edit/ips/new')
147 config.add_route(
147 config.add_route(
148 name='edit_user_ips_delete',
148 name='edit_user_ips_delete',
149 pattern='/users/{user_id:\d+}/edit/ips/delete')
149 pattern='/users/{user_id:\d+}/edit/ips/delete')
150
150
151 # user groups management
151 # user groups management
152 config.add_route(
152 config.add_route(
153 name='edit_user_groups_management',
153 name='edit_user_groups_management',
154 pattern='/users/{user_id:\d+}/edit/groups_management')
154 pattern='/users/{user_id:\d+}/edit/groups_management')
155
155
156 config.add_route(
156 config.add_route(
157 name='edit_user_groups_management_updates',
157 name='edit_user_groups_management_updates',
158 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
158 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
159
159
160 # user audit logs
160 # user audit logs
161 config.add_route(
161 config.add_route(
162 name='edit_user_audit_logs',
162 name='edit_user_audit_logs',
163 pattern='/users/{user_id:\d+}/edit/audit')
163 pattern='/users/{user_id:\d+}/edit/audit')
164
164
165 # user groups admin
166 config.add_route(
167 name='user_groups',
168 pattern='/user_groups')
169
170 config.add_route(
171 name='user_groups_data',
172 pattern='/user_groups_data')
173
174 config.add_route(
175 name='user_group_members_data',
176 pattern='/user_groups/{user_group_id:\d+}/members')
177
165
178
166 def includeme(config):
179 def includeme(config):
167 settings = config.get_settings()
180 settings = config.get_settings()
168
181
169 # Create admin navigation registry and add it to the pyramid registry.
182 # Create admin navigation registry and add it to the pyramid registry.
170 labs_active = str2bool(settings.get('labs_settings_active', False))
183 labs_active = str2bool(settings.get('labs_settings_active', False))
171 navigation_registry = NavigationRegistry(labs_active=labs_active)
184 navigation_registry = NavigationRegistry(labs_active=labs_active)
172 config.registry.registerUtility(navigation_registry)
185 config.registry.registerUtility(navigation_registry)
173
186
174 # main admin routes
187 # main admin routes
175 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
188 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
176 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
189 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
177
190
178 # Scan module for configuration decorators.
191 # Scan module for configuration decorators.
179 config.scan()
192 config.scan()
@@ -1,505 +1,510 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 import datetime
22 import datetime
23 import formencode
23 import formencode
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from sqlalchemy.sql.functions import coalesce
27 from sqlalchemy.sql.functions import coalesce
28
28
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30
30
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
40 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class AdminUsersView(BaseAppView, DataGridAppView):
46 class AdminUsersView(BaseAppView, DataGridAppView):
47 ALLOW_SCOPED_TOKENS = False
47 ALLOW_SCOPED_TOKENS = False
48 """
48 """
49 This view has alternative version inside EE, if modified please take a look
49 This view has alternative version inside EE, if modified please take a look
50 in there as well.
50 in there as well.
51 """
51 """
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
56 self._register_global_c(c)
56 self._register_global_c(c)
57 return c
57 return c
58
58
59 def _redirect_for_default_user(self, username):
59 def _redirect_for_default_user(self, username):
60 _ = self.request.translate
60 _ = self.request.translate
61 if username == User.DEFAULT_USER:
61 if username == User.DEFAULT_USER:
62 h.flash(_("You can't edit this user"), category='warning')
62 h.flash(_("You can't edit this user"), category='warning')
63 # TODO(marcink): redirect to 'users' admin panel once this
63 # TODO(marcink): redirect to 'users' admin panel once this
64 # is a pyramid view
64 # is a pyramid view
65 raise HTTPFound('/')
65 raise HTTPFound('/')
66
66
67 @HasPermissionAllDecorator('hg.admin')
67 @HasPermissionAllDecorator('hg.admin')
68 @view_config(
68 @view_config(
69 route_name='users', request_method='GET',
69 route_name='users', request_method='GET',
70 renderer='rhodecode:templates/admin/users/users.mako')
70 renderer='rhodecode:templates/admin/users/users.mako')
71 def users_list(self):
71 def users_list(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 return self._get_template_context(c)
73 return self._get_template_context(c)
74
74
75 @HasPermissionAllDecorator('hg.admin')
75 @HasPermissionAllDecorator('hg.admin')
76 @view_config(
76 @view_config(
77 # renderer defined below
77 # renderer defined below
78 route_name='users_data', request_method='GET',
78 route_name='users_data', request_method='GET',
79 renderer='json_ext', xhr=True)
79 renderer='json_ext', xhr=True)
80 def users_list_data(self):
80 def users_list_data(self):
81 column_map = {
82 'first_name': 'name',
83 'last_name': 'lastname',
84 }
81 draw, start, limit = self._extract_chunk(self.request)
85 draw, start, limit = self._extract_chunk(self.request)
82 search_q, order_by, order_dir = self._extract_ordering(self.request)
86 search_q, order_by, order_dir = self._extract_ordering(
87 self.request, column_map=column_map)
83
88
84 _render = self.request.get_partial_renderer(
89 _render = self.request.get_partial_renderer(
85 'data_table/_dt_elements.mako')
90 'data_table/_dt_elements.mako')
86
91
87 def user_actions(user_id, username):
92 def user_actions(user_id, username):
88 return _render("user_actions", user_id, username)
93 return _render("user_actions", user_id, username)
89
94
90 users_data_total_count = User.query()\
95 users_data_total_count = User.query()\
91 .filter(User.username != User.DEFAULT_USER) \
96 .filter(User.username != User.DEFAULT_USER) \
92 .count()
97 .count()
93
98
94 # json generate
99 # json generate
95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
100 base_q = User.query().filter(User.username != User.DEFAULT_USER)
96
101
97 if search_q:
102 if search_q:
98 like_expression = u'%{}%'.format(safe_unicode(search_q))
103 like_expression = u'%{}%'.format(safe_unicode(search_q))
99 base_q = base_q.filter(or_(
104 base_q = base_q.filter(or_(
100 User.username.ilike(like_expression),
105 User.username.ilike(like_expression),
101 User._email.ilike(like_expression),
106 User._email.ilike(like_expression),
102 User.name.ilike(like_expression),
107 User.name.ilike(like_expression),
103 User.lastname.ilike(like_expression),
108 User.lastname.ilike(like_expression),
104 ))
109 ))
105
110
106 users_data_total_filtered_count = base_q.count()
111 users_data_total_filtered_count = base_q.count()
107
112
108 sort_col = getattr(User, order_by, None)
113 sort_col = getattr(User, order_by, None)
109 if sort_col:
114 if sort_col:
110 if order_dir == 'asc':
115 if order_dir == 'asc':
111 # handle null values properly to order by NULL last
116 # handle null values properly to order by NULL last
112 if order_by in ['last_activity']:
117 if order_by in ['last_activity']:
113 sort_col = coalesce(sort_col, datetime.date.max)
118 sort_col = coalesce(sort_col, datetime.date.max)
114 sort_col = sort_col.asc()
119 sort_col = sort_col.asc()
115 else:
120 else:
116 # handle null values properly to order by NULL last
121 # handle null values properly to order by NULL last
117 if order_by in ['last_activity']:
122 if order_by in ['last_activity']:
118 sort_col = coalesce(sort_col, datetime.date.min)
123 sort_col = coalesce(sort_col, datetime.date.min)
119 sort_col = sort_col.desc()
124 sort_col = sort_col.desc()
120
125
121 base_q = base_q.order_by(sort_col)
126 base_q = base_q.order_by(sort_col)
122 base_q = base_q.offset(start).limit(limit)
127 base_q = base_q.offset(start).limit(limit)
123
128
124 users_list = base_q.all()
129 users_list = base_q.all()
125
130
126 users_data = []
131 users_data = []
127 for user in users_list:
132 for user in users_list:
128 users_data.append({
133 users_data.append({
129 "username": h.gravatar_with_user(self.request, user.username),
134 "username": h.gravatar_with_user(self.request, user.username),
130 "email": user.email,
135 "email": user.email,
131 "first_name": user.first_name,
136 "first_name": user.first_name,
132 "last_name": user.last_name,
137 "last_name": user.last_name,
133 "last_login": h.format_date(user.last_login),
138 "last_login": h.format_date(user.last_login),
134 "last_activity": h.format_date(user.last_activity),
139 "last_activity": h.format_date(user.last_activity),
135 "active": h.bool2icon(user.active),
140 "active": h.bool2icon(user.active),
136 "active_raw": user.active,
141 "active_raw": user.active,
137 "admin": h.bool2icon(user.admin),
142 "admin": h.bool2icon(user.admin),
138 "extern_type": user.extern_type,
143 "extern_type": user.extern_type,
139 "extern_name": user.extern_name,
144 "extern_name": user.extern_name,
140 "action": user_actions(user.user_id, user.username),
145 "action": user_actions(user.user_id, user.username),
141 })
146 })
142
147
143 data = ({
148 data = ({
144 'draw': draw,
149 'draw': draw,
145 'data': users_data,
150 'data': users_data,
146 'recordsTotal': users_data_total_count,
151 'recordsTotal': users_data_total_count,
147 'recordsFiltered': users_data_total_filtered_count,
152 'recordsFiltered': users_data_total_filtered_count,
148 })
153 })
149
154
150 return data
155 return data
151
156
152 @LoginRequired()
157 @LoginRequired()
153 @HasPermissionAllDecorator('hg.admin')
158 @HasPermissionAllDecorator('hg.admin')
154 @view_config(
159 @view_config(
155 route_name='edit_user_auth_tokens', request_method='GET',
160 route_name='edit_user_auth_tokens', request_method='GET',
156 renderer='rhodecode:templates/admin/users/user_edit.mako')
161 renderer='rhodecode:templates/admin/users/user_edit.mako')
157 def auth_tokens(self):
162 def auth_tokens(self):
158 _ = self.request.translate
163 _ = self.request.translate
159 c = self.load_default_context()
164 c = self.load_default_context()
160
165
161 user_id = self.request.matchdict.get('user_id')
166 user_id = self.request.matchdict.get('user_id')
162 c.user = User.get_or_404(user_id)
167 c.user = User.get_or_404(user_id)
163 self._redirect_for_default_user(c.user.username)
168 self._redirect_for_default_user(c.user.username)
164
169
165 c.active = 'auth_tokens'
170 c.active = 'auth_tokens'
166
171
167 c.lifetime_values = [
172 c.lifetime_values = [
168 (str(-1), _('forever')),
173 (str(-1), _('forever')),
169 (str(5), _('5 minutes')),
174 (str(5), _('5 minutes')),
170 (str(60), _('1 hour')),
175 (str(60), _('1 hour')),
171 (str(60 * 24), _('1 day')),
176 (str(60 * 24), _('1 day')),
172 (str(60 * 24 * 30), _('1 month')),
177 (str(60 * 24 * 30), _('1 month')),
173 ]
178 ]
174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
179 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
175 c.role_values = [
180 c.role_values = [
176 (x, AuthTokenModel.cls._get_role_name(x))
181 (x, AuthTokenModel.cls._get_role_name(x))
177 for x in AuthTokenModel.cls.ROLES]
182 for x in AuthTokenModel.cls.ROLES]
178 c.role_options = [(c.role_values, _("Role"))]
183 c.role_options = [(c.role_values, _("Role"))]
179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
184 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
180 c.user.user_id, show_expired=True)
185 c.user.user_id, show_expired=True)
181 return self._get_template_context(c)
186 return self._get_template_context(c)
182
187
183 def maybe_attach_token_scope(self, token):
188 def maybe_attach_token_scope(self, token):
184 # implemented in EE edition
189 # implemented in EE edition
185 pass
190 pass
186
191
187 @LoginRequired()
192 @LoginRequired()
188 @HasPermissionAllDecorator('hg.admin')
193 @HasPermissionAllDecorator('hg.admin')
189 @CSRFRequired()
194 @CSRFRequired()
190 @view_config(
195 @view_config(
191 route_name='edit_user_auth_tokens_add', request_method='POST')
196 route_name='edit_user_auth_tokens_add', request_method='POST')
192 def auth_tokens_add(self):
197 def auth_tokens_add(self):
193 _ = self.request.translate
198 _ = self.request.translate
194 c = self.load_default_context()
199 c = self.load_default_context()
195
200
196 user_id = self.request.matchdict.get('user_id')
201 user_id = self.request.matchdict.get('user_id')
197 c.user = User.get_or_404(user_id)
202 c.user = User.get_or_404(user_id)
198
203
199 self._redirect_for_default_user(c.user.username)
204 self._redirect_for_default_user(c.user.username)
200
205
201 user_data = c.user.get_api_data()
206 user_data = c.user.get_api_data()
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
207 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
203 description = self.request.POST.get('description')
208 description = self.request.POST.get('description')
204 role = self.request.POST.get('role')
209 role = self.request.POST.get('role')
205
210
206 token = AuthTokenModel().create(
211 token = AuthTokenModel().create(
207 c.user.user_id, description, lifetime, role)
212 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
213 token_data = token.get_api_data()
209
214
210 self.maybe_attach_token_scope(token)
215 self.maybe_attach_token_scope(token)
211 audit_logger.store_web(
216 audit_logger.store_web(
212 'user.edit.token.add', action_data={
217 'user.edit.token.add', action_data={
213 'data': {'token': token_data, 'user': user_data}},
218 'data': {'token': token_data, 'user': user_data}},
214 user=self._rhodecode_user, )
219 user=self._rhodecode_user, )
215 Session().commit()
220 Session().commit()
216
221
217 h.flash(_("Auth token successfully created"), category='success')
222 h.flash(_("Auth token successfully created"), category='success')
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
223 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
219
224
220 @LoginRequired()
225 @LoginRequired()
221 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
222 @CSRFRequired()
227 @CSRFRequired()
223 @view_config(
228 @view_config(
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
229 route_name='edit_user_auth_tokens_delete', request_method='POST')
225 def auth_tokens_delete(self):
230 def auth_tokens_delete(self):
226 _ = self.request.translate
231 _ = self.request.translate
227 c = self.load_default_context()
232 c = self.load_default_context()
228
233
229 user_id = self.request.matchdict.get('user_id')
234 user_id = self.request.matchdict.get('user_id')
230 c.user = User.get_or_404(user_id)
235 c.user = User.get_or_404(user_id)
231 self._redirect_for_default_user(c.user.username)
236 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
237 user_data = c.user.get_api_data()
233
238
234 del_auth_token = self.request.POST.get('del_auth_token')
239 del_auth_token = self.request.POST.get('del_auth_token')
235
240
236 if del_auth_token:
241 if del_auth_token:
237 token = UserApiKeys.get_or_404(del_auth_token)
242 token = UserApiKeys.get_or_404(del_auth_token)
238 token_data = token.get_api_data()
243 token_data = token.get_api_data()
239
244
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
245 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 audit_logger.store_web(
246 audit_logger.store_web(
242 'user.edit.token.delete', action_data={
247 'user.edit.token.delete', action_data={
243 'data': {'token': token_data, 'user': user_data}},
248 'data': {'token': token_data, 'user': user_data}},
244 user=self._rhodecode_user,)
249 user=self._rhodecode_user,)
245 Session().commit()
250 Session().commit()
246 h.flash(_("Auth token successfully deleted"), category='success')
251 h.flash(_("Auth token successfully deleted"), category='success')
247
252
248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
253 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
249
254
250 @LoginRequired()
255 @LoginRequired()
251 @HasPermissionAllDecorator('hg.admin')
256 @HasPermissionAllDecorator('hg.admin')
252 @view_config(
257 @view_config(
253 route_name='edit_user_emails', request_method='GET',
258 route_name='edit_user_emails', request_method='GET',
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
259 renderer='rhodecode:templates/admin/users/user_edit.mako')
255 def emails(self):
260 def emails(self):
256 _ = self.request.translate
261 _ = self.request.translate
257 c = self.load_default_context()
262 c = self.load_default_context()
258
263
259 user_id = self.request.matchdict.get('user_id')
264 user_id = self.request.matchdict.get('user_id')
260 c.user = User.get_or_404(user_id)
265 c.user = User.get_or_404(user_id)
261 self._redirect_for_default_user(c.user.username)
266 self._redirect_for_default_user(c.user.username)
262
267
263 c.active = 'emails'
268 c.active = 'emails'
264 c.user_email_map = UserEmailMap.query() \
269 c.user_email_map = UserEmailMap.query() \
265 .filter(UserEmailMap.user == c.user).all()
270 .filter(UserEmailMap.user == c.user).all()
266
271
267 return self._get_template_context(c)
272 return self._get_template_context(c)
268
273
269 @LoginRequired()
274 @LoginRequired()
270 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
271 @CSRFRequired()
276 @CSRFRequired()
272 @view_config(
277 @view_config(
273 route_name='edit_user_emails_add', request_method='POST')
278 route_name='edit_user_emails_add', request_method='POST')
274 def emails_add(self):
279 def emails_add(self):
275 _ = self.request.translate
280 _ = self.request.translate
276 c = self.load_default_context()
281 c = self.load_default_context()
277
282
278 user_id = self.request.matchdict.get('user_id')
283 user_id = self.request.matchdict.get('user_id')
279 c.user = User.get_or_404(user_id)
284 c.user = User.get_or_404(user_id)
280 self._redirect_for_default_user(c.user.username)
285 self._redirect_for_default_user(c.user.username)
281
286
282 email = self.request.POST.get('new_email')
287 email = self.request.POST.get('new_email')
283 user_data = c.user.get_api_data()
288 user_data = c.user.get_api_data()
284 try:
289 try:
285 UserModel().add_extra_email(c.user.user_id, email)
290 UserModel().add_extra_email(c.user.user_id, email)
286 audit_logger.store_web(
291 audit_logger.store_web(
287 'user.edit.email.add', action_data={'email': email, 'user': user_data},
292 'user.edit.email.add', action_data={'email': email, 'user': user_data},
288 user=self._rhodecode_user)
293 user=self._rhodecode_user)
289 Session().commit()
294 Session().commit()
290 h.flash(_("Added new email address `%s` for user account") % email,
295 h.flash(_("Added new email address `%s` for user account") % email,
291 category='success')
296 category='success')
292 except formencode.Invalid as error:
297 except formencode.Invalid as error:
293 h.flash(h.escape(error.error_dict['email']), category='error')
298 h.flash(h.escape(error.error_dict['email']), category='error')
294 except Exception:
299 except Exception:
295 log.exception("Exception during email saving")
300 log.exception("Exception during email saving")
296 h.flash(_('An error occurred during email saving'),
301 h.flash(_('An error occurred during email saving'),
297 category='error')
302 category='error')
298 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
303 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
299
304
300 @LoginRequired()
305 @LoginRequired()
301 @HasPermissionAllDecorator('hg.admin')
306 @HasPermissionAllDecorator('hg.admin')
302 @CSRFRequired()
307 @CSRFRequired()
303 @view_config(
308 @view_config(
304 route_name='edit_user_emails_delete', request_method='POST')
309 route_name='edit_user_emails_delete', request_method='POST')
305 def emails_delete(self):
310 def emails_delete(self):
306 _ = self.request.translate
311 _ = self.request.translate
307 c = self.load_default_context()
312 c = self.load_default_context()
308
313
309 user_id = self.request.matchdict.get('user_id')
314 user_id = self.request.matchdict.get('user_id')
310 c.user = User.get_or_404(user_id)
315 c.user = User.get_or_404(user_id)
311 self._redirect_for_default_user(c.user.username)
316 self._redirect_for_default_user(c.user.username)
312
317
313 email_id = self.request.POST.get('del_email_id')
318 email_id = self.request.POST.get('del_email_id')
314 user_model = UserModel()
319 user_model = UserModel()
315
320
316 email = UserEmailMap.query().get(email_id).email
321 email = UserEmailMap.query().get(email_id).email
317 user_data = c.user.get_api_data()
322 user_data = c.user.get_api_data()
318 user_model.delete_extra_email(c.user.user_id, email_id)
323 user_model.delete_extra_email(c.user.user_id, email_id)
319 audit_logger.store_web(
324 audit_logger.store_web(
320 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
325 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
321 user=self._rhodecode_user)
326 user=self._rhodecode_user)
322 Session().commit()
327 Session().commit()
323 h.flash(_("Removed email address from user account"),
328 h.flash(_("Removed email address from user account"),
324 category='success')
329 category='success')
325 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
330 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
326
331
327 @LoginRequired()
332 @LoginRequired()
328 @HasPermissionAllDecorator('hg.admin')
333 @HasPermissionAllDecorator('hg.admin')
329 @view_config(
334 @view_config(
330 route_name='edit_user_ips', request_method='GET',
335 route_name='edit_user_ips', request_method='GET',
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
336 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 def ips(self):
337 def ips(self):
333 _ = self.request.translate
338 _ = self.request.translate
334 c = self.load_default_context()
339 c = self.load_default_context()
335
340
336 user_id = self.request.matchdict.get('user_id')
341 user_id = self.request.matchdict.get('user_id')
337 c.user = User.get_or_404(user_id)
342 c.user = User.get_or_404(user_id)
338 self._redirect_for_default_user(c.user.username)
343 self._redirect_for_default_user(c.user.username)
339
344
340 c.active = 'ips'
345 c.active = 'ips'
341 c.user_ip_map = UserIpMap.query() \
346 c.user_ip_map = UserIpMap.query() \
342 .filter(UserIpMap.user == c.user).all()
347 .filter(UserIpMap.user == c.user).all()
343
348
344 c.inherit_default_ips = c.user.inherit_default_permissions
349 c.inherit_default_ips = c.user.inherit_default_permissions
345 c.default_user_ip_map = UserIpMap.query() \
350 c.default_user_ip_map = UserIpMap.query() \
346 .filter(UserIpMap.user == User.get_default_user()).all()
351 .filter(UserIpMap.user == User.get_default_user()).all()
347
352
348 return self._get_template_context(c)
353 return self._get_template_context(c)
349
354
350 @LoginRequired()
355 @LoginRequired()
351 @HasPermissionAllDecorator('hg.admin')
356 @HasPermissionAllDecorator('hg.admin')
352 @CSRFRequired()
357 @CSRFRequired()
353 @view_config(
358 @view_config(
354 route_name='edit_user_ips_add', request_method='POST')
359 route_name='edit_user_ips_add', request_method='POST')
355 def ips_add(self):
360 def ips_add(self):
356 _ = self.request.translate
361 _ = self.request.translate
357 c = self.load_default_context()
362 c = self.load_default_context()
358
363
359 user_id = self.request.matchdict.get('user_id')
364 user_id = self.request.matchdict.get('user_id')
360 c.user = User.get_or_404(user_id)
365 c.user = User.get_or_404(user_id)
361 # NOTE(marcink): this view is allowed for default users, as we can
366 # NOTE(marcink): this view is allowed for default users, as we can
362 # edit their IP white list
367 # edit their IP white list
363
368
364 user_model = UserModel()
369 user_model = UserModel()
365 desc = self.request.POST.get('description')
370 desc = self.request.POST.get('description')
366 try:
371 try:
367 ip_list = user_model.parse_ip_range(
372 ip_list = user_model.parse_ip_range(
368 self.request.POST.get('new_ip'))
373 self.request.POST.get('new_ip'))
369 except Exception as e:
374 except Exception as e:
370 ip_list = []
375 ip_list = []
371 log.exception("Exception during ip saving")
376 log.exception("Exception during ip saving")
372 h.flash(_('An error occurred during ip saving:%s' % (e,)),
377 h.flash(_('An error occurred during ip saving:%s' % (e,)),
373 category='error')
378 category='error')
374 added = []
379 added = []
375 user_data = c.user.get_api_data()
380 user_data = c.user.get_api_data()
376 for ip in ip_list:
381 for ip in ip_list:
377 try:
382 try:
378 user_model.add_extra_ip(c.user.user_id, ip, desc)
383 user_model.add_extra_ip(c.user.user_id, ip, desc)
379 audit_logger.store_web(
384 audit_logger.store_web(
380 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
385 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
381 user=self._rhodecode_user)
386 user=self._rhodecode_user)
382 Session().commit()
387 Session().commit()
383 added.append(ip)
388 added.append(ip)
384 except formencode.Invalid as error:
389 except formencode.Invalid as error:
385 msg = error.error_dict['ip']
390 msg = error.error_dict['ip']
386 h.flash(msg, category='error')
391 h.flash(msg, category='error')
387 except Exception:
392 except Exception:
388 log.exception("Exception during ip saving")
393 log.exception("Exception during ip saving")
389 h.flash(_('An error occurred during ip saving'),
394 h.flash(_('An error occurred during ip saving'),
390 category='error')
395 category='error')
391 if added:
396 if added:
392 h.flash(
397 h.flash(
393 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
398 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
394 category='success')
399 category='success')
395 if 'default_user' in self.request.POST:
400 if 'default_user' in self.request.POST:
396 # case for editing global IP list we do it for 'DEFAULT' user
401 # case for editing global IP list we do it for 'DEFAULT' user
397 raise HTTPFound(h.route_path('admin_permissions_ips'))
402 raise HTTPFound(h.route_path('admin_permissions_ips'))
398 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
403 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
399
404
400 @LoginRequired()
405 @LoginRequired()
401 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
402 @CSRFRequired()
407 @CSRFRequired()
403 @view_config(
408 @view_config(
404 route_name='edit_user_ips_delete', request_method='POST')
409 route_name='edit_user_ips_delete', request_method='POST')
405 def ips_delete(self):
410 def ips_delete(self):
406 _ = self.request.translate
411 _ = self.request.translate
407 c = self.load_default_context()
412 c = self.load_default_context()
408
413
409 user_id = self.request.matchdict.get('user_id')
414 user_id = self.request.matchdict.get('user_id')
410 c.user = User.get_or_404(user_id)
415 c.user = User.get_or_404(user_id)
411 # NOTE(marcink): this view is allowed for default users, as we can
416 # NOTE(marcink): this view is allowed for default users, as we can
412 # edit their IP white list
417 # edit their IP white list
413
418
414 ip_id = self.request.POST.get('del_ip_id')
419 ip_id = self.request.POST.get('del_ip_id')
415 user_model = UserModel()
420 user_model = UserModel()
416 user_data = c.user.get_api_data()
421 user_data = c.user.get_api_data()
417 ip = UserIpMap.query().get(ip_id).ip_addr
422 ip = UserIpMap.query().get(ip_id).ip_addr
418 user_model.delete_extra_ip(c.user.user_id, ip_id)
423 user_model.delete_extra_ip(c.user.user_id, ip_id)
419 audit_logger.store_web(
424 audit_logger.store_web(
420 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
425 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
421 user=self._rhodecode_user)
426 user=self._rhodecode_user)
422 Session().commit()
427 Session().commit()
423 h.flash(_("Removed ip address from user whitelist"), category='success')
428 h.flash(_("Removed ip address from user whitelist"), category='success')
424
429
425 if 'default_user' in self.request.POST:
430 if 'default_user' in self.request.POST:
426 # case for editing global IP list we do it for 'DEFAULT' user
431 # case for editing global IP list we do it for 'DEFAULT' user
427 raise HTTPFound(h.route_path('admin_permissions_ips'))
432 raise HTTPFound(h.route_path('admin_permissions_ips'))
428 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
433 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
429
434
430 @LoginRequired()
435 @LoginRequired()
431 @HasPermissionAllDecorator('hg.admin')
436 @HasPermissionAllDecorator('hg.admin')
432 @view_config(
437 @view_config(
433 route_name='edit_user_groups_management', request_method='GET',
438 route_name='edit_user_groups_management', request_method='GET',
434 renderer='rhodecode:templates/admin/users/user_edit.mako')
439 renderer='rhodecode:templates/admin/users/user_edit.mako')
435 def groups_management(self):
440 def groups_management(self):
436 c = self.load_default_context()
441 c = self.load_default_context()
437
442
438 user_id = self.request.matchdict.get('user_id')
443 user_id = self.request.matchdict.get('user_id')
439 c.user = User.get_or_404(user_id)
444 c.user = User.get_or_404(user_id)
440 c.data = c.user.group_member
445 c.data = c.user.group_member
441 self._redirect_for_default_user(c.user.username)
446 self._redirect_for_default_user(c.user.username)
442 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
447 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
443 for group in c.user.group_member]
448 for group in c.user.group_member]
444 c.groups = json.dumps(groups)
449 c.groups = json.dumps(groups)
445 c.active = 'groups'
450 c.active = 'groups'
446
451
447 return self._get_template_context(c)
452 return self._get_template_context(c)
448
453
449 @LoginRequired()
454 @LoginRequired()
450 @HasPermissionAllDecorator('hg.admin')
455 @HasPermissionAllDecorator('hg.admin')
451 @CSRFRequired()
456 @CSRFRequired()
452 @view_config(
457 @view_config(
453 route_name='edit_user_groups_management_updates', request_method='POST')
458 route_name='edit_user_groups_management_updates', request_method='POST')
454 def groups_management_updates(self):
459 def groups_management_updates(self):
455 _ = self.request.translate
460 _ = self.request.translate
456 c = self.load_default_context()
461 c = self.load_default_context()
457
462
458 user_id = self.request.matchdict.get('user_id')
463 user_id = self.request.matchdict.get('user_id')
459 c.user = User.get_or_404(user_id)
464 c.user = User.get_or_404(user_id)
460 self._redirect_for_default_user(c.user.username)
465 self._redirect_for_default_user(c.user.username)
461
466
462 users_groups = set(self.request.POST.getall('users_group_id'))
467 users_groups = set(self.request.POST.getall('users_group_id'))
463 users_groups_model = []
468 users_groups_model = []
464
469
465 for ugid in users_groups:
470 for ugid in users_groups:
466 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
471 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
467 user_group_model = UserGroupModel()
472 user_group_model = UserGroupModel()
468 user_group_model.change_groups(c.user, users_groups_model)
473 user_group_model.change_groups(c.user, users_groups_model)
469
474
470 Session().commit()
475 Session().commit()
471 c.active = 'user_groups_management'
476 c.active = 'user_groups_management'
472 h.flash(_("Groups successfully changed"), category='success')
477 h.flash(_("Groups successfully changed"), category='success')
473
478
474 return HTTPFound(h.route_path(
479 return HTTPFound(h.route_path(
475 'edit_user_groups_management', user_id=user_id))
480 'edit_user_groups_management', user_id=user_id))
476
481
477 @LoginRequired()
482 @LoginRequired()
478 @HasPermissionAllDecorator('hg.admin')
483 @HasPermissionAllDecorator('hg.admin')
479 @view_config(
484 @view_config(
480 route_name='edit_user_audit_logs', request_method='GET',
485 route_name='edit_user_audit_logs', request_method='GET',
481 renderer='rhodecode:templates/admin/users/user_edit.mako')
486 renderer='rhodecode:templates/admin/users/user_edit.mako')
482 def user_audit_logs(self):
487 def user_audit_logs(self):
483 _ = self.request.translate
488 _ = self.request.translate
484 c = self.load_default_context()
489 c = self.load_default_context()
485
490
486 user_id = self.request.matchdict.get('user_id')
491 user_id = self.request.matchdict.get('user_id')
487 c.user = User.get_or_404(user_id)
492 c.user = User.get_or_404(user_id)
488 self._redirect_for_default_user(c.user.username)
493 self._redirect_for_default_user(c.user.username)
489 c.active = 'audit'
494 c.active = 'audit'
490
495
491 p = safe_int(self.request.GET.get('page', 1), 1)
496 p = safe_int(self.request.GET.get('page', 1), 1)
492
497
493 filter_term = self.request.GET.get('filter')
498 filter_term = self.request.GET.get('filter')
494 user_log = UserModel().get_user_log(c.user, filter_term)
499 user_log = UserModel().get_user_log(c.user, filter_term)
495
500
496 def url_generator(**kw):
501 def url_generator(**kw):
497 if filter_term:
502 if filter_term:
498 kw['filter'] = filter_term
503 kw['filter'] = filter_term
499 return self.request.current_route_path(_query=kw)
504 return self.request.current_route_path(_query=kw)
500
505
501 c.audit_logs = h.Page(
506 c.audit_logs = h.Page(
502 user_log, page=p, items_per_page=10, url=url_generator)
507 user_log, page=p, items_per_page=10, url=url_generator)
503 c.filter_term = filter_term
508 c.filter_term = filter_term
504 return self._get_template_context(c)
509 return self._get_template_context(c)
505
510
@@ -1,521 +1,515 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 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY ROUTES
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
194 requirements=URL_NAME_REQUIREMENTS)
195
195
196 # ADMIN REPOSITORY GROUPS ROUTES
196 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
198 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
199 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
200 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
201 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
202 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
203 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
204 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
206 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
207 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
208 requirements=URL_NAME_REQUIREMENTS)
209
209
210 # EXTRAS REPO GROUP ROUTES
210 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
212 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
213 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
214 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
216 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
217 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
218 requirements=URL_NAME_REQUIREMENTS)
219
219
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
221 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
222 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
225 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
226 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
228
228
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
230 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
231 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
232 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
234 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
235 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
236 requirements=URL_NAME_REQUIREMENTS)
237
237
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
239 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
240 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN USER ROUTES
243 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
245 controller='admin/users') as m:
246 m.connect('users', '/users',
246 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
248 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
249 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
250 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
251 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
252 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
253 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
254 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
256 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
257 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
259 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
261 action='create_personal_repo_group', conditions={'method': ['POST']})
262
262
263 # EXTRAS USER ROUTES
263 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
265 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
267 action='update_advanced', conditions={'method': ['PUT']})
268
268
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
270 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
272 action='update_global_perms', conditions={'method': ['PUT']})
273
273
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 action='edit_perms_summary', conditions={'method': ['GET']})
275 action='edit_perms_summary', conditions={'method': ['GET']})
276
276
277 # ADMIN USER GROUPS REST ROUTES
277 # ADMIN USER GROUPS REST ROUTES
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 controller='admin/user_groups') as m:
279 controller='admin/user_groups') as m:
280 m.connect('users_groups', '/user_groups',
280 m.connect('users_groups', '/user_groups',
281 action='create', conditions={'method': ['POST']})
281 action='create', conditions={'method': ['POST']})
282 m.connect('users_groups', '/user_groups',
283 action='index', conditions={'method': ['GET']})
284 m.connect('new_users_group', '/user_groups/new',
282 m.connect('new_users_group', '/user_groups/new',
285 action='new', conditions={'method': ['GET']})
283 action='new', conditions={'method': ['GET']})
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
284 m.connect('update_users_group', '/user_groups/{user_group_id}',
287 action='update', conditions={'method': ['PUT']})
285 action='update', conditions={'method': ['PUT']})
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
286 m.connect('delete_users_group', '/user_groups/{user_group_id}',
289 action='delete', conditions={'method': ['DELETE']})
287 action='delete', conditions={'method': ['DELETE']})
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
288 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
291 action='edit', conditions={'method': ['GET']},
289 action='edit', conditions={'method': ['GET']},
292 function=check_user_group)
290 function=check_user_group)
293
291
294 # EXTRAS USER GROUP ROUTES
292 # EXTRAS USER GROUP ROUTES
295 m.connect('edit_user_group_global_perms',
293 m.connect('edit_user_group_global_perms',
296 '/user_groups/{user_group_id}/edit/global_permissions',
294 '/user_groups/{user_group_id}/edit/global_permissions',
297 action='edit_global_perms', conditions={'method': ['GET']})
295 action='edit_global_perms', conditions={'method': ['GET']})
298 m.connect('edit_user_group_global_perms',
296 m.connect('edit_user_group_global_perms',
299 '/user_groups/{user_group_id}/edit/global_permissions',
297 '/user_groups/{user_group_id}/edit/global_permissions',
300 action='update_global_perms', conditions={'method': ['PUT']})
298 action='update_global_perms', conditions={'method': ['PUT']})
301 m.connect('edit_user_group_perms_summary',
299 m.connect('edit_user_group_perms_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
300 '/user_groups/{user_group_id}/edit/permissions_summary',
303 action='edit_perms_summary', conditions={'method': ['GET']})
301 action='edit_perms_summary', conditions={'method': ['GET']})
304
302
305 m.connect('edit_user_group_perms',
303 m.connect('edit_user_group_perms',
306 '/user_groups/{user_group_id}/edit/permissions',
304 '/user_groups/{user_group_id}/edit/permissions',
307 action='edit_perms', conditions={'method': ['GET']})
305 action='edit_perms', conditions={'method': ['GET']})
308 m.connect('edit_user_group_perms',
306 m.connect('edit_user_group_perms',
309 '/user_groups/{user_group_id}/edit/permissions',
307 '/user_groups/{user_group_id}/edit/permissions',
310 action='update_perms', conditions={'method': ['PUT']})
308 action='update_perms', conditions={'method': ['PUT']})
311
309
312 m.connect('edit_user_group_advanced',
310 m.connect('edit_user_group_advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
311 '/user_groups/{user_group_id}/edit/advanced',
314 action='edit_advanced', conditions={'method': ['GET']})
312 action='edit_advanced', conditions={'method': ['GET']})
315
313
316 m.connect('edit_user_group_advanced_sync',
314 m.connect('edit_user_group_advanced_sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
315 '/user_groups/{user_group_id}/edit/advanced/sync',
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
316 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
319
317
320 m.connect('edit_user_group_members',
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 action='user_group_members', conditions={'method': ['GET']})
323
324 # ADMIN DEFAULTS REST ROUTES
318 # ADMIN DEFAULTS REST ROUTES
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
319 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 controller='admin/defaults') as m:
320 controller='admin/defaults') as m:
327 m.connect('admin_defaults_repositories', '/defaults/repositories',
321 m.connect('admin_defaults_repositories', '/defaults/repositories',
328 action='update_repository_defaults', conditions={'method': ['POST']})
322 action='update_repository_defaults', conditions={'method': ['POST']})
329 m.connect('admin_defaults_repositories', '/defaults/repositories',
323 m.connect('admin_defaults_repositories', '/defaults/repositories',
330 action='index', conditions={'method': ['GET']})
324 action='index', conditions={'method': ['GET']})
331
325
332 # ADMIN SETTINGS ROUTES
326 # ADMIN SETTINGS ROUTES
333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
334 controller='admin/settings') as m:
328 controller='admin/settings') as m:
335
329
336 # default
330 # default
337 m.connect('admin_settings', '/settings',
331 m.connect('admin_settings', '/settings',
338 action='settings_global_update',
332 action='settings_global_update',
339 conditions={'method': ['POST']})
333 conditions={'method': ['POST']})
340 m.connect('admin_settings', '/settings',
334 m.connect('admin_settings', '/settings',
341 action='settings_global', conditions={'method': ['GET']})
335 action='settings_global', conditions={'method': ['GET']})
342
336
343 m.connect('admin_settings_vcs', '/settings/vcs',
337 m.connect('admin_settings_vcs', '/settings/vcs',
344 action='settings_vcs_update',
338 action='settings_vcs_update',
345 conditions={'method': ['POST']})
339 conditions={'method': ['POST']})
346 m.connect('admin_settings_vcs', '/settings/vcs',
340 m.connect('admin_settings_vcs', '/settings/vcs',
347 action='settings_vcs',
341 action='settings_vcs',
348 conditions={'method': ['GET']})
342 conditions={'method': ['GET']})
349 m.connect('admin_settings_vcs', '/settings/vcs',
343 m.connect('admin_settings_vcs', '/settings/vcs',
350 action='delete_svn_pattern',
344 action='delete_svn_pattern',
351 conditions={'method': ['DELETE']})
345 conditions={'method': ['DELETE']})
352
346
353 m.connect('admin_settings_mapping', '/settings/mapping',
347 m.connect('admin_settings_mapping', '/settings/mapping',
354 action='settings_mapping_update',
348 action='settings_mapping_update',
355 conditions={'method': ['POST']})
349 conditions={'method': ['POST']})
356 m.connect('admin_settings_mapping', '/settings/mapping',
350 m.connect('admin_settings_mapping', '/settings/mapping',
357 action='settings_mapping', conditions={'method': ['GET']})
351 action='settings_mapping', conditions={'method': ['GET']})
358
352
359 m.connect('admin_settings_global', '/settings/global',
353 m.connect('admin_settings_global', '/settings/global',
360 action='settings_global_update',
354 action='settings_global_update',
361 conditions={'method': ['POST']})
355 conditions={'method': ['POST']})
362 m.connect('admin_settings_global', '/settings/global',
356 m.connect('admin_settings_global', '/settings/global',
363 action='settings_global', conditions={'method': ['GET']})
357 action='settings_global', conditions={'method': ['GET']})
364
358
365 m.connect('admin_settings_visual', '/settings/visual',
359 m.connect('admin_settings_visual', '/settings/visual',
366 action='settings_visual_update',
360 action='settings_visual_update',
367 conditions={'method': ['POST']})
361 conditions={'method': ['POST']})
368 m.connect('admin_settings_visual', '/settings/visual',
362 m.connect('admin_settings_visual', '/settings/visual',
369 action='settings_visual', conditions={'method': ['GET']})
363 action='settings_visual', conditions={'method': ['GET']})
370
364
371 m.connect('admin_settings_issuetracker',
365 m.connect('admin_settings_issuetracker',
372 '/settings/issue-tracker', action='settings_issuetracker',
366 '/settings/issue-tracker', action='settings_issuetracker',
373 conditions={'method': ['GET']})
367 conditions={'method': ['GET']})
374 m.connect('admin_settings_issuetracker_save',
368 m.connect('admin_settings_issuetracker_save',
375 '/settings/issue-tracker/save',
369 '/settings/issue-tracker/save',
376 action='settings_issuetracker_save',
370 action='settings_issuetracker_save',
377 conditions={'method': ['POST']})
371 conditions={'method': ['POST']})
378 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
372 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
379 action='settings_issuetracker_test',
373 action='settings_issuetracker_test',
380 conditions={'method': ['POST']})
374 conditions={'method': ['POST']})
381 m.connect('admin_issuetracker_delete',
375 m.connect('admin_issuetracker_delete',
382 '/settings/issue-tracker/delete',
376 '/settings/issue-tracker/delete',
383 action='settings_issuetracker_delete',
377 action='settings_issuetracker_delete',
384 conditions={'method': ['DELETE']})
378 conditions={'method': ['DELETE']})
385
379
386 m.connect('admin_settings_email', '/settings/email',
380 m.connect('admin_settings_email', '/settings/email',
387 action='settings_email_update',
381 action='settings_email_update',
388 conditions={'method': ['POST']})
382 conditions={'method': ['POST']})
389 m.connect('admin_settings_email', '/settings/email',
383 m.connect('admin_settings_email', '/settings/email',
390 action='settings_email', conditions={'method': ['GET']})
384 action='settings_email', conditions={'method': ['GET']})
391
385
392 m.connect('admin_settings_hooks', '/settings/hooks',
386 m.connect('admin_settings_hooks', '/settings/hooks',
393 action='settings_hooks_update',
387 action='settings_hooks_update',
394 conditions={'method': ['POST', 'DELETE']})
388 conditions={'method': ['POST', 'DELETE']})
395 m.connect('admin_settings_hooks', '/settings/hooks',
389 m.connect('admin_settings_hooks', '/settings/hooks',
396 action='settings_hooks', conditions={'method': ['GET']})
390 action='settings_hooks', conditions={'method': ['GET']})
397
391
398 m.connect('admin_settings_search', '/settings/search',
392 m.connect('admin_settings_search', '/settings/search',
399 action='settings_search', conditions={'method': ['GET']})
393 action='settings_search', conditions={'method': ['GET']})
400
394
401 m.connect('admin_settings_supervisor', '/settings/supervisor',
395 m.connect('admin_settings_supervisor', '/settings/supervisor',
402 action='settings_supervisor', conditions={'method': ['GET']})
396 action='settings_supervisor', conditions={'method': ['GET']})
403 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
397 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
404 action='settings_supervisor_log', conditions={'method': ['GET']})
398 action='settings_supervisor_log', conditions={'method': ['GET']})
405
399
406 m.connect('admin_settings_labs', '/settings/labs',
400 m.connect('admin_settings_labs', '/settings/labs',
407 action='settings_labs_update',
401 action='settings_labs_update',
408 conditions={'method': ['POST']})
402 conditions={'method': ['POST']})
409 m.connect('admin_settings_labs', '/settings/labs',
403 m.connect('admin_settings_labs', '/settings/labs',
410 action='settings_labs', conditions={'method': ['GET']})
404 action='settings_labs', conditions={'method': ['GET']})
411
405
412 # ADMIN MY ACCOUNT
406 # ADMIN MY ACCOUNT
413 with rmap.submapper(path_prefix=ADMIN_PREFIX,
407 with rmap.submapper(path_prefix=ADMIN_PREFIX,
414 controller='admin/my_account') as m:
408 controller='admin/my_account') as m:
415
409
416 # NOTE(marcink): this needs to be kept for password force flag to be
410 # NOTE(marcink): this needs to be kept for password force flag to be
417 # handled in pylons controllers, remove after full migration to pyramid
411 # handled in pylons controllers, remove after full migration to pyramid
418 m.connect('my_account_password', '/my_account/password',
412 m.connect('my_account_password', '/my_account/password',
419 action='my_account_password', conditions={'method': ['GET']})
413 action='my_account_password', conditions={'method': ['GET']})
420
414
421 #==========================================================================
415 #==========================================================================
422 # REPOSITORY ROUTES
416 # REPOSITORY ROUTES
423 #==========================================================================
417 #==========================================================================
424
418
425 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
419 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
426 controller='admin/repos', action='repo_creating',
420 controller='admin/repos', action='repo_creating',
427 requirements=URL_NAME_REQUIREMENTS)
421 requirements=URL_NAME_REQUIREMENTS)
428 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
422 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
429 controller='admin/repos', action='repo_check',
423 controller='admin/repos', action='repo_check',
430 requirements=URL_NAME_REQUIREMENTS)
424 requirements=URL_NAME_REQUIREMENTS)
431
425
432 # repo edit options
426 # repo edit options
433 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
427 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
434 controller='admin/repos', action='edit_fields',
428 controller='admin/repos', action='edit_fields',
435 conditions={'method': ['GET'], 'function': check_repo},
429 conditions={'method': ['GET'], 'function': check_repo},
436 requirements=URL_NAME_REQUIREMENTS)
430 requirements=URL_NAME_REQUIREMENTS)
437 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
431 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
438 controller='admin/repos', action='create_repo_field',
432 controller='admin/repos', action='create_repo_field',
439 conditions={'method': ['PUT'], 'function': check_repo},
433 conditions={'method': ['PUT'], 'function': check_repo},
440 requirements=URL_NAME_REQUIREMENTS)
434 requirements=URL_NAME_REQUIREMENTS)
441 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
435 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
442 controller='admin/repos', action='delete_repo_field',
436 controller='admin/repos', action='delete_repo_field',
443 conditions={'method': ['DELETE'], 'function': check_repo},
437 conditions={'method': ['DELETE'], 'function': check_repo},
444 requirements=URL_NAME_REQUIREMENTS)
438 requirements=URL_NAME_REQUIREMENTS)
445
439
446 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
440 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
447 controller='admin/repos', action='toggle_locking',
441 controller='admin/repos', action='toggle_locking',
448 conditions={'method': ['GET'], 'function': check_repo},
442 conditions={'method': ['GET'], 'function': check_repo},
449 requirements=URL_NAME_REQUIREMENTS)
443 requirements=URL_NAME_REQUIREMENTS)
450
444
451 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
445 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
452 controller='admin/repos', action='edit_remote_form',
446 controller='admin/repos', action='edit_remote_form',
453 conditions={'method': ['GET'], 'function': check_repo},
447 conditions={'method': ['GET'], 'function': check_repo},
454 requirements=URL_NAME_REQUIREMENTS)
448 requirements=URL_NAME_REQUIREMENTS)
455 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
449 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
456 controller='admin/repos', action='edit_remote',
450 controller='admin/repos', action='edit_remote',
457 conditions={'method': ['PUT'], 'function': check_repo},
451 conditions={'method': ['PUT'], 'function': check_repo},
458 requirements=URL_NAME_REQUIREMENTS)
452 requirements=URL_NAME_REQUIREMENTS)
459
453
460 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
454 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
461 controller='admin/repos', action='edit_statistics_form',
455 controller='admin/repos', action='edit_statistics_form',
462 conditions={'method': ['GET'], 'function': check_repo},
456 conditions={'method': ['GET'], 'function': check_repo},
463 requirements=URL_NAME_REQUIREMENTS)
457 requirements=URL_NAME_REQUIREMENTS)
464 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
458 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
465 controller='admin/repos', action='edit_statistics',
459 controller='admin/repos', action='edit_statistics',
466 conditions={'method': ['PUT'], 'function': check_repo},
460 conditions={'method': ['PUT'], 'function': check_repo},
467 requirements=URL_NAME_REQUIREMENTS)
461 requirements=URL_NAME_REQUIREMENTS)
468 rmap.connect('repo_settings_issuetracker',
462 rmap.connect('repo_settings_issuetracker',
469 '/{repo_name}/settings/issue-tracker',
463 '/{repo_name}/settings/issue-tracker',
470 controller='admin/repos', action='repo_issuetracker',
464 controller='admin/repos', action='repo_issuetracker',
471 conditions={'method': ['GET'], 'function': check_repo},
465 conditions={'method': ['GET'], 'function': check_repo},
472 requirements=URL_NAME_REQUIREMENTS)
466 requirements=URL_NAME_REQUIREMENTS)
473 rmap.connect('repo_issuetracker_test',
467 rmap.connect('repo_issuetracker_test',
474 '/{repo_name}/settings/issue-tracker/test',
468 '/{repo_name}/settings/issue-tracker/test',
475 controller='admin/repos', action='repo_issuetracker_test',
469 controller='admin/repos', action='repo_issuetracker_test',
476 conditions={'method': ['POST'], 'function': check_repo},
470 conditions={'method': ['POST'], 'function': check_repo},
477 requirements=URL_NAME_REQUIREMENTS)
471 requirements=URL_NAME_REQUIREMENTS)
478 rmap.connect('repo_issuetracker_delete',
472 rmap.connect('repo_issuetracker_delete',
479 '/{repo_name}/settings/issue-tracker/delete',
473 '/{repo_name}/settings/issue-tracker/delete',
480 controller='admin/repos', action='repo_issuetracker_delete',
474 controller='admin/repos', action='repo_issuetracker_delete',
481 conditions={'method': ['DELETE'], 'function': check_repo},
475 conditions={'method': ['DELETE'], 'function': check_repo},
482 requirements=URL_NAME_REQUIREMENTS)
476 requirements=URL_NAME_REQUIREMENTS)
483 rmap.connect('repo_issuetracker_save',
477 rmap.connect('repo_issuetracker_save',
484 '/{repo_name}/settings/issue-tracker/save',
478 '/{repo_name}/settings/issue-tracker/save',
485 controller='admin/repos', action='repo_issuetracker_save',
479 controller='admin/repos', action='repo_issuetracker_save',
486 conditions={'method': ['POST'], 'function': check_repo},
480 conditions={'method': ['POST'], 'function': check_repo},
487 requirements=URL_NAME_REQUIREMENTS)
481 requirements=URL_NAME_REQUIREMENTS)
488 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
482 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
489 controller='admin/repos', action='repo_settings_vcs_update',
483 controller='admin/repos', action='repo_settings_vcs_update',
490 conditions={'method': ['POST'], 'function': check_repo},
484 conditions={'method': ['POST'], 'function': check_repo},
491 requirements=URL_NAME_REQUIREMENTS)
485 requirements=URL_NAME_REQUIREMENTS)
492 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
486 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
493 controller='admin/repos', action='repo_settings_vcs',
487 controller='admin/repos', action='repo_settings_vcs',
494 conditions={'method': ['GET'], 'function': check_repo},
488 conditions={'method': ['GET'], 'function': check_repo},
495 requirements=URL_NAME_REQUIREMENTS)
489 requirements=URL_NAME_REQUIREMENTS)
496 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
490 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
497 controller='admin/repos', action='repo_delete_svn_pattern',
491 controller='admin/repos', action='repo_delete_svn_pattern',
498 conditions={'method': ['DELETE'], 'function': check_repo},
492 conditions={'method': ['DELETE'], 'function': check_repo},
499 requirements=URL_NAME_REQUIREMENTS)
493 requirements=URL_NAME_REQUIREMENTS)
500 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
494 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
501 controller='admin/repos', action='repo_settings_pullrequest',
495 controller='admin/repos', action='repo_settings_pullrequest',
502 conditions={'method': ['GET', 'POST'], 'function': check_repo},
496 conditions={'method': ['GET', 'POST'], 'function': check_repo},
503 requirements=URL_NAME_REQUIREMENTS)
497 requirements=URL_NAME_REQUIREMENTS)
504
498
505
499
506 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
500 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
507 controller='forks', action='fork_create',
501 controller='forks', action='fork_create',
508 conditions={'function': check_repo, 'method': ['POST']},
502 conditions={'function': check_repo, 'method': ['POST']},
509 requirements=URL_NAME_REQUIREMENTS)
503 requirements=URL_NAME_REQUIREMENTS)
510
504
511 rmap.connect('repo_fork_home', '/{repo_name}/fork',
505 rmap.connect('repo_fork_home', '/{repo_name}/fork',
512 controller='forks', action='fork',
506 controller='forks', action='fork',
513 conditions={'function': check_repo},
507 conditions={'function': check_repo},
514 requirements=URL_NAME_REQUIREMENTS)
508 requirements=URL_NAME_REQUIREMENTS)
515
509
516 rmap.connect('repo_forks_home', '/{repo_name}/forks',
510 rmap.connect('repo_forks_home', '/{repo_name}/forks',
517 controller='forks', action='forks',
511 controller='forks', action='forks',
518 conditions={'function': check_repo},
512 conditions={'function': check_repo},
519 requirements=URL_NAME_REQUIREMENTS)
513 requirements=URL_NAME_REQUIREMENTS)
520
514
521 return rmap
515 return rmap
@@ -1,514 +1,448 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-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 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 import peppercorn
28 import peppercorn
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, config
30 from pylons import request, tmpl_context as c, url, config
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35
35
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
41 RepoGroupAssignmentError
41 RepoGroupAssignmentError
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
46 HasPermissionAnyDecorator, XHRRequired)
46 HasPermissionAnyDecorator, XHRRequired)
47 from rhodecode.lib.base import BaseController, render
47 from rhodecode.lib.base import BaseController, render
48 from rhodecode.model.permission import PermissionModel
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.scm import UserGroupList
49 from rhodecode.model.scm import UserGroupList
50 from rhodecode.model.user_group import UserGroupModel
50 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
53 from rhodecode.model.forms import (
53 from rhodecode.model.forms import (
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
55 UserPermissionsForm)
55 UserPermissionsForm)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class UserGroupsController(BaseController):
62 class UserGroupsController(BaseController):
63 """REST Controller styled on the Atom Publishing Protocol"""
63 """REST Controller styled on the Atom Publishing Protocol"""
64
64
65 @LoginRequired()
65 @LoginRequired()
66 def __before__(self):
66 def __before__(self):
67 super(UserGroupsController, self).__before__()
67 super(UserGroupsController, self).__before__()
68 c.available_permissions = config['available_permissions']
68 c.available_permissions = config['available_permissions']
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
70
70
71 def __load_data(self, user_group_id):
71 def __load_data(self, user_group_id):
72 c.group_members_obj = [x.user for x in c.user_group.members]
72 c.group_members_obj = [x.user for x in c.user_group.members]
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
75
75
76 def __load_defaults(self, user_group_id):
76 def __load_defaults(self, user_group_id):
77 """
77 """
78 Load defaults settings for edit, and update
78 Load defaults settings for edit, and update
79
79
80 :param user_group_id:
80 :param user_group_id:
81 """
81 """
82 user_group = UserGroup.get_or_404(user_group_id)
82 user_group = UserGroup.get_or_404(user_group_id)
83 data = user_group.get_dict()
83 data = user_group.get_dict()
84 # fill owner
84 # fill owner
85 if user_group.user:
85 if user_group.user:
86 data.update({'user': user_group.user.username})
86 data.update({'user': user_group.user.username})
87 else:
87 else:
88 replacement_user = User.get_first_super_admin().username
88 replacement_user = User.get_first_super_admin().username
89 data.update({'user': replacement_user})
89 data.update({'user': replacement_user})
90 return data
90 return data
91
91
92 def _revoke_perms_on_yourself(self, form_result):
92 def _revoke_perms_on_yourself(self, form_result):
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
94 form_result['perm_updates'])
94 form_result['perm_updates'])
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 form_result['perm_additions'])
96 form_result['perm_additions'])
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 form_result['perm_deletions'])
98 form_result['perm_deletions'])
99 admin_perm = 'usergroup.admin'
99 admin_perm = 'usergroup.admin'
100 if _updates and _updates[0][1] != admin_perm or \
100 if _updates and _updates[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
102 _deletions and _deletions[0][1] != admin_perm:
102 _deletions and _deletions[0][1] != admin_perm:
103 return True
103 return True
104 return False
104 return False
105
105
106 # permission check inside
107 @NotAnonymous()
108 def index(self):
109 # TODO(marcink): remove bind to self.request after pyramid migration
110 self.request = c.pyramid_request
111 _render = self.request.get_partial_renderer(
112 'data_table/_dt_elements.mako')
113
114 def user_group_name(user_group_id, user_group_name):
115 return _render("user_group_name", user_group_id, user_group_name)
116
117 def user_group_actions(user_group_id, user_group_name):
118 return _render("user_group_actions", user_group_id, user_group_name)
119
120 # json generate
121 group_iter = UserGroupList(UserGroup.query().all(),
122 perm_set=['usergroup.admin'])
123
124 user_groups_data = []
125 for user_gr in group_iter:
126 user_groups_data.append({
127 "group_name": user_group_name(
128 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
129 "group_name_raw": user_gr.users_group_name,
130 "desc": h.escape(user_gr.user_group_description),
131 "members": len(user_gr.members),
132 "sync": user_gr.group_data.get('extern_type'),
133 "active": h.bool2icon(user_gr.users_group_active),
134 "owner": h.escape(h.link_to_user(user_gr.user.username)),
135 "action": user_group_actions(
136 user_gr.users_group_id, user_gr.users_group_name)
137 })
138
139 c.data = json.dumps(user_groups_data)
140 return render('admin/user_groups/user_groups.mako')
141
142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
106 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
143 @auth.CSRFRequired()
107 @auth.CSRFRequired()
144 def create(self):
108 def create(self):
145
109
146 users_group_form = UserGroupForm()()
110 users_group_form = UserGroupForm()()
147 try:
111 try:
148 form_result = users_group_form.to_python(dict(request.POST))
112 form_result = users_group_form.to_python(dict(request.POST))
149 user_group = UserGroupModel().create(
113 user_group = UserGroupModel().create(
150 name=form_result['users_group_name'],
114 name=form_result['users_group_name'],
151 description=form_result['user_group_description'],
115 description=form_result['user_group_description'],
152 owner=c.rhodecode_user.user_id,
116 owner=c.rhodecode_user.user_id,
153 active=form_result['users_group_active'])
117 active=form_result['users_group_active'])
154 Session().flush()
118 Session().flush()
155 creation_data = user_group.get_api_data()
119 creation_data = user_group.get_api_data()
156 user_group_name = form_result['users_group_name']
120 user_group_name = form_result['users_group_name']
157
121
158 audit_logger.store_web(
122 audit_logger.store_web(
159 'user_group.create', action_data={'data': creation_data},
123 'user_group.create', action_data={'data': creation_data},
160 user=c.rhodecode_user)
124 user=c.rhodecode_user)
161
125
162 user_group_link = h.link_to(
126 user_group_link = h.link_to(
163 h.escape(user_group_name),
127 h.escape(user_group_name),
164 url('edit_users_group', user_group_id=user_group.users_group_id))
128 url('edit_users_group', user_group_id=user_group.users_group_id))
165 h.flash(h.literal(_('Created user group %(user_group_link)s')
129 h.flash(h.literal(_('Created user group %(user_group_link)s')
166 % {'user_group_link': user_group_link}),
130 % {'user_group_link': user_group_link}),
167 category='success')
131 category='success')
168 Session().commit()
132 Session().commit()
169 except formencode.Invalid as errors:
133 except formencode.Invalid as errors:
170 return htmlfill.render(
134 return htmlfill.render(
171 render('admin/user_groups/user_group_add.mako'),
135 render('admin/user_groups/user_group_add.mako'),
172 defaults=errors.value,
136 defaults=errors.value,
173 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
174 prefix_error=False,
138 prefix_error=False,
175 encoding="UTF-8",
139 encoding="UTF-8",
176 force_defaults=False)
140 force_defaults=False)
177 except Exception:
141 except Exception:
178 log.exception("Exception creating user group")
142 log.exception("Exception creating user group")
179 h.flash(_('Error occurred during creation of user group %s') \
143 h.flash(_('Error occurred during creation of user group %s') \
180 % request.POST.get('users_group_name'), category='error')
144 % request.POST.get('users_group_name'), category='error')
181
145
182 return redirect(
146 return redirect(
183 url('edit_users_group', user_group_id=user_group.users_group_id))
147 url('edit_users_group', user_group_id=user_group.users_group_id))
184
148
185 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
149 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
186 def new(self):
150 def new(self):
187 """GET /user_groups/new: Form to create a new item"""
151 """GET /user_groups/new: Form to create a new item"""
188 # url('new_users_group')
152 # url('new_users_group')
189 return render('admin/user_groups/user_group_add.mako')
153 return render('admin/user_groups/user_group_add.mako')
190
154
191 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
155 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
192 @auth.CSRFRequired()
156 @auth.CSRFRequired()
193 def update(self, user_group_id):
157 def update(self, user_group_id):
194
158
195 user_group_id = safe_int(user_group_id)
159 user_group_id = safe_int(user_group_id)
196 c.user_group = UserGroup.get_or_404(user_group_id)
160 c.user_group = UserGroup.get_or_404(user_group_id)
197 c.active = 'settings'
161 c.active = 'settings'
198 self.__load_data(user_group_id)
162 self.__load_data(user_group_id)
199
163
200 users_group_form = UserGroupForm(
164 users_group_form = UserGroupForm(
201 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
165 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
202
166
203 old_values = c.user_group.get_api_data()
167 old_values = c.user_group.get_api_data()
204 try:
168 try:
205 form_result = users_group_form.to_python(request.POST)
169 form_result = users_group_form.to_python(request.POST)
206 pstruct = peppercorn.parse(request.POST.items())
170 pstruct = peppercorn.parse(request.POST.items())
207 form_result['users_group_members'] = pstruct['user_group_members']
171 form_result['users_group_members'] = pstruct['user_group_members']
208
172
209 user_group, added_members, removed_members = \
173 user_group, added_members, removed_members = \
210 UserGroupModel().update(c.user_group, form_result)
174 UserGroupModel().update(c.user_group, form_result)
211 updated_user_group = form_result['users_group_name']
175 updated_user_group = form_result['users_group_name']
212
176
213 audit_logger.store_web(
177 audit_logger.store_web(
214 'user_group.edit', action_data={'old_data': old_values},
178 'user_group.edit', action_data={'old_data': old_values},
215 user=c.rhodecode_user)
179 user=c.rhodecode_user)
216
180
217 # TODO(marcink): use added/removed to set user_group.edit.member.add
181 # TODO(marcink): use added/removed to set user_group.edit.member.add
218
182
219 h.flash(_('Updated user group %s') % updated_user_group,
183 h.flash(_('Updated user group %s') % updated_user_group,
220 category='success')
184 category='success')
221 Session().commit()
185 Session().commit()
222 except formencode.Invalid as errors:
186 except formencode.Invalid as errors:
223 defaults = errors.value
187 defaults = errors.value
224 e = errors.error_dict or {}
188 e = errors.error_dict or {}
225
189
226 return htmlfill.render(
190 return htmlfill.render(
227 render('admin/user_groups/user_group_edit.mako'),
191 render('admin/user_groups/user_group_edit.mako'),
228 defaults=defaults,
192 defaults=defaults,
229 errors=e,
193 errors=e,
230 prefix_error=False,
194 prefix_error=False,
231 encoding="UTF-8",
195 encoding="UTF-8",
232 force_defaults=False)
196 force_defaults=False)
233 except Exception:
197 except Exception:
234 log.exception("Exception during update of user group")
198 log.exception("Exception during update of user group")
235 h.flash(_('Error occurred during update of user group %s')
199 h.flash(_('Error occurred during update of user group %s')
236 % request.POST.get('users_group_name'), category='error')
200 % request.POST.get('users_group_name'), category='error')
237
201
238 return redirect(url('edit_users_group', user_group_id=user_group_id))
202 return redirect(url('edit_users_group', user_group_id=user_group_id))
239
203
240 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
204 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
241 @auth.CSRFRequired()
205 @auth.CSRFRequired()
242 def delete(self, user_group_id):
206 def delete(self, user_group_id):
243 user_group_id = safe_int(user_group_id)
207 user_group_id = safe_int(user_group_id)
244 c.user_group = UserGroup.get_or_404(user_group_id)
208 c.user_group = UserGroup.get_or_404(user_group_id)
245 force = str2bool(request.POST.get('force'))
209 force = str2bool(request.POST.get('force'))
246
210
247 old_values = c.user_group.get_api_data()
211 old_values = c.user_group.get_api_data()
248 try:
212 try:
249 UserGroupModel().delete(c.user_group, force=force)
213 UserGroupModel().delete(c.user_group, force=force)
250 audit_logger.store_web(
214 audit_logger.store_web(
251 'user.delete', action_data={'old_data': old_values},
215 'user.delete', action_data={'old_data': old_values},
252 user=c.rhodecode_user)
216 user=c.rhodecode_user)
253 Session().commit()
217 Session().commit()
254 h.flash(_('Successfully deleted user group'), category='success')
218 h.flash(_('Successfully deleted user group'), category='success')
255 except UserGroupAssignedException as e:
219 except UserGroupAssignedException as e:
256 h.flash(str(e), category='error')
220 h.flash(str(e), category='error')
257 except Exception:
221 except Exception:
258 log.exception("Exception during deletion of user group")
222 log.exception("Exception during deletion of user group")
259 h.flash(_('An error occurred during deletion of user group'),
223 h.flash(_('An error occurred during deletion of user group'),
260 category='error')
224 category='error')
261 return redirect(url('users_groups'))
225 return redirect(url('users_groups'))
262
226
263 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
227 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
264 def edit(self, user_group_id):
228 def edit(self, user_group_id):
265 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
229 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
266 # url('edit_users_group', user_group_id=ID)
230 # url('edit_users_group', user_group_id=ID)
267
231
268 user_group_id = safe_int(user_group_id)
232 user_group_id = safe_int(user_group_id)
269 c.user_group = UserGroup.get_or_404(user_group_id)
233 c.user_group = UserGroup.get_or_404(user_group_id)
270 c.active = 'settings'
234 c.active = 'settings'
271 self.__load_data(user_group_id)
235 self.__load_data(user_group_id)
272
236
273 defaults = self.__load_defaults(user_group_id)
237 defaults = self.__load_defaults(user_group_id)
274
238
275 return htmlfill.render(
239 return htmlfill.render(
276 render('admin/user_groups/user_group_edit.mako'),
240 render('admin/user_groups/user_group_edit.mako'),
277 defaults=defaults,
241 defaults=defaults,
278 encoding="UTF-8",
242 encoding="UTF-8",
279 force_defaults=False
243 force_defaults=False
280 )
244 )
281
245
282 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
246 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
283 def edit_perms(self, user_group_id):
247 def edit_perms(self, user_group_id):
284 user_group_id = safe_int(user_group_id)
248 user_group_id = safe_int(user_group_id)
285 c.user_group = UserGroup.get_or_404(user_group_id)
249 c.user_group = UserGroup.get_or_404(user_group_id)
286 c.active = 'perms'
250 c.active = 'perms'
287
251
288 defaults = {}
252 defaults = {}
289 # fill user group users
253 # fill user group users
290 for p in c.user_group.user_user_group_to_perm:
254 for p in c.user_group.user_user_group_to_perm:
291 defaults.update({'u_perm_%s' % p.user.user_id:
255 defaults.update({'u_perm_%s' % p.user.user_id:
292 p.permission.permission_name})
256 p.permission.permission_name})
293
257
294 for p in c.user_group.user_group_user_group_to_perm:
258 for p in c.user_group.user_group_user_group_to_perm:
295 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
259 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
296 p.permission.permission_name})
260 p.permission.permission_name})
297
261
298 return htmlfill.render(
262 return htmlfill.render(
299 render('admin/user_groups/user_group_edit.mako'),
263 render('admin/user_groups/user_group_edit.mako'),
300 defaults=defaults,
264 defaults=defaults,
301 encoding="UTF-8",
265 encoding="UTF-8",
302 force_defaults=False
266 force_defaults=False
303 )
267 )
304
268
305 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
306 @auth.CSRFRequired()
270 @auth.CSRFRequired()
307 def update_perms(self, user_group_id):
271 def update_perms(self, user_group_id):
308 """
272 """
309 grant permission for given usergroup
273 grant permission for given usergroup
310
274
311 :param user_group_id:
275 :param user_group_id:
312 """
276 """
313 user_group_id = safe_int(user_group_id)
277 user_group_id = safe_int(user_group_id)
314 c.user_group = UserGroup.get_or_404(user_group_id)
278 c.user_group = UserGroup.get_or_404(user_group_id)
315 form = UserGroupPermsForm()().to_python(request.POST)
279 form = UserGroupPermsForm()().to_python(request.POST)
316
280
317 if not c.rhodecode_user.is_admin:
281 if not c.rhodecode_user.is_admin:
318 if self._revoke_perms_on_yourself(form):
282 if self._revoke_perms_on_yourself(form):
319 msg = _('Cannot change permission for yourself as admin')
283 msg = _('Cannot change permission for yourself as admin')
320 h.flash(msg, category='warning')
284 h.flash(msg, category='warning')
321 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
285 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
322
286
323 try:
287 try:
324 UserGroupModel().update_permissions(user_group_id,
288 UserGroupModel().update_permissions(user_group_id,
325 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
289 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
326 except RepoGroupAssignmentError:
290 except RepoGroupAssignmentError:
327 h.flash(_('Target group cannot be the same'), category='error')
291 h.flash(_('Target group cannot be the same'), category='error')
328 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
292 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
329
293
330 # TODO(marcink): implement global permissions
294 # TODO(marcink): implement global permissions
331 # audit_log.store_web('user_group.edit.permissions')
295 # audit_log.store_web('user_group.edit.permissions')
332 Session().commit()
296 Session().commit()
333 h.flash(_('User Group permissions updated'), category='success')
297 h.flash(_('User Group permissions updated'), category='success')
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
298 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
335
299
336 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
300 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
337 def edit_perms_summary(self, user_group_id):
301 def edit_perms_summary(self, user_group_id):
338 user_group_id = safe_int(user_group_id)
302 user_group_id = safe_int(user_group_id)
339 c.user_group = UserGroup.get_or_404(user_group_id)
303 c.user_group = UserGroup.get_or_404(user_group_id)
340 c.active = 'perms_summary'
304 c.active = 'perms_summary'
341 permissions = {
305 permissions = {
342 'repositories': {},
306 'repositories': {},
343 'repositories_groups': {},
307 'repositories_groups': {},
344 }
308 }
345 ugroup_repo_perms = UserGroupRepoToPerm.query()\
309 ugroup_repo_perms = UserGroupRepoToPerm.query()\
346 .options(joinedload(UserGroupRepoToPerm.permission))\
310 .options(joinedload(UserGroupRepoToPerm.permission))\
347 .options(joinedload(UserGroupRepoToPerm.repository))\
311 .options(joinedload(UserGroupRepoToPerm.repository))\
348 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
312 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
349 .all()
313 .all()
350
314
351 for gr in ugroup_repo_perms:
315 for gr in ugroup_repo_perms:
352 permissions['repositories'][gr.repository.repo_name] \
316 permissions['repositories'][gr.repository.repo_name] \
353 = gr.permission.permission_name
317 = gr.permission.permission_name
354
318
355 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
319 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
356 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
320 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
357 .options(joinedload(UserGroupRepoGroupToPerm.group))\
321 .options(joinedload(UserGroupRepoGroupToPerm.group))\
358 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
322 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
359 .all()
323 .all()
360
324
361 for gr in ugroup_group_perms:
325 for gr in ugroup_group_perms:
362 permissions['repositories_groups'][gr.group.group_name] \
326 permissions['repositories_groups'][gr.group.group_name] \
363 = gr.permission.permission_name
327 = gr.permission.permission_name
364 c.permissions = permissions
328 c.permissions = permissions
365 return render('admin/user_groups/user_group_edit.mako')
329 return render('admin/user_groups/user_group_edit.mako')
366
330
367 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
331 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
368 def edit_global_perms(self, user_group_id):
332 def edit_global_perms(self, user_group_id):
369 user_group_id = safe_int(user_group_id)
333 user_group_id = safe_int(user_group_id)
370 c.user_group = UserGroup.get_or_404(user_group_id)
334 c.user_group = UserGroup.get_or_404(user_group_id)
371 c.active = 'global_perms'
335 c.active = 'global_perms'
372
336
373 c.default_user = User.get_default_user()
337 c.default_user = User.get_default_user()
374 defaults = c.user_group.get_dict()
338 defaults = c.user_group.get_dict()
375 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
339 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
376 defaults.update(c.user_group.get_default_perms())
340 defaults.update(c.user_group.get_default_perms())
377
341
378 return htmlfill.render(
342 return htmlfill.render(
379 render('admin/user_groups/user_group_edit.mako'),
343 render('admin/user_groups/user_group_edit.mako'),
380 defaults=defaults,
344 defaults=defaults,
381 encoding="UTF-8",
345 encoding="UTF-8",
382 force_defaults=False
346 force_defaults=False
383 )
347 )
384
348
385 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
349 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
386 @auth.CSRFRequired()
350 @auth.CSRFRequired()
387 def update_global_perms(self, user_group_id):
351 def update_global_perms(self, user_group_id):
388 user_group_id = safe_int(user_group_id)
352 user_group_id = safe_int(user_group_id)
389 user_group = UserGroup.get_or_404(user_group_id)
353 user_group = UserGroup.get_or_404(user_group_id)
390 c.active = 'global_perms'
354 c.active = 'global_perms'
391
355
392 try:
356 try:
393 # first stage that verifies the checkbox
357 # first stage that verifies the checkbox
394 _form = UserIndividualPermissionsForm()
358 _form = UserIndividualPermissionsForm()
395 form_result = _form.to_python(dict(request.POST))
359 form_result = _form.to_python(dict(request.POST))
396 inherit_perms = form_result['inherit_default_permissions']
360 inherit_perms = form_result['inherit_default_permissions']
397 user_group.inherit_default_permissions = inherit_perms
361 user_group.inherit_default_permissions = inherit_perms
398 Session().add(user_group)
362 Session().add(user_group)
399
363
400 if not inherit_perms:
364 if not inherit_perms:
401 # only update the individual ones if we un check the flag
365 # only update the individual ones if we un check the flag
402 _form = UserPermissionsForm(
366 _form = UserPermissionsForm(
403 [x[0] for x in c.repo_create_choices],
367 [x[0] for x in c.repo_create_choices],
404 [x[0] for x in c.repo_create_on_write_choices],
368 [x[0] for x in c.repo_create_on_write_choices],
405 [x[0] for x in c.repo_group_create_choices],
369 [x[0] for x in c.repo_group_create_choices],
406 [x[0] for x in c.user_group_create_choices],
370 [x[0] for x in c.user_group_create_choices],
407 [x[0] for x in c.fork_choices],
371 [x[0] for x in c.fork_choices],
408 [x[0] for x in c.inherit_default_permission_choices])()
372 [x[0] for x in c.inherit_default_permission_choices])()
409
373
410 form_result = _form.to_python(dict(request.POST))
374 form_result = _form.to_python(dict(request.POST))
411 form_result.update({'perm_user_group_id': user_group.users_group_id})
375 form_result.update({'perm_user_group_id': user_group.users_group_id})
412
376
413 PermissionModel().update_user_group_permissions(form_result)
377 PermissionModel().update_user_group_permissions(form_result)
414
378
415 Session().commit()
379 Session().commit()
416 h.flash(_('User Group global permissions updated successfully'),
380 h.flash(_('User Group global permissions updated successfully'),
417 category='success')
381 category='success')
418
382
419 except formencode.Invalid as errors:
383 except formencode.Invalid as errors:
420 defaults = errors.value
384 defaults = errors.value
421 c.user_group = user_group
385 c.user_group = user_group
422 return htmlfill.render(
386 return htmlfill.render(
423 render('admin/user_groups/user_group_edit.mako'),
387 render('admin/user_groups/user_group_edit.mako'),
424 defaults=defaults,
388 defaults=defaults,
425 errors=errors.error_dict or {},
389 errors=errors.error_dict or {},
426 prefix_error=False,
390 prefix_error=False,
427 encoding="UTF-8",
391 encoding="UTF-8",
428 force_defaults=False)
392 force_defaults=False)
429 except Exception:
393 except Exception:
430 log.exception("Exception during permissions saving")
394 log.exception("Exception during permissions saving")
431 h.flash(_('An error occurred during permissions saving'),
395 h.flash(_('An error occurred during permissions saving'),
432 category='error')
396 category='error')
433
397
434 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
398 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
435
399
436 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
400 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
437 def edit_advanced(self, user_group_id):
401 def edit_advanced(self, user_group_id):
438 user_group_id = safe_int(user_group_id)
402 user_group_id = safe_int(user_group_id)
439 c.user_group = UserGroup.get_or_404(user_group_id)
403 c.user_group = UserGroup.get_or_404(user_group_id)
440 c.active = 'advanced'
404 c.active = 'advanced'
441 c.group_members_obj = sorted(
405 c.group_members_obj = sorted(
442 (x.user for x in c.user_group.members),
406 (x.user for x in c.user_group.members),
443 key=lambda u: u.username.lower())
407 key=lambda u: u.username.lower())
444
408
445 c.group_to_repos = sorted(
409 c.group_to_repos = sorted(
446 (x.repository for x in c.user_group.users_group_repo_to_perm),
410 (x.repository for x in c.user_group.users_group_repo_to_perm),
447 key=lambda u: u.repo_name.lower())
411 key=lambda u: u.repo_name.lower())
448
412
449 c.group_to_repo_groups = sorted(
413 c.group_to_repo_groups = sorted(
450 (x.group for x in c.user_group.users_group_repo_group_to_perm),
414 (x.group for x in c.user_group.users_group_repo_group_to_perm),
451 key=lambda u: u.group_name.lower())
415 key=lambda u: u.group_name.lower())
452
416
453 return render('admin/user_groups/user_group_edit.mako')
417 return render('admin/user_groups/user_group_edit.mako')
454
418
455 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
419 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
456 def edit_advanced_set_synchronization(self, user_group_id):
420 def edit_advanced_set_synchronization(self, user_group_id):
457 user_group_id = safe_int(user_group_id)
421 user_group_id = safe_int(user_group_id)
458 user_group = UserGroup.get_or_404(user_group_id)
422 user_group = UserGroup.get_or_404(user_group_id)
459
423
460 existing = user_group.group_data.get('extern_type')
424 existing = user_group.group_data.get('extern_type')
461
425
462 if existing:
426 if existing:
463 new_state = user_group.group_data
427 new_state = user_group.group_data
464 new_state['extern_type'] = None
428 new_state['extern_type'] = None
465 else:
429 else:
466 new_state = user_group.group_data
430 new_state = user_group.group_data
467 new_state['extern_type'] = 'manual'
431 new_state['extern_type'] = 'manual'
468 new_state['extern_type_set_by'] = c.rhodecode_user.username
432 new_state['extern_type_set_by'] = c.rhodecode_user.username
469
433
470 try:
434 try:
471 user_group.group_data = new_state
435 user_group.group_data = new_state
472 Session().add(user_group)
436 Session().add(user_group)
473 Session().commit()
437 Session().commit()
474
438
475 h.flash(_('User Group synchronization updated successfully'),
439 h.flash(_('User Group synchronization updated successfully'),
476 category='success')
440 category='success')
477 except Exception:
441 except Exception:
478 log.exception("Exception during sync settings saving")
442 log.exception("Exception during sync settings saving")
479 h.flash(_('An error occurred during synchronization update'),
443 h.flash(_('An error occurred during synchronization update'),
480 category='error')
444 category='error')
481
445
482 return redirect(
446 return redirect(
483 url('edit_user_group_advanced', user_group_id=user_group_id))
447 url('edit_user_group_advanced', user_group_id=user_group_id))
484
448
485 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
486 @XHRRequired()
487 @jsonify
488 def user_group_members(self, user_group_id):
489 """
490 Return members of given user group
491 """
492 user_group_id = safe_int(user_group_id)
493 user_group = UserGroup.get_or_404(user_group_id)
494 group_members_obj = sorted((x.user for x in user_group.members),
495 key=lambda u: u.username.lower())
496
497 group_members = [
498 {
499 'id': user.user_id,
500 'first_name': user.first_name,
501 'last_name': user.last_name,
502 'username': user.username,
503 'icon_link': h.gravatar_url(user.email, 30),
504 'value_display': h.person(user.email),
505 'value': user.username,
506 'value_type': 'user',
507 'active': user.active,
508 }
509 for user in group_members_obj
510 ]
511
512 return {
513 'members': group_members
514 }
@@ -1,982 +1,982 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 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45 from pyramid.request import Request
45 from pyramid.request import Request
46
46
47 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.fakemod import create_module
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
100 # pyramid
100 # pyramid
101 _repo = request.db_repo.repo_name
101 _repo = request.db_repo.repo_name
102 else:
102 else:
103 # TODO(marcink): remove after pylons migration...
103 # TODO(marcink): remove after pylons migration...
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105
105
106 if _repo:
106 if _repo:
107 _repo = _repo.rstrip('/')
107 _repo = _repo.rstrip('/')
108 return _repo
108 return _repo
109
109
110
110
111 def get_repo_group_slug(request):
111 def get_repo_group_slug(request):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
113 # pyramid
113 # pyramid
114 _group = request.matchdict.get('repo_group_name')
114 _group = request.matchdict.get('repo_group_name')
115 else:
115 else:
116 _group = request.environ['pylons.routes_dict'].get('group_name')
116 _group = request.environ['pylons.routes_dict'].get('group_name')
117
117
118 if _group:
118 if _group:
119 _group = _group.rstrip('/')
119 _group = _group.rstrip('/')
120 return _group
120 return _group
121
121
122
122
123 def get_user_group_slug(request):
123 def get_user_group_slug(request):
124 if isinstance(request, Request) and getattr(request, 'matchdict', None):
124 if isinstance(request, Request) and getattr(request, 'matchdict', None):
125 # pyramid
125 # pyramid
126 _group = request.matchdict.get('user_group_id')
126 _group = request.matchdict.get('user_group_id')
127 else:
127 else:
128 _group = request.environ['pylons.routes_dict'].get('user_group_id')
128 _group = request.environ['pylons.routes_dict'].get('user_group_id')
129
129
130 try:
130 try:
131 _group = UserGroup.get(_group)
131 _group = UserGroup.get(_group)
132 if _group:
132 if _group:
133 _group = _group.users_group_name
133 _group = _group.users_group_name
134 except Exception:
134 except Exception:
135 log.debug(traceback.format_exc())
135 log.exception('Failed to get user group by id')
136 # catch all failures here
136 # catch all failures here
137 pass
137 return None
138
138
139 return _group
139 return _group
140
140
141
141
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
143 """
143 """
144 Scans given path for repos and return (name,(type,path)) tuple
144 Scans given path for repos and return (name,(type,path)) tuple
145
145
146 :param path: path to scan for repositories
146 :param path: path to scan for repositories
147 :param recursive: recursive search and return names with subdirs in front
147 :param recursive: recursive search and return names with subdirs in front
148 """
148 """
149
149
150 # remove ending slash for better results
150 # remove ending slash for better results
151 path = path.rstrip(os.sep)
151 path = path.rstrip(os.sep)
152 log.debug('now scanning in %s location recursive:%s...', path, recursive)
152 log.debug('now scanning in %s location recursive:%s...', path, recursive)
153
153
154 def _get_repos(p):
154 def _get_repos(p):
155 dirpaths = _get_dirpaths(p)
155 dirpaths = _get_dirpaths(p)
156 if not _is_dir_writable(p):
156 if not _is_dir_writable(p):
157 log.warning('repo path without write access: %s', p)
157 log.warning('repo path without write access: %s', p)
158
158
159 for dirpath in dirpaths:
159 for dirpath in dirpaths:
160 if os.path.isfile(os.path.join(p, dirpath)):
160 if os.path.isfile(os.path.join(p, dirpath)):
161 continue
161 continue
162 cur_path = os.path.join(p, dirpath)
162 cur_path = os.path.join(p, dirpath)
163
163
164 # skip removed repos
164 # skip removed repos
165 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
165 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
166 continue
166 continue
167
167
168 #skip .<somethin> dirs
168 #skip .<somethin> dirs
169 if dirpath.startswith('.'):
169 if dirpath.startswith('.'):
170 continue
170 continue
171
171
172 try:
172 try:
173 scm_info = get_scm(cur_path)
173 scm_info = get_scm(cur_path)
174 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
174 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
175 except VCSError:
175 except VCSError:
176 if not recursive:
176 if not recursive:
177 continue
177 continue
178 #check if this dir containts other repos for recursive scan
178 #check if this dir containts other repos for recursive scan
179 rec_path = os.path.join(p, dirpath)
179 rec_path = os.path.join(p, dirpath)
180 if os.path.isdir(rec_path):
180 if os.path.isdir(rec_path):
181 for inner_scm in _get_repos(rec_path):
181 for inner_scm in _get_repos(rec_path):
182 yield inner_scm
182 yield inner_scm
183
183
184 return _get_repos(path)
184 return _get_repos(path)
185
185
186
186
187 def _get_dirpaths(p):
187 def _get_dirpaths(p):
188 try:
188 try:
189 # OS-independable way of checking if we have at least read-only
189 # OS-independable way of checking if we have at least read-only
190 # access or not.
190 # access or not.
191 dirpaths = os.listdir(p)
191 dirpaths = os.listdir(p)
192 except OSError:
192 except OSError:
193 log.warning('ignoring repo path without read access: %s', p)
193 log.warning('ignoring repo path without read access: %s', p)
194 return []
194 return []
195
195
196 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
196 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
197 # decode paths and suddenly returns unicode objects itself. The items it
197 # decode paths and suddenly returns unicode objects itself. The items it
198 # cannot decode are returned as strings and cause issues.
198 # cannot decode are returned as strings and cause issues.
199 #
199 #
200 # Those paths are ignored here until a solid solution for path handling has
200 # Those paths are ignored here until a solid solution for path handling has
201 # been built.
201 # been built.
202 expected_type = type(p)
202 expected_type = type(p)
203
203
204 def _has_correct_type(item):
204 def _has_correct_type(item):
205 if type(item) is not expected_type:
205 if type(item) is not expected_type:
206 log.error(
206 log.error(
207 u"Ignoring path %s since it cannot be decoded into unicode.",
207 u"Ignoring path %s since it cannot be decoded into unicode.",
208 # Using "repr" to make sure that we see the byte value in case
208 # Using "repr" to make sure that we see the byte value in case
209 # of support.
209 # of support.
210 repr(item))
210 repr(item))
211 return False
211 return False
212 return True
212 return True
213
213
214 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
214 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
215
215
216 return dirpaths
216 return dirpaths
217
217
218
218
219 def _is_dir_writable(path):
219 def _is_dir_writable(path):
220 """
220 """
221 Probe if `path` is writable.
221 Probe if `path` is writable.
222
222
223 Due to trouble on Cygwin / Windows, this is actually probing if it is
223 Due to trouble on Cygwin / Windows, this is actually probing if it is
224 possible to create a file inside of `path`, stat does not produce reliable
224 possible to create a file inside of `path`, stat does not produce reliable
225 results in this case.
225 results in this case.
226 """
226 """
227 try:
227 try:
228 with tempfile.TemporaryFile(dir=path):
228 with tempfile.TemporaryFile(dir=path):
229 pass
229 pass
230 except OSError:
230 except OSError:
231 return False
231 return False
232 return True
232 return True
233
233
234
234
235 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
235 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
236 """
236 """
237 Returns True if given path is a valid repository False otherwise.
237 Returns True if given path is a valid repository False otherwise.
238 If expect_scm param is given also, compare if given scm is the same
238 If expect_scm param is given also, compare if given scm is the same
239 as expected from scm parameter. If explicit_scm is given don't try to
239 as expected from scm parameter. If explicit_scm is given don't try to
240 detect the scm, just use the given one to check if repo is valid
240 detect the scm, just use the given one to check if repo is valid
241
241
242 :param repo_name:
242 :param repo_name:
243 :param base_path:
243 :param base_path:
244 :param expect_scm:
244 :param expect_scm:
245 :param explicit_scm:
245 :param explicit_scm:
246
246
247 :return True: if given path is a valid repository
247 :return True: if given path is a valid repository
248 """
248 """
249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
250 log.debug('Checking if `%s` is a valid path for repository. '
250 log.debug('Checking if `%s` is a valid path for repository. '
251 'Explicit type: %s', repo_name, explicit_scm)
251 'Explicit type: %s', repo_name, explicit_scm)
252
252
253 try:
253 try:
254 if explicit_scm:
254 if explicit_scm:
255 detected_scms = [get_scm_backend(explicit_scm)]
255 detected_scms = [get_scm_backend(explicit_scm)]
256 else:
256 else:
257 detected_scms = get_scm(full_path)
257 detected_scms = get_scm(full_path)
258
258
259 if expect_scm:
259 if expect_scm:
260 return detected_scms[0] == expect_scm
260 return detected_scms[0] == expect_scm
261 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
261 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
262 return True
262 return True
263 except VCSError:
263 except VCSError:
264 log.debug('path: %s is not a valid repo !', full_path)
264 log.debug('path: %s is not a valid repo !', full_path)
265 return False
265 return False
266
266
267
267
268 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
268 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
269 """
269 """
270 Returns True if given path is a repository group, False otherwise
270 Returns True if given path is a repository group, False otherwise
271
271
272 :param repo_name:
272 :param repo_name:
273 :param base_path:
273 :param base_path:
274 """
274 """
275 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
275 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
276 log.debug('Checking if `%s` is a valid path for repository group',
276 log.debug('Checking if `%s` is a valid path for repository group',
277 repo_group_name)
277 repo_group_name)
278
278
279 # check if it's not a repo
279 # check if it's not a repo
280 if is_valid_repo(repo_group_name, base_path):
280 if is_valid_repo(repo_group_name, base_path):
281 log.debug('Repo called %s exist, it is not a valid '
281 log.debug('Repo called %s exist, it is not a valid '
282 'repo group' % repo_group_name)
282 'repo group' % repo_group_name)
283 return False
283 return False
284
284
285 try:
285 try:
286 # we need to check bare git repos at higher level
286 # we need to check bare git repos at higher level
287 # since we might match branches/hooks/info/objects or possible
287 # since we might match branches/hooks/info/objects or possible
288 # other things inside bare git repo
288 # other things inside bare git repo
289 scm_ = get_scm(os.path.dirname(full_path))
289 scm_ = get_scm(os.path.dirname(full_path))
290 log.debug('path: %s is a vcs object:%s, not valid '
290 log.debug('path: %s is a vcs object:%s, not valid '
291 'repo group' % (full_path, scm_))
291 'repo group' % (full_path, scm_))
292 return False
292 return False
293 except VCSError:
293 except VCSError:
294 pass
294 pass
295
295
296 # check if it's a valid path
296 # check if it's a valid path
297 if skip_path_check or os.path.isdir(full_path):
297 if skip_path_check or os.path.isdir(full_path):
298 log.debug('path: %s is a valid repo group !', full_path)
298 log.debug('path: %s is a valid repo group !', full_path)
299 return True
299 return True
300
300
301 log.debug('path: %s is not a valid repo group !', full_path)
301 log.debug('path: %s is not a valid repo group !', full_path)
302 return False
302 return False
303
303
304
304
305 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
305 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
306 while True:
306 while True:
307 ok = raw_input(prompt)
307 ok = raw_input(prompt)
308 if ok.lower() in ('y', 'ye', 'yes'):
308 if ok.lower() in ('y', 'ye', 'yes'):
309 return True
309 return True
310 if ok.lower() in ('n', 'no', 'nop', 'nope'):
310 if ok.lower() in ('n', 'no', 'nop', 'nope'):
311 return False
311 return False
312 retries = retries - 1
312 retries = retries - 1
313 if retries < 0:
313 if retries < 0:
314 raise IOError
314 raise IOError
315 print(complaint)
315 print(complaint)
316
316
317 # propagated from mercurial documentation
317 # propagated from mercurial documentation
318 ui_sections = [
318 ui_sections = [
319 'alias', 'auth',
319 'alias', 'auth',
320 'decode/encode', 'defaults',
320 'decode/encode', 'defaults',
321 'diff', 'email',
321 'diff', 'email',
322 'extensions', 'format',
322 'extensions', 'format',
323 'merge-patterns', 'merge-tools',
323 'merge-patterns', 'merge-tools',
324 'hooks', 'http_proxy',
324 'hooks', 'http_proxy',
325 'smtp', 'patch',
325 'smtp', 'patch',
326 'paths', 'profiling',
326 'paths', 'profiling',
327 'server', 'trusted',
327 'server', 'trusted',
328 'ui', 'web', ]
328 'ui', 'web', ]
329
329
330
330
331 def config_data_from_db(clear_session=True, repo=None):
331 def config_data_from_db(clear_session=True, repo=None):
332 """
332 """
333 Read the configuration data from the database and return configuration
333 Read the configuration data from the database and return configuration
334 tuples.
334 tuples.
335 """
335 """
336 from rhodecode.model.settings import VcsSettingsModel
336 from rhodecode.model.settings import VcsSettingsModel
337
337
338 config = []
338 config = []
339
339
340 sa = meta.Session()
340 sa = meta.Session()
341 settings_model = VcsSettingsModel(repo=repo, sa=sa)
341 settings_model = VcsSettingsModel(repo=repo, sa=sa)
342
342
343 ui_settings = settings_model.get_ui_settings()
343 ui_settings = settings_model.get_ui_settings()
344
344
345 for setting in ui_settings:
345 for setting in ui_settings:
346 if setting.active:
346 if setting.active:
347 log.debug(
347 log.debug(
348 'settings ui from db: [%s] %s=%s',
348 'settings ui from db: [%s] %s=%s',
349 setting.section, setting.key, setting.value)
349 setting.section, setting.key, setting.value)
350 config.append((
350 config.append((
351 safe_str(setting.section), safe_str(setting.key),
351 safe_str(setting.section), safe_str(setting.key),
352 safe_str(setting.value)))
352 safe_str(setting.value)))
353 if setting.key == 'push_ssl':
353 if setting.key == 'push_ssl':
354 # force set push_ssl requirement to False, rhodecode
354 # force set push_ssl requirement to False, rhodecode
355 # handles that
355 # handles that
356 config.append((
356 config.append((
357 safe_str(setting.section), safe_str(setting.key), False))
357 safe_str(setting.section), safe_str(setting.key), False))
358 if clear_session:
358 if clear_session:
359 meta.Session.remove()
359 meta.Session.remove()
360
360
361 # TODO: mikhail: probably it makes no sense to re-read hooks information.
361 # TODO: mikhail: probably it makes no sense to re-read hooks information.
362 # It's already there and activated/deactivated
362 # It's already there and activated/deactivated
363 skip_entries = []
363 skip_entries = []
364 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
364 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
365 if 'pull' not in enabled_hook_classes:
365 if 'pull' not in enabled_hook_classes:
366 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
366 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
367 if 'push' not in enabled_hook_classes:
367 if 'push' not in enabled_hook_classes:
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
371
371
372 config = [entry for entry in config if entry[:2] not in skip_entries]
372 config = [entry for entry in config if entry[:2] not in skip_entries]
373
373
374 return config
374 return config
375
375
376
376
377 def make_db_config(clear_session=True, repo=None):
377 def make_db_config(clear_session=True, repo=None):
378 """
378 """
379 Create a :class:`Config` instance based on the values in the database.
379 Create a :class:`Config` instance based on the values in the database.
380 """
380 """
381 config = Config()
381 config = Config()
382 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
382 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
383 for section, option, value in config_data:
383 for section, option, value in config_data:
384 config.set(section, option, value)
384 config.set(section, option, value)
385 return config
385 return config
386
386
387
387
388 def get_enabled_hook_classes(ui_settings):
388 def get_enabled_hook_classes(ui_settings):
389 """
389 """
390 Return the enabled hook classes.
390 Return the enabled hook classes.
391
391
392 :param ui_settings: List of ui_settings as returned
392 :param ui_settings: List of ui_settings as returned
393 by :meth:`VcsSettingsModel.get_ui_settings`
393 by :meth:`VcsSettingsModel.get_ui_settings`
394
394
395 :return: a list with the enabled hook classes. The order is not guaranteed.
395 :return: a list with the enabled hook classes. The order is not guaranteed.
396 :rtype: list
396 :rtype: list
397 """
397 """
398 enabled_hooks = []
398 enabled_hooks = []
399 active_hook_keys = [
399 active_hook_keys = [
400 key for section, key, value, active in ui_settings
400 key for section, key, value, active in ui_settings
401 if section == 'hooks' and active]
401 if section == 'hooks' and active]
402
402
403 hook_names = {
403 hook_names = {
404 RhodeCodeUi.HOOK_PUSH: 'push',
404 RhodeCodeUi.HOOK_PUSH: 'push',
405 RhodeCodeUi.HOOK_PULL: 'pull',
405 RhodeCodeUi.HOOK_PULL: 'pull',
406 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
406 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
407 }
407 }
408
408
409 for key in active_hook_keys:
409 for key in active_hook_keys:
410 hook = hook_names.get(key)
410 hook = hook_names.get(key)
411 if hook:
411 if hook:
412 enabled_hooks.append(hook)
412 enabled_hooks.append(hook)
413
413
414 return enabled_hooks
414 return enabled_hooks
415
415
416
416
417 def set_rhodecode_config(config):
417 def set_rhodecode_config(config):
418 """
418 """
419 Updates pylons config with new settings from database
419 Updates pylons config with new settings from database
420
420
421 :param config:
421 :param config:
422 """
422 """
423 from rhodecode.model.settings import SettingsModel
423 from rhodecode.model.settings import SettingsModel
424 app_settings = SettingsModel().get_all_settings()
424 app_settings = SettingsModel().get_all_settings()
425
425
426 for k, v in app_settings.items():
426 for k, v in app_settings.items():
427 config[k] = v
427 config[k] = v
428
428
429
429
430 def get_rhodecode_realm():
430 def get_rhodecode_realm():
431 """
431 """
432 Return the rhodecode realm from database.
432 Return the rhodecode realm from database.
433 """
433 """
434 from rhodecode.model.settings import SettingsModel
434 from rhodecode.model.settings import SettingsModel
435 realm = SettingsModel().get_setting_by_name('realm')
435 realm = SettingsModel().get_setting_by_name('realm')
436 return safe_str(realm.app_settings_value)
436 return safe_str(realm.app_settings_value)
437
437
438
438
439 def get_rhodecode_base_path():
439 def get_rhodecode_base_path():
440 """
440 """
441 Returns the base path. The base path is the filesystem path which points
441 Returns the base path. The base path is the filesystem path which points
442 to the repository store.
442 to the repository store.
443 """
443 """
444 from rhodecode.model.settings import SettingsModel
444 from rhodecode.model.settings import SettingsModel
445 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
445 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
446 return safe_str(paths_ui.ui_value)
446 return safe_str(paths_ui.ui_value)
447
447
448
448
449 def map_groups(path):
449 def map_groups(path):
450 """
450 """
451 Given a full path to a repository, create all nested groups that this
451 Given a full path to a repository, create all nested groups that this
452 repo is inside. This function creates parent-child relationships between
452 repo is inside. This function creates parent-child relationships between
453 groups and creates default perms for all new groups.
453 groups and creates default perms for all new groups.
454
454
455 :param paths: full path to repository
455 :param paths: full path to repository
456 """
456 """
457 from rhodecode.model.repo_group import RepoGroupModel
457 from rhodecode.model.repo_group import RepoGroupModel
458 sa = meta.Session()
458 sa = meta.Session()
459 groups = path.split(Repository.NAME_SEP)
459 groups = path.split(Repository.NAME_SEP)
460 parent = None
460 parent = None
461 group = None
461 group = None
462
462
463 # last element is repo in nested groups structure
463 # last element is repo in nested groups structure
464 groups = groups[:-1]
464 groups = groups[:-1]
465 rgm = RepoGroupModel(sa)
465 rgm = RepoGroupModel(sa)
466 owner = User.get_first_super_admin()
466 owner = User.get_first_super_admin()
467 for lvl, group_name in enumerate(groups):
467 for lvl, group_name in enumerate(groups):
468 group_name = '/'.join(groups[:lvl] + [group_name])
468 group_name = '/'.join(groups[:lvl] + [group_name])
469 group = RepoGroup.get_by_group_name(group_name)
469 group = RepoGroup.get_by_group_name(group_name)
470 desc = '%s group' % group_name
470 desc = '%s group' % group_name
471
471
472 # skip folders that are now removed repos
472 # skip folders that are now removed repos
473 if REMOVED_REPO_PAT.match(group_name):
473 if REMOVED_REPO_PAT.match(group_name):
474 break
474 break
475
475
476 if group is None:
476 if group is None:
477 log.debug('creating group level: %s group_name: %s',
477 log.debug('creating group level: %s group_name: %s',
478 lvl, group_name)
478 lvl, group_name)
479 group = RepoGroup(group_name, parent)
479 group = RepoGroup(group_name, parent)
480 group.group_description = desc
480 group.group_description = desc
481 group.user = owner
481 group.user = owner
482 sa.add(group)
482 sa.add(group)
483 perm_obj = rgm._create_default_perms(group)
483 perm_obj = rgm._create_default_perms(group)
484 sa.add(perm_obj)
484 sa.add(perm_obj)
485 sa.flush()
485 sa.flush()
486
486
487 parent = group
487 parent = group
488 return group
488 return group
489
489
490
490
491 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
491 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
492 """
492 """
493 maps all repos given in initial_repo_list, non existing repositories
493 maps all repos given in initial_repo_list, non existing repositories
494 are created, if remove_obsolete is True it also checks for db entries
494 are created, if remove_obsolete is True it also checks for db entries
495 that are not in initial_repo_list and removes them.
495 that are not in initial_repo_list and removes them.
496
496
497 :param initial_repo_list: list of repositories found by scanning methods
497 :param initial_repo_list: list of repositories found by scanning methods
498 :param remove_obsolete: check for obsolete entries in database
498 :param remove_obsolete: check for obsolete entries in database
499 """
499 """
500 from rhodecode.model.repo import RepoModel
500 from rhodecode.model.repo import RepoModel
501 from rhodecode.model.scm import ScmModel
501 from rhodecode.model.scm import ScmModel
502 from rhodecode.model.repo_group import RepoGroupModel
502 from rhodecode.model.repo_group import RepoGroupModel
503 from rhodecode.model.settings import SettingsModel
503 from rhodecode.model.settings import SettingsModel
504
504
505 sa = meta.Session()
505 sa = meta.Session()
506 repo_model = RepoModel()
506 repo_model = RepoModel()
507 user = User.get_first_super_admin()
507 user = User.get_first_super_admin()
508 added = []
508 added = []
509
509
510 # creation defaults
510 # creation defaults
511 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
511 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
512 enable_statistics = defs.get('repo_enable_statistics')
512 enable_statistics = defs.get('repo_enable_statistics')
513 enable_locking = defs.get('repo_enable_locking')
513 enable_locking = defs.get('repo_enable_locking')
514 enable_downloads = defs.get('repo_enable_downloads')
514 enable_downloads = defs.get('repo_enable_downloads')
515 private = defs.get('repo_private')
515 private = defs.get('repo_private')
516
516
517 for name, repo in initial_repo_list.items():
517 for name, repo in initial_repo_list.items():
518 group = map_groups(name)
518 group = map_groups(name)
519 unicode_name = safe_unicode(name)
519 unicode_name = safe_unicode(name)
520 db_repo = repo_model.get_by_repo_name(unicode_name)
520 db_repo = repo_model.get_by_repo_name(unicode_name)
521 # found repo that is on filesystem not in RhodeCode database
521 # found repo that is on filesystem not in RhodeCode database
522 if not db_repo:
522 if not db_repo:
523 log.info('repository %s not found, creating now', name)
523 log.info('repository %s not found, creating now', name)
524 added.append(name)
524 added.append(name)
525 desc = (repo.description
525 desc = (repo.description
526 if repo.description != 'unknown'
526 if repo.description != 'unknown'
527 else '%s repository' % name)
527 else '%s repository' % name)
528
528
529 db_repo = repo_model._create_repo(
529 db_repo = repo_model._create_repo(
530 repo_name=name,
530 repo_name=name,
531 repo_type=repo.alias,
531 repo_type=repo.alias,
532 description=desc,
532 description=desc,
533 repo_group=getattr(group, 'group_id', None),
533 repo_group=getattr(group, 'group_id', None),
534 owner=user,
534 owner=user,
535 enable_locking=enable_locking,
535 enable_locking=enable_locking,
536 enable_downloads=enable_downloads,
536 enable_downloads=enable_downloads,
537 enable_statistics=enable_statistics,
537 enable_statistics=enable_statistics,
538 private=private,
538 private=private,
539 state=Repository.STATE_CREATED
539 state=Repository.STATE_CREATED
540 )
540 )
541 sa.commit()
541 sa.commit()
542 # we added that repo just now, and make sure we updated server info
542 # we added that repo just now, and make sure we updated server info
543 if db_repo.repo_type == 'git':
543 if db_repo.repo_type == 'git':
544 git_repo = db_repo.scm_instance()
544 git_repo = db_repo.scm_instance()
545 # update repository server-info
545 # update repository server-info
546 log.debug('Running update server info')
546 log.debug('Running update server info')
547 git_repo._update_server_info()
547 git_repo._update_server_info()
548
548
549 db_repo.update_commit_cache()
549 db_repo.update_commit_cache()
550
550
551 config = db_repo._config
551 config = db_repo._config
552 config.set('extensions', 'largefiles', '')
552 config.set('extensions', 'largefiles', '')
553 ScmModel().install_hooks(
553 ScmModel().install_hooks(
554 db_repo.scm_instance(config=config),
554 db_repo.scm_instance(config=config),
555 repo_type=db_repo.repo_type)
555 repo_type=db_repo.repo_type)
556
556
557 removed = []
557 removed = []
558 if remove_obsolete:
558 if remove_obsolete:
559 # remove from database those repositories that are not in the filesystem
559 # remove from database those repositories that are not in the filesystem
560 for repo in sa.query(Repository).all():
560 for repo in sa.query(Repository).all():
561 if repo.repo_name not in initial_repo_list.keys():
561 if repo.repo_name not in initial_repo_list.keys():
562 log.debug("Removing non-existing repository found in db `%s`",
562 log.debug("Removing non-existing repository found in db `%s`",
563 repo.repo_name)
563 repo.repo_name)
564 try:
564 try:
565 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
565 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
566 sa.commit()
566 sa.commit()
567 removed.append(repo.repo_name)
567 removed.append(repo.repo_name)
568 except Exception:
568 except Exception:
569 # don't hold further removals on error
569 # don't hold further removals on error
570 log.error(traceback.format_exc())
570 log.error(traceback.format_exc())
571 sa.rollback()
571 sa.rollback()
572
572
573 def splitter(full_repo_name):
573 def splitter(full_repo_name):
574 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
574 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
575 gr_name = None
575 gr_name = None
576 if len(_parts) == 2:
576 if len(_parts) == 2:
577 gr_name = _parts[0]
577 gr_name = _parts[0]
578 return gr_name
578 return gr_name
579
579
580 initial_repo_group_list = [splitter(x) for x in
580 initial_repo_group_list = [splitter(x) for x in
581 initial_repo_list.keys() if splitter(x)]
581 initial_repo_list.keys() if splitter(x)]
582
582
583 # remove from database those repository groups that are not in the
583 # remove from database those repository groups that are not in the
584 # filesystem due to parent child relationships we need to delete them
584 # filesystem due to parent child relationships we need to delete them
585 # in a specific order of most nested first
585 # in a specific order of most nested first
586 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
586 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
587 nested_sort = lambda gr: len(gr.split('/'))
587 nested_sort = lambda gr: len(gr.split('/'))
588 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
588 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
589 if group_name not in initial_repo_group_list:
589 if group_name not in initial_repo_group_list:
590 repo_group = RepoGroup.get_by_group_name(group_name)
590 repo_group = RepoGroup.get_by_group_name(group_name)
591 if (repo_group.children.all() or
591 if (repo_group.children.all() or
592 not RepoGroupModel().check_exist_filesystem(
592 not RepoGroupModel().check_exist_filesystem(
593 group_name=group_name, exc_on_failure=False)):
593 group_name=group_name, exc_on_failure=False)):
594 continue
594 continue
595
595
596 log.info(
596 log.info(
597 'Removing non-existing repository group found in db `%s`',
597 'Removing non-existing repository group found in db `%s`',
598 group_name)
598 group_name)
599 try:
599 try:
600 RepoGroupModel(sa).delete(group_name, fs_remove=False)
600 RepoGroupModel(sa).delete(group_name, fs_remove=False)
601 sa.commit()
601 sa.commit()
602 removed.append(group_name)
602 removed.append(group_name)
603 except Exception:
603 except Exception:
604 # don't hold further removals on error
604 # don't hold further removals on error
605 log.exception(
605 log.exception(
606 'Unable to remove repository group `%s`',
606 'Unable to remove repository group `%s`',
607 group_name)
607 group_name)
608 sa.rollback()
608 sa.rollback()
609 raise
609 raise
610
610
611 return added, removed
611 return added, removed
612
612
613
613
614 def get_default_cache_settings(settings):
614 def get_default_cache_settings(settings):
615 cache_settings = {}
615 cache_settings = {}
616 for key in settings.keys():
616 for key in settings.keys():
617 for prefix in ['beaker.cache.', 'cache.']:
617 for prefix in ['beaker.cache.', 'cache.']:
618 if key.startswith(prefix):
618 if key.startswith(prefix):
619 name = key.split(prefix)[1].strip()
619 name = key.split(prefix)[1].strip()
620 cache_settings[name] = settings[key].strip()
620 cache_settings[name] = settings[key].strip()
621 return cache_settings
621 return cache_settings
622
622
623
623
624 # set cache regions for beaker so celery can utilise it
624 # set cache regions for beaker so celery can utilise it
625 def add_cache(settings):
625 def add_cache(settings):
626 from rhodecode.lib import caches
626 from rhodecode.lib import caches
627 cache_settings = {'regions': None}
627 cache_settings = {'regions': None}
628 # main cache settings used as default ...
628 # main cache settings used as default ...
629 cache_settings.update(get_default_cache_settings(settings))
629 cache_settings.update(get_default_cache_settings(settings))
630
630
631 if cache_settings['regions']:
631 if cache_settings['regions']:
632 for region in cache_settings['regions'].split(','):
632 for region in cache_settings['regions'].split(','):
633 region = region.strip()
633 region = region.strip()
634 region_settings = {}
634 region_settings = {}
635 for key, value in cache_settings.items():
635 for key, value in cache_settings.items():
636 if key.startswith(region):
636 if key.startswith(region):
637 region_settings[key.split('.')[1]] = value
637 region_settings[key.split('.')[1]] = value
638
638
639 caches.configure_cache_region(
639 caches.configure_cache_region(
640 region, region_settings, cache_settings)
640 region, region_settings, cache_settings)
641
641
642
642
643 def load_rcextensions(root_path):
643 def load_rcextensions(root_path):
644 import rhodecode
644 import rhodecode
645 from rhodecode.config import conf
645 from rhodecode.config import conf
646
646
647 path = os.path.join(root_path, 'rcextensions', '__init__.py')
647 path = os.path.join(root_path, 'rcextensions', '__init__.py')
648 if os.path.isfile(path):
648 if os.path.isfile(path):
649 rcext = create_module('rc', path)
649 rcext = create_module('rc', path)
650 EXT = rhodecode.EXTENSIONS = rcext
650 EXT = rhodecode.EXTENSIONS = rcext
651 log.debug('Found rcextensions now loading %s...', rcext)
651 log.debug('Found rcextensions now loading %s...', rcext)
652
652
653 # Additional mappings that are not present in the pygments lexers
653 # Additional mappings that are not present in the pygments lexers
654 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
654 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
655
655
656 # auto check if the module is not missing any data, set to default if is
656 # auto check if the module is not missing any data, set to default if is
657 # this will help autoupdate new feature of rcext module
657 # this will help autoupdate new feature of rcext module
658 #from rhodecode.config import rcextensions
658 #from rhodecode.config import rcextensions
659 #for k in dir(rcextensions):
659 #for k in dir(rcextensions):
660 # if not k.startswith('_') and not hasattr(EXT, k):
660 # if not k.startswith('_') and not hasattr(EXT, k):
661 # setattr(EXT, k, getattr(rcextensions, k))
661 # setattr(EXT, k, getattr(rcextensions, k))
662
662
663
663
664 def get_custom_lexer(extension):
664 def get_custom_lexer(extension):
665 """
665 """
666 returns a custom lexer if it is defined in rcextensions module, or None
666 returns a custom lexer if it is defined in rcextensions module, or None
667 if there's no custom lexer defined
667 if there's no custom lexer defined
668 """
668 """
669 import rhodecode
669 import rhodecode
670 from pygments import lexers
670 from pygments import lexers
671
671
672 # custom override made by RhodeCode
672 # custom override made by RhodeCode
673 if extension in ['mako']:
673 if extension in ['mako']:
674 return lexers.get_lexer_by_name('html+mako')
674 return lexers.get_lexer_by_name('html+mako')
675
675
676 # check if we didn't define this extension as other lexer
676 # check if we didn't define this extension as other lexer
677 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
677 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
678 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
678 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
679 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
679 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
680 return lexers.get_lexer_by_name(_lexer_name)
680 return lexers.get_lexer_by_name(_lexer_name)
681
681
682
682
683 #==============================================================================
683 #==============================================================================
684 # TEST FUNCTIONS AND CREATORS
684 # TEST FUNCTIONS AND CREATORS
685 #==============================================================================
685 #==============================================================================
686 def create_test_index(repo_location, config):
686 def create_test_index(repo_location, config):
687 """
687 """
688 Makes default test index.
688 Makes default test index.
689 """
689 """
690 import rc_testdata
690 import rc_testdata
691
691
692 rc_testdata.extract_search_index(
692 rc_testdata.extract_search_index(
693 'vcs_search_index', os.path.dirname(config['search.location']))
693 'vcs_search_index', os.path.dirname(config['search.location']))
694
694
695
695
696 def create_test_directory(test_path):
696 def create_test_directory(test_path):
697 """
697 """
698 Create test directory if it doesn't exist.
698 Create test directory if it doesn't exist.
699 """
699 """
700 if not os.path.isdir(test_path):
700 if not os.path.isdir(test_path):
701 log.debug('Creating testdir %s', test_path)
701 log.debug('Creating testdir %s', test_path)
702 os.makedirs(test_path)
702 os.makedirs(test_path)
703
703
704
704
705 def create_test_database(test_path, config):
705 def create_test_database(test_path, config):
706 """
706 """
707 Makes a fresh database.
707 Makes a fresh database.
708 """
708 """
709 from rhodecode.lib.db_manage import DbManage
709 from rhodecode.lib.db_manage import DbManage
710
710
711 # PART ONE create db
711 # PART ONE create db
712 dbconf = config['sqlalchemy.db1.url']
712 dbconf = config['sqlalchemy.db1.url']
713 log.debug('making test db %s', dbconf)
713 log.debug('making test db %s', dbconf)
714
714
715 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
715 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
716 tests=True, cli_args={'force_ask': True})
716 tests=True, cli_args={'force_ask': True})
717 dbmanage.create_tables(override=True)
717 dbmanage.create_tables(override=True)
718 dbmanage.set_db_version()
718 dbmanage.set_db_version()
719 # for tests dynamically set new root paths based on generated content
719 # for tests dynamically set new root paths based on generated content
720 dbmanage.create_settings(dbmanage.config_prompt(test_path))
720 dbmanage.create_settings(dbmanage.config_prompt(test_path))
721 dbmanage.create_default_user()
721 dbmanage.create_default_user()
722 dbmanage.create_test_admin_and_users()
722 dbmanage.create_test_admin_and_users()
723 dbmanage.create_permissions()
723 dbmanage.create_permissions()
724 dbmanage.populate_default_permissions()
724 dbmanage.populate_default_permissions()
725 Session().commit()
725 Session().commit()
726
726
727
727
728 def create_test_repositories(test_path, config):
728 def create_test_repositories(test_path, config):
729 """
729 """
730 Creates test repositories in the temporary directory. Repositories are
730 Creates test repositories in the temporary directory. Repositories are
731 extracted from archives within the rc_testdata package.
731 extracted from archives within the rc_testdata package.
732 """
732 """
733 import rc_testdata
733 import rc_testdata
734 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
734 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
735
735
736 log.debug('making test vcs repositories')
736 log.debug('making test vcs repositories')
737
737
738 idx_path = config['search.location']
738 idx_path = config['search.location']
739 data_path = config['cache_dir']
739 data_path = config['cache_dir']
740
740
741 # clean index and data
741 # clean index and data
742 if idx_path and os.path.exists(idx_path):
742 if idx_path and os.path.exists(idx_path):
743 log.debug('remove %s', idx_path)
743 log.debug('remove %s', idx_path)
744 shutil.rmtree(idx_path)
744 shutil.rmtree(idx_path)
745
745
746 if data_path and os.path.exists(data_path):
746 if data_path and os.path.exists(data_path):
747 log.debug('remove %s', data_path)
747 log.debug('remove %s', data_path)
748 shutil.rmtree(data_path)
748 shutil.rmtree(data_path)
749
749
750 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
750 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
751 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
751 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
752
752
753 # Note: Subversion is in the process of being integrated with the system,
753 # Note: Subversion is in the process of being integrated with the system,
754 # until we have a properly packed version of the test svn repository, this
754 # until we have a properly packed version of the test svn repository, this
755 # tries to copy over the repo from a package "rc_testdata"
755 # tries to copy over the repo from a package "rc_testdata"
756 svn_repo_path = rc_testdata.get_svn_repo_archive()
756 svn_repo_path = rc_testdata.get_svn_repo_archive()
757 with tarfile.open(svn_repo_path) as tar:
757 with tarfile.open(svn_repo_path) as tar:
758 tar.extractall(jn(test_path, SVN_REPO))
758 tar.extractall(jn(test_path, SVN_REPO))
759
759
760
760
761 #==============================================================================
761 #==============================================================================
762 # PASTER COMMANDS
762 # PASTER COMMANDS
763 #==============================================================================
763 #==============================================================================
764 class BasePasterCommand(Command):
764 class BasePasterCommand(Command):
765 """
765 """
766 Abstract Base Class for paster commands.
766 Abstract Base Class for paster commands.
767
767
768 The celery commands are somewhat aggressive about loading
768 The celery commands are somewhat aggressive about loading
769 celery.conf, and since our module sets the `CELERY_LOADER`
769 celery.conf, and since our module sets the `CELERY_LOADER`
770 environment variable to our loader, we have to bootstrap a bit and
770 environment variable to our loader, we have to bootstrap a bit and
771 make sure we've had a chance to load the pylons config off of the
771 make sure we've had a chance to load the pylons config off of the
772 command line, otherwise everything fails.
772 command line, otherwise everything fails.
773 """
773 """
774 min_args = 1
774 min_args = 1
775 min_args_error = "Please provide a paster config file as an argument."
775 min_args_error = "Please provide a paster config file as an argument."
776 takes_config_file = 1
776 takes_config_file = 1
777 requires_config_file = True
777 requires_config_file = True
778
778
779 def notify_msg(self, msg, log=False):
779 def notify_msg(self, msg, log=False):
780 """Make a notification to user, additionally if logger is passed
780 """Make a notification to user, additionally if logger is passed
781 it logs this action using given logger
781 it logs this action using given logger
782
782
783 :param msg: message that will be printed to user
783 :param msg: message that will be printed to user
784 :param log: logging instance, to use to additionally log this message
784 :param log: logging instance, to use to additionally log this message
785
785
786 """
786 """
787 if log and isinstance(log, logging):
787 if log and isinstance(log, logging):
788 log(msg)
788 log(msg)
789
789
790 def run(self, args):
790 def run(self, args):
791 """
791 """
792 Overrides Command.run
792 Overrides Command.run
793
793
794 Checks for a config file argument and loads it.
794 Checks for a config file argument and loads it.
795 """
795 """
796 if len(args) < self.min_args:
796 if len(args) < self.min_args:
797 raise BadCommand(
797 raise BadCommand(
798 self.min_args_error % {'min_args': self.min_args,
798 self.min_args_error % {'min_args': self.min_args,
799 'actual_args': len(args)})
799 'actual_args': len(args)})
800
800
801 # Decrement because we're going to lob off the first argument.
801 # Decrement because we're going to lob off the first argument.
802 # @@ This is hacky
802 # @@ This is hacky
803 self.min_args -= 1
803 self.min_args -= 1
804 self.bootstrap_config(args[0])
804 self.bootstrap_config(args[0])
805 self.update_parser()
805 self.update_parser()
806 return super(BasePasterCommand, self).run(args[1:])
806 return super(BasePasterCommand, self).run(args[1:])
807
807
808 def update_parser(self):
808 def update_parser(self):
809 """
809 """
810 Abstract method. Allows for the class' parser to be updated
810 Abstract method. Allows for the class' parser to be updated
811 before the superclass' `run` method is called. Necessary to
811 before the superclass' `run` method is called. Necessary to
812 allow options/arguments to be passed through to the underlying
812 allow options/arguments to be passed through to the underlying
813 celery command.
813 celery command.
814 """
814 """
815 raise NotImplementedError("Abstract Method.")
815 raise NotImplementedError("Abstract Method.")
816
816
817 def bootstrap_config(self, conf):
817 def bootstrap_config(self, conf):
818 """
818 """
819 Loads the pylons configuration.
819 Loads the pylons configuration.
820 """
820 """
821 from pylons import config as pylonsconfig
821 from pylons import config as pylonsconfig
822
822
823 self.path_to_ini_file = os.path.realpath(conf)
823 self.path_to_ini_file = os.path.realpath(conf)
824 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
824 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
825 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
825 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
826
826
827 def _init_session(self):
827 def _init_session(self):
828 """
828 """
829 Inits SqlAlchemy Session
829 Inits SqlAlchemy Session
830 """
830 """
831 logging.config.fileConfig(self.path_to_ini_file)
831 logging.config.fileConfig(self.path_to_ini_file)
832 from pylons import config
832 from pylons import config
833 from rhodecode.config.utils import initialize_database
833 from rhodecode.config.utils import initialize_database
834
834
835 # get to remove repos !!
835 # get to remove repos !!
836 add_cache(config)
836 add_cache(config)
837 initialize_database(config)
837 initialize_database(config)
838
838
839
839
840 @decorator.decorator
840 @decorator.decorator
841 def jsonify(func, *args, **kwargs):
841 def jsonify(func, *args, **kwargs):
842 """Action decorator that formats output for JSON
842 """Action decorator that formats output for JSON
843
843
844 Given a function that will return content, this decorator will turn
844 Given a function that will return content, this decorator will turn
845 the result into JSON, with a content-type of 'application/json' and
845 the result into JSON, with a content-type of 'application/json' and
846 output it.
846 output it.
847
847
848 """
848 """
849 from pylons.decorators.util import get_pylons
849 from pylons.decorators.util import get_pylons
850 from rhodecode.lib.ext_json import json
850 from rhodecode.lib.ext_json import json
851 pylons = get_pylons(args)
851 pylons = get_pylons(args)
852 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
852 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
853 data = func(*args, **kwargs)
853 data = func(*args, **kwargs)
854 if isinstance(data, (list, tuple)):
854 if isinstance(data, (list, tuple)):
855 msg = "JSON responses with Array envelopes are susceptible to " \
855 msg = "JSON responses with Array envelopes are susceptible to " \
856 "cross-site data leak attacks, see " \
856 "cross-site data leak attacks, see " \
857 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
857 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
858 warnings.warn(msg, Warning, 2)
858 warnings.warn(msg, Warning, 2)
859 log.warning(msg)
859 log.warning(msg)
860 log.debug("Returning JSON wrapped action output")
860 log.debug("Returning JSON wrapped action output")
861 return json.dumps(data, encoding='utf-8')
861 return json.dumps(data, encoding='utf-8')
862
862
863
863
864 class PartialRenderer(object):
864 class PartialRenderer(object):
865 """
865 """
866 Partial renderer used to render chunks of html used in datagrids
866 Partial renderer used to render chunks of html used in datagrids
867 use like::
867 use like::
868
868
869 _render = PartialRenderer('data_table/_dt_elements.mako')
869 _render = PartialRenderer('data_table/_dt_elements.mako')
870 _render('quick_menu', args, kwargs)
870 _render('quick_menu', args, kwargs)
871 PartialRenderer.h,
871 PartialRenderer.h,
872 c,
872 c,
873 _,
873 _,
874 ungettext
874 ungettext
875 are the template stuff initialized inside and can be re-used later
875 are the template stuff initialized inside and can be re-used later
876
876
877 :param tmpl_name: template path relate to /templates/ dir
877 :param tmpl_name: template path relate to /templates/ dir
878 """
878 """
879
879
880 def __init__(self, tmpl_name):
880 def __init__(self, tmpl_name):
881 import rhodecode
881 import rhodecode
882 from pylons import request, tmpl_context as c
882 from pylons import request, tmpl_context as c
883 from pylons.i18n.translation import _, ungettext
883 from pylons.i18n.translation import _, ungettext
884 from rhodecode.lib import helpers as h
884 from rhodecode.lib import helpers as h
885
885
886 self.tmpl_name = tmpl_name
886 self.tmpl_name = tmpl_name
887 self.rhodecode = rhodecode
887 self.rhodecode = rhodecode
888 self.c = c
888 self.c = c
889 self._ = _
889 self._ = _
890 self.ungettext = ungettext
890 self.ungettext = ungettext
891 self.h = h
891 self.h = h
892 self.request = request
892 self.request = request
893
893
894 def _mako_lookup(self):
894 def _mako_lookup(self):
895 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
895 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
896 return _tmpl_lookup.get_template(self.tmpl_name)
896 return _tmpl_lookup.get_template(self.tmpl_name)
897
897
898 def _update_kwargs_for_render(self, kwargs):
898 def _update_kwargs_for_render(self, kwargs):
899 """
899 """
900 Inject params required for Mako rendering
900 Inject params required for Mako rendering
901 """
901 """
902 _kwargs = {
902 _kwargs = {
903 '_': self._,
903 '_': self._,
904 'h': self.h,
904 'h': self.h,
905 'c': self.c,
905 'c': self.c,
906 'request': self.request,
906 'request': self.request,
907 '_ungettext': self.ungettext,
907 '_ungettext': self.ungettext,
908 }
908 }
909 _kwargs.update(kwargs)
909 _kwargs.update(kwargs)
910 return _kwargs
910 return _kwargs
911
911
912 def _render_with_exc(self, render_func, args, kwargs):
912 def _render_with_exc(self, render_func, args, kwargs):
913 try:
913 try:
914 return render_func.render(*args, **kwargs)
914 return render_func.render(*args, **kwargs)
915 except:
915 except:
916 log.error(exceptions.text_error_template().render())
916 log.error(exceptions.text_error_template().render())
917 raise
917 raise
918
918
919 def _get_template(self, template_obj, def_name):
919 def _get_template(self, template_obj, def_name):
920 if def_name:
920 if def_name:
921 tmpl = template_obj.get_def(def_name)
921 tmpl = template_obj.get_def(def_name)
922 else:
922 else:
923 tmpl = template_obj
923 tmpl = template_obj
924 return tmpl
924 return tmpl
925
925
926 def render(self, def_name, *args, **kwargs):
926 def render(self, def_name, *args, **kwargs):
927 lookup_obj = self._mako_lookup()
927 lookup_obj = self._mako_lookup()
928 tmpl = self._get_template(lookup_obj, def_name=def_name)
928 tmpl = self._get_template(lookup_obj, def_name=def_name)
929 kwargs = self._update_kwargs_for_render(kwargs)
929 kwargs = self._update_kwargs_for_render(kwargs)
930 return self._render_with_exc(tmpl, args, kwargs)
930 return self._render_with_exc(tmpl, args, kwargs)
931
931
932 def __call__(self, tmpl, *args, **kwargs):
932 def __call__(self, tmpl, *args, **kwargs):
933 return self.render(tmpl, *args, **kwargs)
933 return self.render(tmpl, *args, **kwargs)
934
934
935
935
936 def password_changed(auth_user, session):
936 def password_changed(auth_user, session):
937 # Never report password change in case of default user or anonymous user.
937 # Never report password change in case of default user or anonymous user.
938 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
938 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
939 return False
939 return False
940
940
941 password_hash = md5(auth_user.password) if auth_user.password else None
941 password_hash = md5(auth_user.password) if auth_user.password else None
942 rhodecode_user = session.get('rhodecode_user', {})
942 rhodecode_user = session.get('rhodecode_user', {})
943 session_password_hash = rhodecode_user.get('password', '')
943 session_password_hash = rhodecode_user.get('password', '')
944 return password_hash != session_password_hash
944 return password_hash != session_password_hash
945
945
946
946
947 def read_opensource_licenses():
947 def read_opensource_licenses():
948 global _license_cache
948 global _license_cache
949
949
950 if not _license_cache:
950 if not _license_cache:
951 licenses = pkg_resources.resource_string(
951 licenses = pkg_resources.resource_string(
952 'rhodecode', 'config/licenses.json')
952 'rhodecode', 'config/licenses.json')
953 _license_cache = json.loads(licenses)
953 _license_cache = json.loads(licenses)
954
954
955 return _license_cache
955 return _license_cache
956
956
957
957
958 def get_registry(request):
958 def get_registry(request):
959 """
959 """
960 Utility to get the pyramid registry from a request. During migration to
960 Utility to get the pyramid registry from a request. During migration to
961 pyramid we sometimes want to use the pyramid registry from pylons context.
961 pyramid we sometimes want to use the pyramid registry from pylons context.
962 Therefore this utility returns `request.registry` for pyramid requests and
962 Therefore this utility returns `request.registry` for pyramid requests and
963 uses `get_current_registry()` for pylons requests.
963 uses `get_current_registry()` for pylons requests.
964 """
964 """
965 try:
965 try:
966 return request.registry
966 return request.registry
967 except AttributeError:
967 except AttributeError:
968 return get_current_registry()
968 return get_current_registry()
969
969
970
970
971 def generate_platform_uuid():
971 def generate_platform_uuid():
972 """
972 """
973 Generates platform UUID based on it's name
973 Generates platform UUID based on it's name
974 """
974 """
975 import platform
975 import platform
976
976
977 try:
977 try:
978 uuid_list = [platform.platform()]
978 uuid_list = [platform.platform()]
979 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
979 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
980 except Exception as e:
980 except Exception as e:
981 log.error('Failed to generate host uuid: %s' % e)
981 log.error('Failed to generate host uuid: %s' % e)
982 return 'UNDEFINED'
982 return 'UNDEFINED'
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,218 +1,220 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('favicon', '/favicon.ico', []);
17 pyroutes.register('favicon', '/favicon.ico', []);
19 pyroutes.register('robots', '/robots.txt', []);
18 pyroutes.register('robots', '/robots.txt', []);
20 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
21 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
22 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
23 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
24 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
25 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
24 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
26 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
27 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
27 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
29 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
30 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
29 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
31 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
30 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
32 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
32 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
34 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
35 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
34 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
36 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
35 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
37 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
38 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
39 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
40 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
43 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
44 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
52 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
53 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
54 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
55 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
56 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
57 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
58 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
59 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
60 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
61 pyroutes.register('users', '/_admin/users', []);
60 pyroutes.register('users', '/_admin/users', []);
62 pyroutes.register('users_data', '/_admin/users_data', []);
61 pyroutes.register('users_data', '/_admin/users_data', []);
63 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
62 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
63 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
65 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
66 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
65 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
67 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
66 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
68 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
67 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
69 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
68 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
70 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
69 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
71 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
70 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
72 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
71 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
73 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
72 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
74 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
73 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
74 pyroutes.register('user_groups', '/_admin/user_groups', []);
75 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
76 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
75 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
77 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
76 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
78 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
77 pyroutes.register('channelstream_proxy', '/_channelstream', []);
79 pyroutes.register('channelstream_proxy', '/_channelstream', []);
78 pyroutes.register('login', '/_admin/login', []);
80 pyroutes.register('login', '/_admin/login', []);
79 pyroutes.register('logout', '/_admin/logout', []);
81 pyroutes.register('logout', '/_admin/logout', []);
80 pyroutes.register('register', '/_admin/register', []);
82 pyroutes.register('register', '/_admin/register', []);
81 pyroutes.register('reset_password', '/_admin/password_reset', []);
83 pyroutes.register('reset_password', '/_admin/password_reset', []);
82 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
84 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
83 pyroutes.register('home', '/', []);
85 pyroutes.register('home', '/', []);
84 pyroutes.register('user_autocomplete_data', '/_users', []);
86 pyroutes.register('user_autocomplete_data', '/_users', []);
85 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
87 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
86 pyroutes.register('repo_list_data', '/_repos', []);
88 pyroutes.register('repo_list_data', '/_repos', []);
87 pyroutes.register('goto_switcher_data', '/_goto_data', []);
89 pyroutes.register('goto_switcher_data', '/_goto_data', []);
88 pyroutes.register('journal', '/_admin/journal', []);
90 pyroutes.register('journal', '/_admin/journal', []);
89 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
91 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
90 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
92 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
91 pyroutes.register('journal_public', '/_admin/public_journal', []);
93 pyroutes.register('journal_public', '/_admin/public_journal', []);
92 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
94 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
93 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
95 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
94 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
96 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
95 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
97 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
96 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
98 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
97 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
99 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
98 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
100 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
99 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
101 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
100 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
102 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
101 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
103 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
102 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
104 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
103 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
105 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
104 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
105 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
109 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
110 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
109 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
111 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
110 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
112 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
111 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
113 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
112 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
114 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
113 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
117 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
116 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
135 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
134 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
136 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
135 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
137 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
136 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
138 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
137 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
140 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
139 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
141 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
140 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
142 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
141 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
143 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
142 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
144 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
143 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
145 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
144 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
146 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
145 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
147 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
146 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
148 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
147 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
149 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
148 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
150 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
149 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
151 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
150 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
152 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
151 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
153 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
152 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
154 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
153 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
154 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
156 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
157 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
156 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
158 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
157 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
159 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
158 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
160 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
159 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
161 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
160 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
162 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
161 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
163 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
162 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
164 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
163 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
165 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
164 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
166 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
165 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
167 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
166 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
168 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
167 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
169 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
168 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
170 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
169 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
171 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
170 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
172 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
171 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
173 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
172 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
174 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
173 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
175 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
174 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
176 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
175 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
177 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
176 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
178 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
177 pyroutes.register('search', '/_admin/search', []);
179 pyroutes.register('search', '/_admin/search', []);
178 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
180 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
179 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
181 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
180 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
182 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
181 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
183 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
182 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
184 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
183 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
185 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
184 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
186 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
185 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
187 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
186 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
188 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
187 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
189 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
188 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
190 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
189 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
191 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
190 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
192 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
191 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
193 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
192 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
194 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
193 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
195 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
194 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
196 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
195 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
197 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
196 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
198 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
197 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
199 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
198 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
200 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
199 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
201 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
200 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
202 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
201 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
203 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
202 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
204 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
203 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
205 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
204 pyroutes.register('gists_show', '/_admin/gists', []);
206 pyroutes.register('gists_show', '/_admin/gists', []);
205 pyroutes.register('gists_new', '/_admin/gists/new', []);
207 pyroutes.register('gists_new', '/_admin/gists/new', []);
206 pyroutes.register('gists_create', '/_admin/gists/create', []);
208 pyroutes.register('gists_create', '/_admin/gists/create', []);
207 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
209 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
208 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
210 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
209 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
211 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
210 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
212 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
211 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
213 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
212 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
214 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
213 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
215 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
214 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
216 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
215 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
217 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
216 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
218 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
217 pyroutes.register('apiv2', '/_admin/api', []);
219 pyroutes.register('apiv2', '/_admin/api', []);
218 }
220 }
@@ -1,186 +1,186 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
9 ${h.secure_form(h.url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13 <div class="field">
13 <div class="field">
14 <div class="label">
14 <div class="label">
15 <label for="users_group_name">${_('Group name')}:</label>
15 <label for="users_group_name">${_('Group name')}:</label>
16 </div>
16 </div>
17 <div class="input">
17 <div class="input">
18 ${h.text('users_group_name',class_='medium')}
18 ${h.text('users_group_name',class_='medium')}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field badged-field">
22 <div class="field badged-field">
23 <div class="label">
23 <div class="label">
24 <label for="user">${_('Owner')}:</label>
24 <label for="user">${_('Owner')}:</label>
25 </div>
25 </div>
26 <div class="input">
26 <div class="input">
27 <div class="badge-input-container">
27 <div class="badge-input-container">
28 <div class="user-badge">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
30 </div>
30 </div>
31 <div class="badge-input-wrap">
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
33 </div>
34 </div>
34 </div>
35 <form:error name="user"/>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="user_group_description">${_('Description')}:</label>
42 <label for="user_group_description">${_('Description')}:</label>
43 </div>
43 </div>
44 <div class="textarea textarea-small editor">
44 <div class="textarea textarea-small editor">
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
47 </div>
47 </div>
48 </div>
48 </div>
49 <div class="field">
49 <div class="field">
50 <div class="label label-checkbox">
50 <div class="label label-checkbox">
51 <label for="users_group_active">${_('Active')}:</label>
51 <label for="users_group_active">${_('Active')}:</label>
52 </div>
52 </div>
53 <div class="checkboxes">
53 <div class="checkboxes">
54 ${h.checkbox('users_group_active',value=True)}
54 ${h.checkbox('users_group_active',value=True)}
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="field">
58 <div class="field">
59 <div class="label label-checkbox">
59 <div class="label label-checkbox">
60 <label for="users_group_active">${_('Add members')}:</label>
60 <label for="users_group_active">${_('Add members')}:</label>
61 </div>
61 </div>
62 <div class="input">
62 <div class="input">
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
68 <table id="group_members_placeholder" class="rctable group_members">
68 <table id="group_members_placeholder" class="rctable group_members">
69 <tr>
69 <tr>
70 <th>${_('Username')}</th>
70 <th>${_('Username')}</th>
71 <th>${_('Action')}</th>
71 <th>${_('Action')}</th>
72 </tr>
72 </tr>
73
73
74 % if c.group_members_obj:
74 % if c.group_members_obj:
75 % for user in c.group_members_obj:
75 % for user in c.group_members_obj:
76 <tr>
76 <tr>
77 <td id="member_user_${user.user_id}" class="td-author">
77 <td id="member_user_${user.user_id}" class="td-author">
78 <div class="group_member">
78 <div class="group_member">
79 ${base.gravatar(user.email, 16)}
79 ${base.gravatar(user.email, 16)}
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
81 <input type="hidden" name="__start__" value="member:mapping">
81 <input type="hidden" name="__start__" value="member:mapping">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
84 <input type="hidden" name="__end__" value="member:mapping">
84 <input type="hidden" name="__end__" value="member:mapping">
85 </div>
85 </div>
86 </td>
86 </td>
87 <td class="">
87 <td class="">
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
89 <i class="icon-remove-sign"></i>
89 <i class="icon-remove-sign"></i>
90 </div>
90 </div>
91 </td>
91 </td>
92 </tr>
92 </tr>
93 % endfor
93 % endfor
94
94
95 % else:
95 % else:
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
97 % endif
97 % endif
98 </table>
98 </table>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
100
100
101 <div class="buttons">
101 <div class="buttons">
102 ${h.submit('Save',_('Save'),class_="btn")}
102 ${h.submit('Save',_('Save'),class_="btn")}
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 ${h.end_form()}
106 ${h.end_form()}
107 </div>
107 </div>
108 </div>
108 </div>
109 <script>
109 <script>
110 $(document).ready(function(){
110 $(document).ready(function(){
111 $("#group_parent_id").select2({
111 $("#group_parent_id").select2({
112 'containerCssClass': "drop-menu",
112 'containerCssClass': "drop-menu",
113 'dropdownCssClass': "drop-menu-dropdown",
113 'dropdownCssClass': "drop-menu-dropdown",
114 'dropdownAutoWidth': true
114 'dropdownAutoWidth': true
115 });
115 });
116
116
117 removeUserGroupMember = function(userId){
117 removeUserGroupMember = function(userId){
118 $('#member_'+userId).val('remove');
118 $('#member_'+userId).val('remove');
119 $('#member_user_'+userId).addClass('to-delete');
119 $('#member_user_'+userId).addClass('to-delete');
120 };
120 };
121
121
122 $('#user_group_add_members').autocomplete({
122 $('#user_group_add_members').autocomplete({
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
124 minChars:2,
124 minChars:2,
125 maxHeight:400,
125 maxHeight:400,
126 width:300,
126 width:300,
127 deferRequestBy: 300, //miliseconds
127 deferRequestBy: 300, //miliseconds
128 showNoSuggestionNotice: true,
128 showNoSuggestionNotice: true,
129 params: { user_groups:true },
129 params: { user_groups:true },
130 formatResult: autocompleteFormatResult,
130 formatResult: autocompleteFormatResult,
131 lookupFilter: autocompleteFilterResult,
131 lookupFilter: autocompleteFilterResult,
132 onSelect: function(element, suggestion){
132 onSelect: function(element, suggestion){
133
133
134 function addMember(user, fromUserGroup) {
134 function addMember(user, fromUserGroup) {
135 var gravatar = user.icon_link;
135 var gravatar = user.icon_link;
136 var username = user.value_display;
136 var username = user.value_display;
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
138 var uid = user.id;
138 var uid = user.id;
139
139
140 if (fromUserGroup) {
140 if (fromUserGroup) {
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
142 }
142 }
143
143
144 var elem = $(
144 var elem = $(
145 ('<tr>'+
145 ('<tr>'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
147 '<div class="group_member">'+
147 '<div class="group_member">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
154 '</div>'+
154 '</div>'+
155 '</td>'+
155 '</td>'+
156 '<td class="td-author-new-entry">'+
156 '<td class="td-author-new-entry">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
158 '<i class="icon-remove-sign"></i>'+
158 '<i class="icon-remove-sign"></i>'+
159 '</div>'+
159 '</div>'+
160 '</td>'+
160 '</td>'+
161 '</tr>').format(gravatar, userLink, username,
161 '</tr>').format(gravatar, userLink, username,
162 uid, uid, uid, uid)
162 uid, uid, uid, uid)
163 );
163 );
164 $('#group_members_placeholder').append(elem)
164 $('#group_members_placeholder').append(elem)
165 }
165 }
166
166
167 if (suggestion.value_type == 'user_group') {
167 if (suggestion.value_type == 'user_group') {
168 $.getJSON(
168 $.getJSON(
169 pyroutes.url('edit_user_group_members',
169 pyroutes.url('user_group_members_data',
170 {'user_group_id': suggestion.id}),
170 {'user_group_id': suggestion.id}),
171 function(data) {
171 function(data) {
172 $.each(data.members, function(idx, user) {
172 $.each(data.members, function(idx, user) {
173 addMember(user, suggestion.value)
173 addMember(user, suggestion.value)
174 });
174 });
175 }
175 }
176 );
176 );
177 } else if (suggestion.value_type == 'user') {
177 } else if (suggestion.value_type == 'user') {
178 addMember(suggestion, null);
178 addMember(suggestion, null);
179 }
179 }
180 }
180 }
181 });
181 });
182
182
183
183
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
185 })
185 })
186 </script>
186 </script>
@@ -1,100 +1,108 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 ${_('User groups administration')}
5 ${_('User groups 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.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
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 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 <li>
27 <li>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 </li>
29 </li>
30 %endif
30 %endif
31 </ul>
31 </ul>
32 </div>
32 </div>
33
33
34 <div id="repos_list_wrap">
34 <div id="repos_list_wrap">
35 <table id="user_group_list_table" class="display"></table>
35 <table id="user_group_list_table" class="display"></table>
36 </div>
36 </div>
37
37
38 </div>
38 </div>
39 <script>
39 <script>
40 $(document).ready(function() {
40 $(document).ready(function() {
41 var getDatatableCount = function(){
42 var table = $('#user_group_list_table').dataTable();
43 var page = table.api().page.info();
44 var active = page.recordsDisplay;
45 var total = page.recordsTotal;
41
46
42 var get_datatable_count = function(){
47 var _text = _gettext("{0} out of {1} users").format(active, total);
43 var api = $('#user_group_list_table').dataTable().api();
48 $('#user_group_count').text(_text);
44 $('#user_group_count').text(api.page.info().recordsDisplay);
45 };
49 };
46
50
47 // user list
51 // user list
48 $('#user_group_list_table').DataTable({
52 $('#user_group_list_table').DataTable({
49 data: ${c.data|n},
53 processing: true,
54 serverSide: true,
55 ajax: "${h.route_path('user_groups_data')}",
50 dom: 'rtp',
56 dom: 'rtp',
51 pageLength: ${c.visual.admin_grid_items},
57 pageLength: ${c.visual.admin_grid_items},
52 order: [[ 0, "asc" ]],
58 order: [[ 0, "asc" ]],
53 columns: [
59 columns: [
54 { data: {"_": "group_name",
60 { data: {"_": "users_group_name",
55 "sort": "group_name_raw"}, title: "${_('Name')}", className: "td-componentname" },
61 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
56 { data: {"_": "desc",
62 { data: {"_": "description",
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
63 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
58 { data: {"_": "members",
64 { data: {"_": "members",
59 "sort": "members",
65 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
60 "type": Number}, title: "${_('Members')}", className: "td-number" },
61 { data: {"_": "sync",
66 { data: {"_": "sync",
62 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
67 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
63 { data: {"_": "active",
68 { data: {"_": "active",
64 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
69 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
65 { data: {"_": "owner",
70 { data: {"_": "owner",
66 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
71 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
67 { data: {"_": "action",
72 { data: {"_": "action",
68 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
69 ],
74 ],
70 language: {
75 language: {
71 paginate: DEFAULT_GRID_PAGINATION,
76 paginate: DEFAULT_GRID_PAGINATION,
77 sProcessing: _gettext('loading...'),
72 emptyTable: _gettext("No user groups available yet.")
78 emptyTable: _gettext("No user groups available yet.")
73 },
74 "initComplete": function( settings, json ) {
75 get_datatable_count();
76 }
79 }
77 });
80 });
78
81
79 // update the counter when doing search
82 $('#user_group_list_table').on('xhr.dt', function(e, settings, json, xhr){
80 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
83 $('#user_group_list_table').css('opacity', 1);
81 get_datatable_count();
84 });
85
86 $('#user_group_list_table').on('preXhr.dt', function(e, settings, data){
87 $('#user_group_list_table').css('opacity', 0.3);
82 });
88 });
83
89
84 // filter, filter both grids
90 // refresh counters on draw
85 $('#q_filter').on( 'keyup', function () {
91 $('#user_group_list_table').on('draw.dt', function(){
86 var user_api = $('#user_group_list_table').dataTable().api();
92 getDatatableCount();
87 user_api
88 .columns(0)
89 .search(this.value)
90 .draw();
91 });
93 });
92
94
93 // refilter table if page load via back button
95 // filter
94 $("#q_filter").trigger('keyup');
96 $('#q_filter').on('keyup',
97 $.debounce(250, function() {
98 $('#user_group_list_table').DataTable().search(
99 $('#q_filter').val()
100 ).draw();
101 })
102 );
95
103
96 });
104 });
97
105
98 </script>
106 </script>
99
107
100 </%def>
108 </%def>
@@ -1,270 +1,225 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.tests import (
23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
25 from rhodecode.model.db import User, UserGroup
25 from rhodecode.model.db import User, UserGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 TEST_USER_GROUP = 'admins_test'
29 TEST_USER_GROUP = 'admins_test'
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 class TestAdminUsersGroupsController(TestController):
34 class TestAdminUsersGroupsController(TestController):
35
35
36 def test_index(self):
37 self.log_user()
38 response = self.app.get(url('users_groups'))
39 assert response.status_int == 200
40
41 def test_create(self):
36 def test_create(self):
42 self.log_user()
37 self.log_user()
43 users_group_name = TEST_USER_GROUP
38 users_group_name = TEST_USER_GROUP
44 response = self.app.post(url('users_groups'), {
39 response = self.app.post(url('users_groups'), {
45 'users_group_name': users_group_name,
40 'users_group_name': users_group_name,
46 'user_group_description': 'DESC',
41 'user_group_description': 'DESC',
47 'active': True,
42 'active': True,
48 'csrf_token': self.csrf_token})
43 'csrf_token': self.csrf_token})
49
44
50 user_group_link = link_to(
45 user_group_link = link_to(
51 users_group_name,
46 users_group_name,
52 url('edit_users_group',
47 url('edit_users_group',
53 user_group_id=UserGroup.get_by_group_name(
48 user_group_id=UserGroup.get_by_group_name(
54 users_group_name).users_group_id))
49 users_group_name).users_group_id))
55 assert_session_flash(
50 assert_session_flash(
56 response,
51 response,
57 'Created user group %s' % user_group_link)
52 'Created user group %s' % user_group_link)
58
53
59 def test_set_synchronization(self):
54 def test_set_synchronization(self):
60 self.log_user()
55 self.log_user()
61 users_group_name = TEST_USER_GROUP + 'sync'
56 users_group_name = TEST_USER_GROUP + 'sync'
62 response = self.app.post(url('users_groups'), {
57 response = self.app.post(url('users_groups'), {
63 'users_group_name': users_group_name,
58 'users_group_name': users_group_name,
64 'user_group_description': 'DESC',
59 'user_group_description': 'DESC',
65 'active': True,
60 'active': True,
66 'csrf_token': self.csrf_token})
61 'csrf_token': self.csrf_token})
67
62
68 group = Session().query(UserGroup).filter(
63 group = Session().query(UserGroup).filter(
69 UserGroup.users_group_name == users_group_name).one()
64 UserGroup.users_group_name == users_group_name).one()
70
65
71 assert group.group_data.get('extern_type') is None
66 assert group.group_data.get('extern_type') is None
72
67
73 # enable
68 # enable
74 self.app.post(
69 self.app.post(
75 url('edit_user_group_advanced_sync', user_group_id=group.users_group_id),
70 url('edit_user_group_advanced_sync', user_group_id=group.users_group_id),
76 params={'csrf_token': self.csrf_token}, status=302)
71 params={'csrf_token': self.csrf_token}, status=302)
77
72
78 group = Session().query(UserGroup).filter(
73 group = Session().query(UserGroup).filter(
79 UserGroup.users_group_name == users_group_name).one()
74 UserGroup.users_group_name == users_group_name).one()
80 assert group.group_data.get('extern_type') == 'manual'
75 assert group.group_data.get('extern_type') == 'manual'
81 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
76 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
82
77
83 # disable
78 # disable
84 self.app.post(
79 self.app.post(
85 url('edit_user_group_advanced_sync',
80 url('edit_user_group_advanced_sync',
86 user_group_id=group.users_group_id),
81 user_group_id=group.users_group_id),
87 params={'csrf_token': self.csrf_token}, status=302)
82 params={'csrf_token': self.csrf_token}, status=302)
88
83
89 group = Session().query(UserGroup).filter(
84 group = Session().query(UserGroup).filter(
90 UserGroup.users_group_name == users_group_name).one()
85 UserGroup.users_group_name == users_group_name).one()
91 assert group.group_data.get('extern_type') is None
86 assert group.group_data.get('extern_type') is None
92 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
87 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
93
88
94 def test_delete(self):
89 def test_delete(self):
95 self.log_user()
90 self.log_user()
96 users_group_name = TEST_USER_GROUP + 'another'
91 users_group_name = TEST_USER_GROUP + 'another'
97 response = self.app.post(url('users_groups'), {
92 response = self.app.post(url('users_groups'), {
98 'users_group_name': users_group_name,
93 'users_group_name': users_group_name,
99 'user_group_description': 'DESC',
94 'user_group_description': 'DESC',
100 'active': True,
95 'active': True,
101 'csrf_token': self.csrf_token})
96 'csrf_token': self.csrf_token})
102
97
103 user_group_link = link_to(
98 user_group_link = link_to(
104 users_group_name,
99 users_group_name,
105 url('edit_users_group',
100 url('edit_users_group',
106 user_group_id=UserGroup.get_by_group_name(
101 user_group_id=UserGroup.get_by_group_name(
107 users_group_name).users_group_id))
102 users_group_name).users_group_id))
108 assert_session_flash(
103 assert_session_flash(
109 response,
104 response,
110 'Created user group %s' % user_group_link)
105 'Created user group %s' % user_group_link)
111
106
112 group = Session().query(UserGroup).filter(
107 group = Session().query(UserGroup).filter(
113 UserGroup.users_group_name == users_group_name).one()
108 UserGroup.users_group_name == users_group_name).one()
114
109
115 self.app.post(
110 self.app.post(
116 url('delete_users_group', user_group_id=group.users_group_id),
111 url('delete_users_group', user_group_id=group.users_group_id),
117 params={'_method': 'delete', 'csrf_token': self.csrf_token})
112 params={'_method': 'delete', 'csrf_token': self.csrf_token})
118
113
119 group = Session().query(UserGroup).filter(
114 group = Session().query(UserGroup).filter(
120 UserGroup.users_group_name == users_group_name).scalar()
115 UserGroup.users_group_name == users_group_name).scalar()
121
116
122 assert group is None
117 assert group is None
123
118
124 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
119 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
125 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
120 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
126 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
121 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
127 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
122 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
128 ('', '', '', '', '', '', True, False),
123 ('', '', '', '', '', '', True, False),
129 ])
124 ])
130 def test_global_perms_on_group(
125 def test_global_perms_on_group(
131 self, repo_create, repo_create_write, user_group_create,
126 self, repo_create, repo_create_write, user_group_create,
132 repo_group_create, fork_create, expect_error, expect_form_error,
127 repo_group_create, fork_create, expect_error, expect_form_error,
133 inherit_default_permissions):
128 inherit_default_permissions):
134 self.log_user()
129 self.log_user()
135 users_group_name = TEST_USER_GROUP + 'another2'
130 users_group_name = TEST_USER_GROUP + 'another2'
136 response = self.app.post(url('users_groups'),
131 response = self.app.post(url('users_groups'),
137 {'users_group_name': users_group_name,
132 {'users_group_name': users_group_name,
138 'user_group_description': 'DESC',
133 'user_group_description': 'DESC',
139 'active': True,
134 'active': True,
140 'csrf_token': self.csrf_token})
135 'csrf_token': self.csrf_token})
141
136
142 ug = UserGroup.get_by_group_name(users_group_name)
137 ug = UserGroup.get_by_group_name(users_group_name)
143 user_group_link = link_to(
138 user_group_link = link_to(
144 users_group_name,
139 users_group_name,
145 url('edit_users_group', user_group_id=ug.users_group_id))
140 url('edit_users_group', user_group_id=ug.users_group_id))
146 assert_session_flash(
141 assert_session_flash(
147 response,
142 response,
148 'Created user group %s' % user_group_link)
143 'Created user group %s' % user_group_link)
149 response.follow()
144 response.follow()
150
145
151 # ENABLE REPO CREATE ON A GROUP
146 # ENABLE REPO CREATE ON A GROUP
152 perm_params = {
147 perm_params = {
153 'inherit_default_permissions': False,
148 'inherit_default_permissions': False,
154 'default_repo_create': repo_create,
149 'default_repo_create': repo_create,
155 'default_repo_create_on_write': repo_create_write,
150 'default_repo_create_on_write': repo_create_write,
156 'default_user_group_create': user_group_create,
151 'default_user_group_create': user_group_create,
157 'default_repo_group_create': repo_group_create,
152 'default_repo_group_create': repo_group_create,
158 'default_fork_create': fork_create,
153 'default_fork_create': fork_create,
159 'default_inherit_default_permissions': inherit_default_permissions,
154 'default_inherit_default_permissions': inherit_default_permissions,
160
155
161 '_method': 'put',
156 '_method': 'put',
162 'csrf_token': self.csrf_token,
157 'csrf_token': self.csrf_token,
163 }
158 }
164 response = self.app.post(
159 response = self.app.post(
165 url('edit_user_group_global_perms',
160 url('edit_user_group_global_perms',
166 user_group_id=ug.users_group_id),
161 user_group_id=ug.users_group_id),
167 params=perm_params)
162 params=perm_params)
168
163
169 if expect_form_error:
164 if expect_form_error:
170 assert response.status_int == 200
165 assert response.status_int == 200
171 response.mustcontain('Value must be one of')
166 response.mustcontain('Value must be one of')
172 else:
167 else:
173 if expect_error:
168 if expect_error:
174 msg = 'An error occurred during permissions saving'
169 msg = 'An error occurred during permissions saving'
175 else:
170 else:
176 msg = 'User Group global permissions updated successfully'
171 msg = 'User Group global permissions updated successfully'
177 ug = UserGroup.get_by_group_name(users_group_name)
172 ug = UserGroup.get_by_group_name(users_group_name)
178 del perm_params['_method']
173 del perm_params['_method']
179 del perm_params['csrf_token']
174 del perm_params['csrf_token']
180 del perm_params['inherit_default_permissions']
175 del perm_params['inherit_default_permissions']
181 assert perm_params == ug.get_default_perms()
176 assert perm_params == ug.get_default_perms()
182 assert_session_flash(response, msg)
177 assert_session_flash(response, msg)
183
178
184 fixture.destroy_user_group(users_group_name)
179 fixture.destroy_user_group(users_group_name)
185
180
186 def test_edit_autocomplete(self):
181 def test_edit_autocomplete(self):
187 self.log_user()
182 self.log_user()
188 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
183 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
189 response = self.app.get(
184 response = self.app.get(
190 url('edit_users_group', user_group_id=ug.users_group_id))
185 url('edit_users_group', user_group_id=ug.users_group_id))
191 fixture.destroy_user_group(TEST_USER_GROUP)
186 fixture.destroy_user_group(TEST_USER_GROUP)
192
187
193 def test_edit_user_group_autocomplete_members(self, xhr_header):
194 self.log_user()
195 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
196 response = self.app.get(
197 url('edit_user_group_members', user_group_id=ug.users_group_id),
198 extra_environ=xhr_header)
199
200 assert response.body == '{"members": []}'
201 fixture.destroy_user_group(TEST_USER_GROUP)
202
203 def test_usergroup_escape(self, user_util):
204 user = user_util.create_user(
205 username='escape_user',
206 firstname='<img src="/image2" onload="alert(\'Hello, World!\');">',
207 lastname='<img src="/image2" onload="alert(\'Hello, World!\');">'
208 )
209
210 user_util.create_user_group(owner=user.username)
211
212 self.log_user()
213 users_group_name = 'samplegroup'
214 data = {
215 'users_group_name': users_group_name,
216 'user_group_description': (
217 '<strong onload="alert();">DESC</strong>'),
218 'active': True,
219 'csrf_token': self.csrf_token
220 }
221
222 self.app.post(url('users_groups'), data)
223 response = self.app.get(url('users_groups'))
224
225 response.mustcontain(
226 '&lt;strong onload=&#34;alert();&#34;&gt;'
227 'DESC&lt;/strong&gt;')
228 # TODO(marcink): fix this test after user-group grid rewrite
229 # response.mustcontain(
230 # '&lt;img src=&#34;/image2&#34; onload=&#34;'
231 # 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
232
233 def test_update_members_from_user_ids(self, user_regular):
188 def test_update_members_from_user_ids(self, user_regular):
234 uid = user_regular.user_id
189 uid = user_regular.user_id
235 username = user_regular.username
190 username = user_regular.username
236 self.log_user()
191 self.log_user()
237
192
238 user_group = fixture.create_user_group('test_gr_ids')
193 user_group = fixture.create_user_group('test_gr_ids')
239 assert user_group.members == []
194 assert user_group.members == []
240 assert user_group.user != user_regular
195 assert user_group.user != user_regular
241 expected_active_state = not user_group.users_group_active
196 expected_active_state = not user_group.users_group_active
242
197
243 form_data = [
198 form_data = [
244 ('csrf_token', self.csrf_token),
199 ('csrf_token', self.csrf_token),
245 ('_method', 'put'),
200 ('_method', 'put'),
246 ('user', username),
201 ('user', username),
247 ('users_group_name', 'changed_name'),
202 ('users_group_name', 'changed_name'),
248 ('users_group_active', expected_active_state),
203 ('users_group_active', expected_active_state),
249 ('user_group_description', 'changed_description'),
204 ('user_group_description', 'changed_description'),
250
205
251 ('__start__', 'user_group_members:sequence'),
206 ('__start__', 'user_group_members:sequence'),
252 ('__start__', 'member:mapping'),
207 ('__start__', 'member:mapping'),
253 ('member_user_id', uid),
208 ('member_user_id', uid),
254 ('type', 'existing'),
209 ('type', 'existing'),
255 ('__end__', 'member:mapping'),
210 ('__end__', 'member:mapping'),
256 ('__end__', 'user_group_members:sequence'),
211 ('__end__', 'user_group_members:sequence'),
257 ]
212 ]
258 ugid = user_group.users_group_id
213 ugid = user_group.users_group_id
259 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
214 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
260
215
261 user_group = UserGroup.get(ugid)
216 user_group = UserGroup.get(ugid)
262 assert user_group
217 assert user_group
263
218
264 assert user_group.members[0].user_id == uid
219 assert user_group.members[0].user_id == uid
265 assert user_group.user_id == uid
220 assert user_group.user_id == uid
266 assert 'changed_name' in user_group.users_group_name
221 assert 'changed_name' in user_group.users_group_name
267 assert 'changed_description' in user_group.user_group_description
222 assert 'changed_description' in user_group.user_group_description
268 assert user_group.users_group_active == expected_active_state
223 assert user_group.users_group_active == expected_active_state
269
224
270 fixture.destroy_user_group(user_group)
225 fixture.destroy_user_group(user_group)
@@ -1,134 +1,151 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 pytest
21 import pytest
22
22
23 from rhodecode.tests import (
23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to)
24 TestController, url, assert_session_flash, link_to)
25 from rhodecode.model.db import User, UserGroup
25 from rhodecode.model.db import User, UserGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29
29
30 def route_path(name, params=None, **kwargs):
31 import urllib
32 from rhodecode.apps._base import ADMIN_PREFIX
33
34 base_url = {
35 'home': '/',
36 'user_groups':
37 ADMIN_PREFIX + '/user_groups',
38 'user_groups_data':
39 ADMIN_PREFIX + '/user_groups_data',
40 }[name].format(**kwargs)
41
42 if params:
43 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
44 return base_url
45
46
30 fixture = Fixture()
47 fixture = Fixture()
31
48
32
49
33 def route_path(name, **kwargs):
50 class TestAdminDelegatedUser(TestController):
34 return {
35 'home': '/',
36 }[name].format(**kwargs)
37
51
38
52 def test_regular_user_cannot_see_admin_interfaces(
39 class TestAdminUsersGroupsController(TestController):
53 self, user_util, xhr_header):
40
41 def test_regular_user_cannot_see_admin_interfaces(self, user_util):
42 user = user_util.create_user(password='qweqwe')
54 user = user_util.create_user(password='qweqwe')
43 self.log_user(user.username, 'qweqwe')
55 self.log_user(user.username, 'qweqwe')
44
56
45 # check if in home view, such user doesn't see the "admin" menus
57 # check if in home view, such user doesn't see the "admin" menus
46 response = self.app.get(route_path('home'))
58 response = self.app.get(route_path('home'))
47
59
48 assert_response = response.assert_response()
60 assert_response = response.assert_response()
49
61
50 assert_response.no_element_exists('li.local-admin-repos')
62 assert_response.no_element_exists('li.local-admin-repos')
51 assert_response.no_element_exists('li.local-admin-repo-groups')
63 assert_response.no_element_exists('li.local-admin-repo-groups')
52 assert_response.no_element_exists('li.local-admin-user-groups')
64 assert_response.no_element_exists('li.local-admin-user-groups')
53
65
54 response = self.app.get(url('repos'), status=200)
66 response = self.app.get(url('repos'), status=200)
55 response.mustcontain('data: []')
67 response.mustcontain('data: []')
56
68
57 response = self.app.get(url('repo_groups'), status=200)
69 response = self.app.get(url('repo_groups'), status=200)
58 response.mustcontain('data: []')
70 response.mustcontain('data: []')
59
71
60 response = self.app.get(url('users_groups'), status=200)
72 response = self.app.get(route_path('user_groups_data'),
61 response.mustcontain('data: []')
73 status=200, extra_environ=xhr_header)
74 assert response.json['data'] == []
62
75
63 def test_regular_user_can_see_admin_interfaces_if_owner(self, user_util):
76 def test_regular_user_can_see_admin_interfaces_if_owner(
77 self, user_util, xhr_header):
64 user = user_util.create_user(password='qweqwe')
78 user = user_util.create_user(password='qweqwe')
65 username = user.username
79 username = user.username
66
80
67 repo = user_util.create_repo(owner=username)
81 repo = user_util.create_repo(owner=username)
68 repo_name = repo.repo_name
82 repo_name = repo.repo_name
69
83
70 repo_group = user_util.create_repo_group(owner=username)
84 repo_group = user_util.create_repo_group(owner=username)
71 repo_group_name = repo_group.group_name
85 repo_group_name = repo_group.group_name
72
86
73 user_group = user_util.create_user_group(owner=username)
87 user_group = user_util.create_user_group(owner=username)
74 user_group_name = user_group.users_group_name
88 user_group_name = user_group.users_group_name
75
89
76 self.log_user(username, 'qweqwe')
90 self.log_user(username, 'qweqwe')
77 # check if in home view, such user doesn't see the "admin" menus
91 # check if in home view, such user doesn't see the "admin" menus
78 response = self.app.get(route_path('home'))
92 response = self.app.get(route_path('home'))
79
93
80 assert_response = response.assert_response()
94 assert_response = response.assert_response()
81
95
82 assert_response.one_element_exists('li.local-admin-repos')
96 assert_response.one_element_exists('li.local-admin-repos')
83 assert_response.one_element_exists('li.local-admin-repo-groups')
97 assert_response.one_element_exists('li.local-admin-repo-groups')
84 assert_response.one_element_exists('li.local-admin-user-groups')
98 assert_response.one_element_exists('li.local-admin-user-groups')
85
99
86 # admin interfaces have visible elements
100 # admin interfaces have visible elements
87 response = self.app.get(url('repos'), status=200)
101 response = self.app.get(url('repos'), status=200)
88 response.mustcontain('"name_raw": "{}"'.format(repo_name))
102 response.mustcontain('"name_raw": "{}"'.format(repo_name))
89
103
90 response = self.app.get(url('repo_groups'), status=200)
104 response = self.app.get(url('repo_groups'), status=200)
91 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
105 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
92
106
93 response = self.app.get(url('users_groups'), status=200)
107 response = self.app.get(route_path('user_groups_data'),
94 response.mustcontain('"group_name_raw": "{}"'.format(user_group_name))
108 extra_environ=xhr_header, status=200)
109 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
95
110
96 def test_regular_user_can_see_admin_interfaces_if_admin_perm(self, user_util):
111 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
112 self, user_util, xhr_header):
97 user = user_util.create_user(password='qweqwe')
113 user = user_util.create_user(password='qweqwe')
98 username = user.username
114 username = user.username
99
115
100 repo = user_util.create_repo()
116 repo = user_util.create_repo()
101 repo_name = repo.repo_name
117 repo_name = repo.repo_name
102
118
103 repo_group = user_util.create_repo_group()
119 repo_group = user_util.create_repo_group()
104 repo_group_name = repo_group.group_name
120 repo_group_name = repo_group.group_name
105
121
106 user_group = user_util.create_user_group()
122 user_group = user_util.create_user_group()
107 user_group_name = user_group.users_group_name
123 user_group_name = user_group.users_group_name
108
124
109 user_util.grant_user_permission_to_repo(
125 user_util.grant_user_permission_to_repo(
110 repo, user, 'repository.admin')
126 repo, user, 'repository.admin')
111 user_util.grant_user_permission_to_repo_group(
127 user_util.grant_user_permission_to_repo_group(
112 repo_group, user, 'group.admin')
128 repo_group, user, 'group.admin')
113 user_util.grant_user_permission_to_user_group(
129 user_util.grant_user_permission_to_user_group(
114 user_group, user, 'usergroup.admin')
130 user_group, user, 'usergroup.admin')
115
131
116 self.log_user(username, 'qweqwe')
132 self.log_user(username, 'qweqwe')
117 # check if in home view, such user doesn't see the "admin" menus
133 # check if in home view, such user doesn't see the "admin" menus
118 response = self.app.get(route_path('home'))
134 response = self.app.get(route_path('home'))
119
135
120 assert_response = response.assert_response()
136 assert_response = response.assert_response()
121
137
122 assert_response.one_element_exists('li.local-admin-repos')
138 assert_response.one_element_exists('li.local-admin-repos')
123 assert_response.one_element_exists('li.local-admin-repo-groups')
139 assert_response.one_element_exists('li.local-admin-repo-groups')
124 assert_response.one_element_exists('li.local-admin-user-groups')
140 assert_response.one_element_exists('li.local-admin-user-groups')
125
141
126 # admin interfaces have visible elements
142 # admin interfaces have visible elements
127 response = self.app.get(url('repos'), status=200)
143 response = self.app.get(url('repos'), status=200)
128 response.mustcontain('"name_raw": "{}"'.format(repo_name))
144 response.mustcontain('"name_raw": "{}"'.format(repo_name))
129
145
130 response = self.app.get(url('repo_groups'), status=200)
146 response = self.app.get(url('repo_groups'), status=200)
131 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
147 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
132
148
133 response = self.app.get(url('users_groups'), status=200)
149 response = self.app.get(route_path('user_groups_data'),
134 response.mustcontain('"group_name_raw": "{}"'.format(user_group_name))
150 extra_environ=xhr_header, status=200)
151 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
General Comments 0
You need to be logged in to leave comments. Login now