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