##// 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
@@ -0,0 +1,113 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.model.db import UserGroup, User
24 from rhodecode.model.meta import Session
25
26 from rhodecode.tests import (
27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29
30 fixture = Fixture()
31
32
33 def route_path(name, params=None, **kwargs):
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
36
37 base_url = {
38 'user_groups': ADMIN_PREFIX + '/user_groups',
39 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
40 'user_group_members_data': ADMIN_PREFIX + '/user_groups/{user_group_id}/members',
41 }[name].format(**kwargs)
42
43 if params:
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 return base_url
46
47
48 class TestAdminUserGroupsView(TestController):
49
50 def test_show_users(self):
51 self.log_user()
52 self.app.get(route_path('user_groups'))
53
54 def test_show_user_groups_data(self, xhr_header):
55 self.log_user()
56 response = self.app.get(route_path(
57 'user_groups_data'), extra_environ=xhr_header)
58
59 all_user_groups = UserGroup.query().count()
60 assert response.json['recordsTotal'] == all_user_groups
61
62 def test_show_user_groups_data_filtered(self, xhr_header):
63 self.log_user()
64 response = self.app.get(route_path(
65 'user_groups_data', params={'search[value]': 'empty_search'}),
66 extra_environ=xhr_header)
67
68 all_user_groups = UserGroup.query().count()
69 assert response.json['recordsTotal'] == all_user_groups
70 assert response.json['recordsFiltered'] == 0
71
72 def test_usergroup_escape(self, user_util, xhr_header):
73 self.log_user()
74
75 xss_img = '<img src="/image1" onload="alert(\'Hello, World!\');">'
76 user = user_util.create_user()
77 user.name = xss_img
78 user.lastname = xss_img
79 Session().add(user)
80 Session().commit()
81
82 user_group = user_util.create_user_group()
83
84 user_group.users_group_name = xss_img
85 user_group.user_group_description = '<strong onload="alert();">DESC</strong>'
86
87 response = self.app.get(
88 route_path('user_groups_data'), extra_environ=xhr_header)
89
90 response.mustcontain(
91 '&lt;strong onload=&#34;alert();&#34;&gt;DESC&lt;/strong&gt;')
92 response.mustcontain(
93 '&lt;img src=&#34;/image1&#34; onload=&#34;'
94 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
95
96 def test_edit_user_group_autocomplete_empty_members(self, xhr_header, user_util):
97 self.log_user()
98 ug = user_util.create_user_group()
99 response = self.app.get(
100 route_path('user_group_members_data', user_group_id=ug.users_group_id),
101 extra_environ=xhr_header)
102
103 assert response.json == {'members': []}
104
105 def test_edit_user_group_autocomplete_members(self, xhr_header, user_util):
106 self.log_user()
107 members = [u.user_id for u in User.get_all()]
108 ug = user_util.create_user_group(members=members)
109 response = self.app.get(
110 route_path('user_group_members_data', user_group_id=ug.users_group_id),
111 extra_environ=xhr_header)
112
113 assert len(response.json['members']) == len(members)
@@ -0,0 +1,195 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import datetime
23
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.lib.helpers import Page
28 from rhodecode.model.scm import UserGroupList
29 from rhodecode_tools.lib.ext_json import json
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired, NotAnonymous,
34 HasUserGroupPermissionAnyDecorator)
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import PartialRenderer
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.db import User, UserGroup, UserGroupMember, or_, count
42 from rhodecode.model.meta import Session
43
44 log = logging.getLogger(__name__)
45
46
47 class AdminUserGroupsView(BaseAppView, DataGridAppView):
48
49 def load_default_context(self):
50 c = self._get_local_tmpl_context()
51 self._register_global_c(c)
52 return c
53
54 # permission check in data loading of
55 # `user_groups_list_data` via UserGroupList
56 @NotAnonymous()
57 @view_config(
58 route_name='user_groups', request_method='GET',
59 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
60 def user_groups_list(self):
61 c = self.load_default_context()
62 return self._get_template_context(c)
63
64 # permission check inside
65 @NotAnonymous()
66 @view_config(
67 route_name='user_groups_data', request_method='GET',
68 renderer='json_ext', xhr=True)
69 def user_groups_list_data(self):
70 column_map = {
71 'active': 'users_group_active',
72 'description': 'user_group_description',
73 'members': 'members_total',
74 'owner': 'user_username',
75 'sync': 'group_data'
76 }
77 draw, start, limit = self._extract_chunk(self.request)
78 search_q, order_by, order_dir = self._extract_ordering(
79 self.request, column_map=column_map)
80
81 _render = PartialRenderer('data_table/_dt_elements.mako')
82
83 def user_group_name(user_group_id, user_group_name):
84 return _render("user_group_name", user_group_id, user_group_name)
85
86 def user_group_actions(user_group_id, user_group_name):
87 return _render("user_group_actions", user_group_id, user_group_name)
88
89 def user_profile(username):
90 return _render('user_profile', username)
91
92 user_groups_data_total_count = UserGroup.query().count()
93
94 member_count = count(UserGroupMember.user_id)
95 base_q = Session.query(
96 UserGroup.users_group_name,
97 UserGroup.user_group_description,
98 UserGroup.users_group_active,
99 UserGroup.users_group_id,
100 UserGroup.group_data,
101 User,
102 member_count.label('member_count')
103 ) \
104 .outerjoin(UserGroupMember) \
105 .join(User, User.user_id == UserGroup.user_id) \
106 .group_by(UserGroup, User)
107
108 if search_q:
109 like_expression = u'%{}%'.format(safe_unicode(search_q))
110 base_q = base_q.filter(or_(
111 UserGroup.users_group_name.ilike(like_expression),
112 ))
113
114 user_groups_data_total_filtered_count = base_q.count()
115
116 if order_by == 'members_total':
117 sort_col = member_count
118 elif order_by == 'user_username':
119 sort_col = User.username
120 else:
121 sort_col = getattr(UserGroup, order_by, None)
122
123 if isinstance(sort_col, count) or sort_col:
124 if order_dir == 'asc':
125 sort_col = sort_col.asc()
126 else:
127 sort_col = sort_col.desc()
128
129 base_q = base_q.order_by(sort_col)
130 base_q = base_q.offset(start).limit(limit)
131
132 # authenticated access to user groups
133 user_group_list = base_q.all()
134 auth_user_group_list = UserGroupList(
135 user_group_list, perm_set=['usergroup.admin'])
136
137 user_groups_data = []
138 for user_gr in auth_user_group_list:
139 user_groups_data.append({
140 "users_group_name": user_group_name(
141 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
142 "name_raw": h.escape(user_gr.users_group_name),
143 "description": h.escape(user_gr.user_group_description),
144 "members": user_gr.member_count,
145 # NOTE(marcink): because of advanced query we
146 # need to load it like that
147 "sync": UserGroup._load_group_data(
148 user_gr.group_data).get('extern_type'),
149 "active": h.bool2icon(user_gr.users_group_active),
150 "owner": user_profile(user_gr.User.username),
151 "action": user_group_actions(
152 user_gr.users_group_id, user_gr.users_group_name)
153 })
154
155 data = ({
156 'draw': draw,
157 'data': user_groups_data,
158 'recordsTotal': user_groups_data_total_count,
159 'recordsFiltered': user_groups_data_total_filtered_count,
160 })
161
162 return data
163
164 @LoginRequired()
165 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
166 @view_config(
167 route_name='user_group_members_data', request_method='GET',
168 renderer='json_ext', xhr=True)
169 def user_group_members(self):
170 """
171 Return members of given user group
172 """
173 user_group_id = self.request.matchdict['user_group_id']
174 user_group = UserGroup.get_or_404(user_group_id)
175 group_members_obj = sorted((x.user for x in user_group.members),
176 key=lambda u: u.username.lower())
177
178 group_members = [
179 {
180 'id': user.user_id,
181 'first_name': user.first_name,
182 'last_name': user.last_name,
183 'username': user.username,
184 'icon_link': h.gravatar_url(user.email, 30),
185 'value_display': h.person(user.email),
186 'value': user.username,
187 'value_type': 'user',
188 'active': user.active,
189 }
190 for user in group_members_obj
191 ]
192
193 return {
194 'members': group_members
195 }
@@ -1,179 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
33 name='admin_audit_logs',
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:\d+}')
38 pattern='/pull_requests/{pull_request_id:\d+}')
39 config.add_route(
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:\d+}')
41 pattern='/pull-requests/{pull_request_id:\d+}')
42 config.add_route(
42 config.add_route(
43 name='pull_requests_global',
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:\d+}')
44 pattern='/pull-request/{pull_request_id:\d+}')
45
45
46 config.add_route(
46 config.add_route(
47 name='admin_settings_open_source',
47 name='admin_settings_open_source',
48 pattern='/settings/open_source')
48 pattern='/settings/open_source')
49 config.add_route(
49 config.add_route(
50 name='admin_settings_vcs_svn_generate_cfg',
50 name='admin_settings_vcs_svn_generate_cfg',
51 pattern='/settings/vcs/svn_generate_cfg')
51 pattern='/settings/vcs/svn_generate_cfg')
52
52
53 config.add_route(
53 config.add_route(
54 name='admin_settings_system',
54 name='admin_settings_system',
55 pattern='/settings/system')
55 pattern='/settings/system')
56 config.add_route(
56 config.add_route(
57 name='admin_settings_system_update',
57 name='admin_settings_system_update',
58 pattern='/settings/system/updates')
58 pattern='/settings/system/updates')
59
59
60 config.add_route(
60 config.add_route(
61 name='admin_settings_sessions',
61 name='admin_settings_sessions',
62 pattern='/settings/sessions')
62 pattern='/settings/sessions')
63 config.add_route(
63 config.add_route(
64 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
65 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
66
66
67 config.add_route(
67 config.add_route(
68 name='admin_settings_process_management',
68 name='admin_settings_process_management',
69 pattern='/settings/process_management')
69 pattern='/settings/process_management')
70 config.add_route(
70 config.add_route(
71 name='admin_settings_process_management_signal',
71 name='admin_settings_process_management_signal',
72 pattern='/settings/process_management/signal')
72 pattern='/settings/process_management/signal')
73
73
74 # global permissions
74 # global permissions
75
75
76 config.add_route(
76 config.add_route(
77 name='admin_permissions_application',
77 name='admin_permissions_application',
78 pattern='/permissions/application')
78 pattern='/permissions/application')
79 config.add_route(
79 config.add_route(
80 name='admin_permissions_application_update',
80 name='admin_permissions_application_update',
81 pattern='/permissions/application/update')
81 pattern='/permissions/application/update')
82
82
83 config.add_route(
83 config.add_route(
84 name='admin_permissions_global',
84 name='admin_permissions_global',
85 pattern='/permissions/global')
85 pattern='/permissions/global')
86 config.add_route(
86 config.add_route(
87 name='admin_permissions_global_update',
87 name='admin_permissions_global_update',
88 pattern='/permissions/global/update')
88 pattern='/permissions/global/update')
89
89
90 config.add_route(
90 config.add_route(
91 name='admin_permissions_object',
91 name='admin_permissions_object',
92 pattern='/permissions/object')
92 pattern='/permissions/object')
93 config.add_route(
93 config.add_route(
94 name='admin_permissions_object_update',
94 name='admin_permissions_object_update',
95 pattern='/permissions/object/update')
95 pattern='/permissions/object/update')
96
96
97 config.add_route(
97 config.add_route(
98 name='admin_permissions_ips',
98 name='admin_permissions_ips',
99 pattern='/permissions/ips')
99 pattern='/permissions/ips')
100
100
101 config.add_route(
101 config.add_route(
102 name='admin_permissions_overview',
102 name='admin_permissions_overview',
103 pattern='/permissions/overview')
103 pattern='/permissions/overview')
104
104
105 config.add_route(
105 config.add_route(
106 name='admin_permissions_auth_token_access',
106 name='admin_permissions_auth_token_access',
107 pattern='/permissions/auth_token_access')
107 pattern='/permissions/auth_token_access')
108
108
109 # users admin
109 # users admin
110 config.add_route(
110 config.add_route(
111 name='users',
111 name='users',
112 pattern='/users')
112 pattern='/users')
113
113
114 config.add_route(
114 config.add_route(
115 name='users_data',
115 name='users_data',
116 pattern='/users_data')
116 pattern='/users_data')
117
117
118 # user auth tokens
118 # user auth tokens
119 config.add_route(
119 config.add_route(
120 name='edit_user_auth_tokens',
120 name='edit_user_auth_tokens',
121 pattern='/users/{user_id:\d+}/edit/auth_tokens')
121 pattern='/users/{user_id:\d+}/edit/auth_tokens')
122 config.add_route(
122 config.add_route(
123 name='edit_user_auth_tokens_add',
123 name='edit_user_auth_tokens_add',
124 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
124 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
125 config.add_route(
125 config.add_route(
126 name='edit_user_auth_tokens_delete',
126 name='edit_user_auth_tokens_delete',
127 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
127 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
128
128
129 # user emails
129 # user emails
130 config.add_route(
130 config.add_route(
131 name='edit_user_emails',
131 name='edit_user_emails',
132 pattern='/users/{user_id:\d+}/edit/emails')
132 pattern='/users/{user_id:\d+}/edit/emails')
133 config.add_route(
133 config.add_route(
134 name='edit_user_emails_add',
134 name='edit_user_emails_add',
135 pattern='/users/{user_id:\d+}/edit/emails/new')
135 pattern='/users/{user_id:\d+}/edit/emails/new')
136 config.add_route(
136 config.add_route(
137 name='edit_user_emails_delete',
137 name='edit_user_emails_delete',
138 pattern='/users/{user_id:\d+}/edit/emails/delete')
138 pattern='/users/{user_id:\d+}/edit/emails/delete')
139
139
140 # user IPs
140 # user IPs
141 config.add_route(
141 config.add_route(
142 name='edit_user_ips',
142 name='edit_user_ips',
143 pattern='/users/{user_id:\d+}/edit/ips')
143 pattern='/users/{user_id:\d+}/edit/ips')
144 config.add_route(
144 config.add_route(
145 name='edit_user_ips_add',
145 name='edit_user_ips_add',
146 pattern='/users/{user_id:\d+}/edit/ips/new')
146 pattern='/users/{user_id:\d+}/edit/ips/new')
147 config.add_route(
147 config.add_route(
148 name='edit_user_ips_delete',
148 name='edit_user_ips_delete',
149 pattern='/users/{user_id:\d+}/edit/ips/delete')
149 pattern='/users/{user_id:\d+}/edit/ips/delete')
150
150
151 # user groups management
151 # user groups management
152 config.add_route(
152 config.add_route(
153 name='edit_user_groups_management',
153 name='edit_user_groups_management',
154 pattern='/users/{user_id:\d+}/edit/groups_management')
154 pattern='/users/{user_id:\d+}/edit/groups_management')
155
155
156 config.add_route(
156 config.add_route(
157 name='edit_user_groups_management_updates',
157 name='edit_user_groups_management_updates',
158 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
158 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
159
159
160 # user audit logs
160 # user audit logs
161 config.add_route(
161 config.add_route(
162 name='edit_user_audit_logs',
162 name='edit_user_audit_logs',
163 pattern='/users/{user_id:\d+}/edit/audit')
163 pattern='/users/{user_id:\d+}/edit/audit')
164
164
165 # user groups admin
166 config.add_route(
167 name='user_groups',
168 pattern='/user_groups')
169
170 config.add_route(
171 name='user_groups_data',
172 pattern='/user_groups_data')
173
174 config.add_route(
175 name='user_group_members_data',
176 pattern='/user_groups/{user_group_id:\d+}/members')
177
165
178
166 def includeme(config):
179 def includeme(config):
167 settings = config.get_settings()
180 settings = config.get_settings()
168
181
169 # Create admin navigation registry and add it to the pyramid registry.
182 # Create admin navigation registry and add it to the pyramid registry.
170 labs_active = str2bool(settings.get('labs_settings_active', False))
183 labs_active = str2bool(settings.get('labs_settings_active', False))
171 navigation_registry = NavigationRegistry(labs_active=labs_active)
184 navigation_registry = NavigationRegistry(labs_active=labs_active)
172 config.registry.registerUtility(navigation_registry)
185 config.registry.registerUtility(navigation_registry)
173
186
174 # main admin routes
187 # main admin routes
175 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
188 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
176 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
189 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
177
190
178 # Scan module for configuration decorators.
191 # Scan module for configuration decorators.
179 config.scan()
192 config.scan()
@@ -1,505 +1,510 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from sqlalchemy.sql.functions import coalesce
27 from sqlalchemy.sql.functions import coalesce
28
28
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30
30
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
40 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class AdminUsersView(BaseAppView, DataGridAppView):
46 class AdminUsersView(BaseAppView, DataGridAppView):
47 ALLOW_SCOPED_TOKENS = False
47 ALLOW_SCOPED_TOKENS = False
48 """
48 """
49 This view has alternative version inside EE, if modified please take a look
49 This view has alternative version inside EE, if modified please take a look
50 in there as well.
50 in there as well.
51 """
51 """
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
56 self._register_global_c(c)
56 self._register_global_c(c)
57 return c
57 return c
58
58
59 def _redirect_for_default_user(self, username):
59 def _redirect_for_default_user(self, username):
60 _ = self.request.translate
60 _ = self.request.translate
61 if username == User.DEFAULT_USER:
61 if username == User.DEFAULT_USER:
62 h.flash(_("You can't edit this user"), category='warning')
62 h.flash(_("You can't edit this user"), category='warning')
63 # TODO(marcink): redirect to 'users' admin panel once this
63 # TODO(marcink): redirect to 'users' admin panel once this
64 # is a pyramid view
64 # is a pyramid view
65 raise HTTPFound('/')
65 raise HTTPFound('/')
66
66
67 @HasPermissionAllDecorator('hg.admin')
67 @HasPermissionAllDecorator('hg.admin')
68 @view_config(
68 @view_config(
69 route_name='users', request_method='GET',
69 route_name='users', request_method='GET',
70 renderer='rhodecode:templates/admin/users/users.mako')
70 renderer='rhodecode:templates/admin/users/users.mako')
71 def users_list(self):
71 def users_list(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 return self._get_template_context(c)
73 return self._get_template_context(c)
74
74
75 @HasPermissionAllDecorator('hg.admin')
75 @HasPermissionAllDecorator('hg.admin')
76 @view_config(
76 @view_config(
77 # renderer defined below
77 # renderer defined below
78 route_name='users_data', request_method='GET',
78 route_name='users_data', request_method='GET',
79 renderer='json_ext', xhr=True)
79 renderer='json_ext', xhr=True)
80 def users_list_data(self):
80 def users_list_data(self):
81 column_map = {
82 'first_name': 'name',
83 'last_name': 'lastname',
84 }
81 draw, start, limit = self._extract_chunk(self.request)
85 draw, start, limit = self._extract_chunk(self.request)
82 search_q, order_by, order_dir = self._extract_ordering(self.request)
86 search_q, order_by, order_dir = self._extract_ordering(
87 self.request, column_map=column_map)
83
88
84 _render = self.request.get_partial_renderer(
89 _render = self.request.get_partial_renderer(
85 'data_table/_dt_elements.mako')
90 'data_table/_dt_elements.mako')
86
91
87 def user_actions(user_id, username):
92 def user_actions(user_id, username):
88 return _render("user_actions", user_id, username)
93 return _render("user_actions", user_id, username)
89
94
90 users_data_total_count = User.query()\
95 users_data_total_count = User.query()\
91 .filter(User.username != User.DEFAULT_USER) \
96 .filter(User.username != User.DEFAULT_USER) \
92 .count()
97 .count()
93
98
94 # json generate
99 # json generate
95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
100 base_q = User.query().filter(User.username != User.DEFAULT_USER)
96
101
97 if search_q:
102 if search_q:
98 like_expression = u'%{}%'.format(safe_unicode(search_q))
103 like_expression = u'%{}%'.format(safe_unicode(search_q))
99 base_q = base_q.filter(or_(
104 base_q = base_q.filter(or_(
100 User.username.ilike(like_expression),
105 User.username.ilike(like_expression),
101 User._email.ilike(like_expression),
106 User._email.ilike(like_expression),
102 User.name.ilike(like_expression),
107 User.name.ilike(like_expression),
103 User.lastname.ilike(like_expression),
108 User.lastname.ilike(like_expression),
104 ))
109 ))
105
110
106 users_data_total_filtered_count = base_q.count()
111 users_data_total_filtered_count = base_q.count()
107
112
108 sort_col = getattr(User, order_by, None)
113 sort_col = getattr(User, order_by, None)
109 if sort_col:
114 if sort_col:
110 if order_dir == 'asc':
115 if order_dir == 'asc':
111 # handle null values properly to order by NULL last
116 # handle null values properly to order by NULL last
112 if order_by in ['last_activity']:
117 if order_by in ['last_activity']:
113 sort_col = coalesce(sort_col, datetime.date.max)
118 sort_col = coalesce(sort_col, datetime.date.max)
114 sort_col = sort_col.asc()
119 sort_col = sort_col.asc()
115 else:
120 else:
116 # handle null values properly to order by NULL last
121 # handle null values properly to order by NULL last
117 if order_by in ['last_activity']:
122 if order_by in ['last_activity']:
118 sort_col = coalesce(sort_col, datetime.date.min)
123 sort_col = coalesce(sort_col, datetime.date.min)
119 sort_col = sort_col.desc()
124 sort_col = sort_col.desc()
120
125
121 base_q = base_q.order_by(sort_col)
126 base_q = base_q.order_by(sort_col)
122 base_q = base_q.offset(start).limit(limit)
127 base_q = base_q.offset(start).limit(limit)
123
128
124 users_list = base_q.all()
129 users_list = base_q.all()
125
130
126 users_data = []
131 users_data = []
127 for user in users_list:
132 for user in users_list:
128 users_data.append({
133 users_data.append({
129 "username": h.gravatar_with_user(self.request, user.username),
134 "username": h.gravatar_with_user(self.request, user.username),
130 "email": user.email,
135 "email": user.email,
131 "first_name": user.first_name,
136 "first_name": user.first_name,
132 "last_name": user.last_name,
137 "last_name": user.last_name,
133 "last_login": h.format_date(user.last_login),
138 "last_login": h.format_date(user.last_login),
134 "last_activity": h.format_date(user.last_activity),
139 "last_activity": h.format_date(user.last_activity),
135 "active": h.bool2icon(user.active),
140 "active": h.bool2icon(user.active),
136 "active_raw": user.active,
141 "active_raw": user.active,
137 "admin": h.bool2icon(user.admin),
142 "admin": h.bool2icon(user.admin),
138 "extern_type": user.extern_type,
143 "extern_type": user.extern_type,
139 "extern_name": user.extern_name,
144 "extern_name": user.extern_name,
140 "action": user_actions(user.user_id, user.username),
145 "action": user_actions(user.user_id, user.username),
141 })
146 })
142
147
143 data = ({
148 data = ({
144 'draw': draw,
149 'draw': draw,
145 'data': users_data,
150 'data': users_data,
146 'recordsTotal': users_data_total_count,
151 'recordsTotal': users_data_total_count,
147 'recordsFiltered': users_data_total_filtered_count,
152 'recordsFiltered': users_data_total_filtered_count,
148 })
153 })
149
154
150 return data
155 return data
151
156
152 @LoginRequired()
157 @LoginRequired()
153 @HasPermissionAllDecorator('hg.admin')
158 @HasPermissionAllDecorator('hg.admin')
154 @view_config(
159 @view_config(
155 route_name='edit_user_auth_tokens', request_method='GET',
160 route_name='edit_user_auth_tokens', request_method='GET',
156 renderer='rhodecode:templates/admin/users/user_edit.mako')
161 renderer='rhodecode:templates/admin/users/user_edit.mako')
157 def auth_tokens(self):
162 def auth_tokens(self):
158 _ = self.request.translate
163 _ = self.request.translate
159 c = self.load_default_context()
164 c = self.load_default_context()
160
165
161 user_id = self.request.matchdict.get('user_id')
166 user_id = self.request.matchdict.get('user_id')
162 c.user = User.get_or_404(user_id)
167 c.user = User.get_or_404(user_id)
163 self._redirect_for_default_user(c.user.username)
168 self._redirect_for_default_user(c.user.username)
164
169
165 c.active = 'auth_tokens'
170 c.active = 'auth_tokens'
166
171
167 c.lifetime_values = [
172 c.lifetime_values = [
168 (str(-1), _('forever')),
173 (str(-1), _('forever')),
169 (str(5), _('5 minutes')),
174 (str(5), _('5 minutes')),
170 (str(60), _('1 hour')),
175 (str(60), _('1 hour')),
171 (str(60 * 24), _('1 day')),
176 (str(60 * 24), _('1 day')),
172 (str(60 * 24 * 30), _('1 month')),
177 (str(60 * 24 * 30), _('1 month')),
173 ]
178 ]
174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
179 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
175 c.role_values = [
180 c.role_values = [
176 (x, AuthTokenModel.cls._get_role_name(x))
181 (x, AuthTokenModel.cls._get_role_name(x))
177 for x in AuthTokenModel.cls.ROLES]
182 for x in AuthTokenModel.cls.ROLES]
178 c.role_options = [(c.role_values, _("Role"))]
183 c.role_options = [(c.role_values, _("Role"))]
179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
184 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
180 c.user.user_id, show_expired=True)
185 c.user.user_id, show_expired=True)
181 return self._get_template_context(c)
186 return self._get_template_context(c)
182
187
183 def maybe_attach_token_scope(self, token):
188 def maybe_attach_token_scope(self, token):
184 # implemented in EE edition
189 # implemented in EE edition
185 pass
190 pass
186
191
187 @LoginRequired()
192 @LoginRequired()
188 @HasPermissionAllDecorator('hg.admin')
193 @HasPermissionAllDecorator('hg.admin')
189 @CSRFRequired()
194 @CSRFRequired()
190 @view_config(
195 @view_config(
191 route_name='edit_user_auth_tokens_add', request_method='POST')
196 route_name='edit_user_auth_tokens_add', request_method='POST')
192 def auth_tokens_add(self):
197 def auth_tokens_add(self):
193 _ = self.request.translate
198 _ = self.request.translate
194 c = self.load_default_context()
199 c = self.load_default_context()
195
200
196 user_id = self.request.matchdict.get('user_id')
201 user_id = self.request.matchdict.get('user_id')
197 c.user = User.get_or_404(user_id)
202 c.user = User.get_or_404(user_id)
198
203
199 self._redirect_for_default_user(c.user.username)
204 self._redirect_for_default_user(c.user.username)
200
205
201 user_data = c.user.get_api_data()
206 user_data = c.user.get_api_data()
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
207 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
203 description = self.request.POST.get('description')
208 description = self.request.POST.get('description')
204 role = self.request.POST.get('role')
209 role = self.request.POST.get('role')
205
210
206 token = AuthTokenModel().create(
211 token = AuthTokenModel().create(
207 c.user.user_id, description, lifetime, role)
212 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
213 token_data = token.get_api_data()
209
214
210 self.maybe_attach_token_scope(token)
215 self.maybe_attach_token_scope(token)
211 audit_logger.store_web(
216 audit_logger.store_web(
212 'user.edit.token.add', action_data={
217 'user.edit.token.add', action_data={
213 'data': {'token': token_data, 'user': user_data}},
218 'data': {'token': token_data, 'user': user_data}},
214 user=self._rhodecode_user, )
219 user=self._rhodecode_user, )
215 Session().commit()
220 Session().commit()
216
221
217 h.flash(_("Auth token successfully created"), category='success')
222 h.flash(_("Auth token successfully created"), category='success')
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
223 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
219
224
220 @LoginRequired()
225 @LoginRequired()
221 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
222 @CSRFRequired()
227 @CSRFRequired()
223 @view_config(
228 @view_config(
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
229 route_name='edit_user_auth_tokens_delete', request_method='POST')
225 def auth_tokens_delete(self):
230 def auth_tokens_delete(self):
226 _ = self.request.translate
231 _ = self.request.translate
227 c = self.load_default_context()
232 c = self.load_default_context()
228
233
229 user_id = self.request.matchdict.get('user_id')
234 user_id = self.request.matchdict.get('user_id')
230 c.user = User.get_or_404(user_id)
235 c.user = User.get_or_404(user_id)
231 self._redirect_for_default_user(c.user.username)
236 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
237 user_data = c.user.get_api_data()
233
238
234 del_auth_token = self.request.POST.get('del_auth_token')
239 del_auth_token = self.request.POST.get('del_auth_token')
235
240
236 if del_auth_token:
241 if del_auth_token:
237 token = UserApiKeys.get_or_404(del_auth_token)
242 token = UserApiKeys.get_or_404(del_auth_token)
238 token_data = token.get_api_data()
243 token_data = token.get_api_data()
239
244
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
245 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 audit_logger.store_web(
246 audit_logger.store_web(
242 'user.edit.token.delete', action_data={
247 'user.edit.token.delete', action_data={
243 'data': {'token': token_data, 'user': user_data}},
248 'data': {'token': token_data, 'user': user_data}},
244 user=self._rhodecode_user,)
249 user=self._rhodecode_user,)
245 Session().commit()
250 Session().commit()
246 h.flash(_("Auth token successfully deleted"), category='success')
251 h.flash(_("Auth token successfully deleted"), category='success')
247
252
248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
253 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
249
254
250 @LoginRequired()
255 @LoginRequired()
251 @HasPermissionAllDecorator('hg.admin')
256 @HasPermissionAllDecorator('hg.admin')
252 @view_config(
257 @view_config(
253 route_name='edit_user_emails', request_method='GET',
258 route_name='edit_user_emails', request_method='GET',
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
259 renderer='rhodecode:templates/admin/users/user_edit.mako')
255 def emails(self):
260 def emails(self):
256 _ = self.request.translate
261 _ = self.request.translate
257 c = self.load_default_context()
262 c = self.load_default_context()
258
263
259 user_id = self.request.matchdict.get('user_id')
264 user_id = self.request.matchdict.get('user_id')
260 c.user = User.get_or_404(user_id)
265 c.user = User.get_or_404(user_id)
261 self._redirect_for_default_user(c.user.username)
266 self._redirect_for_default_user(c.user.username)
262
267
263 c.active = 'emails'
268 c.active = 'emails'
264 c.user_email_map = UserEmailMap.query() \
269 c.user_email_map = UserEmailMap.query() \
265 .filter(UserEmailMap.user == c.user).all()
270 .filter(UserEmailMap.user == c.user).all()
266
271
267 return self._get_template_context(c)
272 return self._get_template_context(c)
268
273
269 @LoginRequired()
274 @LoginRequired()
270 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
271 @CSRFRequired()
276 @CSRFRequired()
272 @view_config(
277 @view_config(
273 route_name='edit_user_emails_add', request_method='POST')
278 route_name='edit_user_emails_add', request_method='POST')
274 def emails_add(self):
279 def emails_add(self):
275 _ = self.request.translate
280 _ = self.request.translate
276 c = self.load_default_context()
281 c = self.load_default_context()
277
282
278 user_id = self.request.matchdict.get('user_id')
283 user_id = self.request.matchdict.get('user_id')
279 c.user = User.get_or_404(user_id)
284 c.user = User.get_or_404(user_id)
280 self._redirect_for_default_user(c.user.username)
285 self._redirect_for_default_user(c.user.username)
281
286
282 email = self.request.POST.get('new_email')
287 email = self.request.POST.get('new_email')
283 user_data = c.user.get_api_data()
288 user_data = c.user.get_api_data()
284 try:
289 try:
285 UserModel().add_extra_email(c.user.user_id, email)
290 UserModel().add_extra_email(c.user.user_id, email)
286 audit_logger.store_web(
291 audit_logger.store_web(
287 'user.edit.email.add', action_data={'email': email, 'user': user_data},
292 'user.edit.email.add', action_data={'email': email, 'user': user_data},
288 user=self._rhodecode_user)
293 user=self._rhodecode_user)
289 Session().commit()
294 Session().commit()
290 h.flash(_("Added new email address `%s` for user account") % email,
295 h.flash(_("Added new email address `%s` for user account") % email,
291 category='success')
296 category='success')
292 except formencode.Invalid as error:
297 except formencode.Invalid as error:
293 h.flash(h.escape(error.error_dict['email']), category='error')
298 h.flash(h.escape(error.error_dict['email']), category='error')
294 except Exception:
299 except Exception:
295 log.exception("Exception during email saving")
300 log.exception("Exception during email saving")
296 h.flash(_('An error occurred during email saving'),
301 h.flash(_('An error occurred during email saving'),
297 category='error')
302 category='error')
298 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
303 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
299
304
300 @LoginRequired()
305 @LoginRequired()
301 @HasPermissionAllDecorator('hg.admin')
306 @HasPermissionAllDecorator('hg.admin')
302 @CSRFRequired()
307 @CSRFRequired()
303 @view_config(
308 @view_config(
304 route_name='edit_user_emails_delete', request_method='POST')
309 route_name='edit_user_emails_delete', request_method='POST')
305 def emails_delete(self):
310 def emails_delete(self):
306 _ = self.request.translate
311 _ = self.request.translate
307 c = self.load_default_context()
312 c = self.load_default_context()
308
313
309 user_id = self.request.matchdict.get('user_id')
314 user_id = self.request.matchdict.get('user_id')
310 c.user = User.get_or_404(user_id)
315 c.user = User.get_or_404(user_id)
311 self._redirect_for_default_user(c.user.username)
316 self._redirect_for_default_user(c.user.username)
312
317
313 email_id = self.request.POST.get('del_email_id')
318 email_id = self.request.POST.get('del_email_id')
314 user_model = UserModel()
319 user_model = UserModel()
315
320
316 email = UserEmailMap.query().get(email_id).email
321 email = UserEmailMap.query().get(email_id).email
317 user_data = c.user.get_api_data()
322 user_data = c.user.get_api_data()
318 user_model.delete_extra_email(c.user.user_id, email_id)
323 user_model.delete_extra_email(c.user.user_id, email_id)
319 audit_logger.store_web(
324 audit_logger.store_web(
320 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
325 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
321 user=self._rhodecode_user)
326 user=self._rhodecode_user)
322 Session().commit()
327 Session().commit()
323 h.flash(_("Removed email address from user account"),
328 h.flash(_("Removed email address from user account"),
324 category='success')
329 category='success')
325 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
330 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
326
331
327 @LoginRequired()
332 @LoginRequired()
328 @HasPermissionAllDecorator('hg.admin')
333 @HasPermissionAllDecorator('hg.admin')
329 @view_config(
334 @view_config(
330 route_name='edit_user_ips', request_method='GET',
335 route_name='edit_user_ips', request_method='GET',
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
336 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 def ips(self):
337 def ips(self):
333 _ = self.request.translate
338 _ = self.request.translate
334 c = self.load_default_context()
339 c = self.load_default_context()
335
340
336 user_id = self.request.matchdict.get('user_id')
341 user_id = self.request.matchdict.get('user_id')
337 c.user = User.get_or_404(user_id)
342 c.user = User.get_or_404(user_id)
338 self._redirect_for_default_user(c.user.username)
343 self._redirect_for_default_user(c.user.username)
339
344
340 c.active = 'ips'
345 c.active = 'ips'
341 c.user_ip_map = UserIpMap.query() \
346 c.user_ip_map = UserIpMap.query() \
342 .filter(UserIpMap.user == c.user).all()
347 .filter(UserIpMap.user == c.user).all()
343
348
344 c.inherit_default_ips = c.user.inherit_default_permissions
349 c.inherit_default_ips = c.user.inherit_default_permissions
345 c.default_user_ip_map = UserIpMap.query() \
350 c.default_user_ip_map = UserIpMap.query() \
346 .filter(UserIpMap.user == User.get_default_user()).all()
351 .filter(UserIpMap.user == User.get_default_user()).all()
347
352
348 return self._get_template_context(c)
353 return self._get_template_context(c)
349
354
350 @LoginRequired()
355 @LoginRequired()
351 @HasPermissionAllDecorator('hg.admin')
356 @HasPermissionAllDecorator('hg.admin')
352 @CSRFRequired()
357 @CSRFRequired()
353 @view_config(
358 @view_config(
354 route_name='edit_user_ips_add', request_method='POST')
359 route_name='edit_user_ips_add', request_method='POST')
355 def ips_add(self):
360 def ips_add(self):
356 _ = self.request.translate
361 _ = self.request.translate
357 c = self.load_default_context()
362 c = self.load_default_context()
358
363
359 user_id = self.request.matchdict.get('user_id')
364 user_id = self.request.matchdict.get('user_id')
360 c.user = User.get_or_404(user_id)
365 c.user = User.get_or_404(user_id)
361 # NOTE(marcink): this view is allowed for default users, as we can
366 # NOTE(marcink): this view is allowed for default users, as we can
362 # edit their IP white list
367 # edit their IP white list
363
368
364 user_model = UserModel()
369 user_model = UserModel()
365 desc = self.request.POST.get('description')
370 desc = self.request.POST.get('description')
366 try:
371 try:
367 ip_list = user_model.parse_ip_range(
372 ip_list = user_model.parse_ip_range(
368 self.request.POST.get('new_ip'))
373 self.request.POST.get('new_ip'))
369 except Exception as e:
374 except Exception as e:
370 ip_list = []
375 ip_list = []
371 log.exception("Exception during ip saving")
376 log.exception("Exception during ip saving")
372 h.flash(_('An error occurred during ip saving:%s' % (e,)),
377 h.flash(_('An error occurred during ip saving:%s' % (e,)),
373 category='error')
378 category='error')
374 added = []
379 added = []
375 user_data = c.user.get_api_data()
380 user_data = c.user.get_api_data()
376 for ip in ip_list:
381 for ip in ip_list:
377 try:
382 try:
378 user_model.add_extra_ip(c.user.user_id, ip, desc)
383 user_model.add_extra_ip(c.user.user_id, ip, desc)
379 audit_logger.store_web(
384 audit_logger.store_web(
380 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
385 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
381 user=self._rhodecode_user)
386 user=self._rhodecode_user)
382 Session().commit()
387 Session().commit()
383 added.append(ip)
388 added.append(ip)
384 except formencode.Invalid as error:
389 except formencode.Invalid as error:
385 msg = error.error_dict['ip']
390 msg = error.error_dict['ip']
386 h.flash(msg, category='error')
391 h.flash(msg, category='error')
387 except Exception:
392 except Exception:
388 log.exception("Exception during ip saving")
393 log.exception("Exception during ip saving")
389 h.flash(_('An error occurred during ip saving'),
394 h.flash(_('An error occurred during ip saving'),
390 category='error')
395 category='error')
391 if added:
396 if added:
392 h.flash(
397 h.flash(
393 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
398 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
394 category='success')
399 category='success')
395 if 'default_user' in self.request.POST:
400 if 'default_user' in self.request.POST:
396 # case for editing global IP list we do it for 'DEFAULT' user
401 # case for editing global IP list we do it for 'DEFAULT' user
397 raise HTTPFound(h.route_path('admin_permissions_ips'))
402 raise HTTPFound(h.route_path('admin_permissions_ips'))
398 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
403 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
399
404
400 @LoginRequired()
405 @LoginRequired()
401 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
402 @CSRFRequired()
407 @CSRFRequired()
403 @view_config(
408 @view_config(
404 route_name='edit_user_ips_delete', request_method='POST')
409 route_name='edit_user_ips_delete', request_method='POST')
405 def ips_delete(self):
410 def ips_delete(self):
406 _ = self.request.translate
411 _ = self.request.translate
407 c = self.load_default_context()
412 c = self.load_default_context()
408
413
409 user_id = self.request.matchdict.get('user_id')
414 user_id = self.request.matchdict.get('user_id')
410 c.user = User.get_or_404(user_id)
415 c.user = User.get_or_404(user_id)
411 # NOTE(marcink): this view is allowed for default users, as we can
416 # NOTE(marcink): this view is allowed for default users, as we can
412 # edit their IP white list
417 # edit their IP white list
413
418
414 ip_id = self.request.POST.get('del_ip_id')
419 ip_id = self.request.POST.get('del_ip_id')
415 user_model = UserModel()
420 user_model = UserModel()
416 user_data = c.user.get_api_data()
421 user_data = c.user.get_api_data()
417 ip = UserIpMap.query().get(ip_id).ip_addr
422 ip = UserIpMap.query().get(ip_id).ip_addr
418 user_model.delete_extra_ip(c.user.user_id, ip_id)
423 user_model.delete_extra_ip(c.user.user_id, ip_id)
419 audit_logger.store_web(
424 audit_logger.store_web(
420 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
425 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
421 user=self._rhodecode_user)
426 user=self._rhodecode_user)
422 Session().commit()
427 Session().commit()
423 h.flash(_("Removed ip address from user whitelist"), category='success')
428 h.flash(_("Removed ip address from user whitelist"), category='success')
424
429
425 if 'default_user' in self.request.POST:
430 if 'default_user' in self.request.POST:
426 # case for editing global IP list we do it for 'DEFAULT' user
431 # case for editing global IP list we do it for 'DEFAULT' user
427 raise HTTPFound(h.route_path('admin_permissions_ips'))
432 raise HTTPFound(h.route_path('admin_permissions_ips'))
428 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
433 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
429
434
430 @LoginRequired()
435 @LoginRequired()
431 @HasPermissionAllDecorator('hg.admin')
436 @HasPermissionAllDecorator('hg.admin')
432 @view_config(
437 @view_config(
433 route_name='edit_user_groups_management', request_method='GET',
438 route_name='edit_user_groups_management', request_method='GET',
434 renderer='rhodecode:templates/admin/users/user_edit.mako')
439 renderer='rhodecode:templates/admin/users/user_edit.mako')
435 def groups_management(self):
440 def groups_management(self):
436 c = self.load_default_context()
441 c = self.load_default_context()
437
442
438 user_id = self.request.matchdict.get('user_id')
443 user_id = self.request.matchdict.get('user_id')
439 c.user = User.get_or_404(user_id)
444 c.user = User.get_or_404(user_id)
440 c.data = c.user.group_member
445 c.data = c.user.group_member
441 self._redirect_for_default_user(c.user.username)
446 self._redirect_for_default_user(c.user.username)
442 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
447 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
443 for group in c.user.group_member]
448 for group in c.user.group_member]
444 c.groups = json.dumps(groups)
449 c.groups = json.dumps(groups)
445 c.active = 'groups'
450 c.active = 'groups'
446
451
447 return self._get_template_context(c)
452 return self._get_template_context(c)
448
453
449 @LoginRequired()
454 @LoginRequired()
450 @HasPermissionAllDecorator('hg.admin')
455 @HasPermissionAllDecorator('hg.admin')
451 @CSRFRequired()
456 @CSRFRequired()
452 @view_config(
457 @view_config(
453 route_name='edit_user_groups_management_updates', request_method='POST')
458 route_name='edit_user_groups_management_updates', request_method='POST')
454 def groups_management_updates(self):
459 def groups_management_updates(self):
455 _ = self.request.translate
460 _ = self.request.translate
456 c = self.load_default_context()
461 c = self.load_default_context()
457
462
458 user_id = self.request.matchdict.get('user_id')
463 user_id = self.request.matchdict.get('user_id')
459 c.user = User.get_or_404(user_id)
464 c.user = User.get_or_404(user_id)
460 self._redirect_for_default_user(c.user.username)
465 self._redirect_for_default_user(c.user.username)
461
466
462 users_groups = set(self.request.POST.getall('users_group_id'))
467 users_groups = set(self.request.POST.getall('users_group_id'))
463 users_groups_model = []
468 users_groups_model = []
464
469
465 for ugid in users_groups:
470 for ugid in users_groups:
466 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
471 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
467 user_group_model = UserGroupModel()
472 user_group_model = UserGroupModel()
468 user_group_model.change_groups(c.user, users_groups_model)
473 user_group_model.change_groups(c.user, users_groups_model)
469
474
470 Session().commit()
475 Session().commit()
471 c.active = 'user_groups_management'
476 c.active = 'user_groups_management'
472 h.flash(_("Groups successfully changed"), category='success')
477 h.flash(_("Groups successfully changed"), category='success')
473
478
474 return HTTPFound(h.route_path(
479 return HTTPFound(h.route_path(
475 'edit_user_groups_management', user_id=user_id))
480 'edit_user_groups_management', user_id=user_id))
476
481
477 @LoginRequired()
482 @LoginRequired()
478 @HasPermissionAllDecorator('hg.admin')
483 @HasPermissionAllDecorator('hg.admin')
479 @view_config(
484 @view_config(
480 route_name='edit_user_audit_logs', request_method='GET',
485 route_name='edit_user_audit_logs', request_method='GET',
481 renderer='rhodecode:templates/admin/users/user_edit.mako')
486 renderer='rhodecode:templates/admin/users/user_edit.mako')
482 def user_audit_logs(self):
487 def user_audit_logs(self):
483 _ = self.request.translate
488 _ = self.request.translate
484 c = self.load_default_context()
489 c = self.load_default_context()
485
490
486 user_id = self.request.matchdict.get('user_id')
491 user_id = self.request.matchdict.get('user_id')
487 c.user = User.get_or_404(user_id)
492 c.user = User.get_or_404(user_id)
488 self._redirect_for_default_user(c.user.username)
493 self._redirect_for_default_user(c.user.username)
489 c.active = 'audit'
494 c.active = 'audit'
490
495
491 p = safe_int(self.request.GET.get('page', 1), 1)
496 p = safe_int(self.request.GET.get('page', 1), 1)
492
497
493 filter_term = self.request.GET.get('filter')
498 filter_term = self.request.GET.get('filter')
494 user_log = UserModel().get_user_log(c.user, filter_term)
499 user_log = UserModel().get_user_log(c.user, filter_term)
495
500
496 def url_generator(**kw):
501 def url_generator(**kw):
497 if filter_term:
502 if filter_term:
498 kw['filter'] = filter_term
503 kw['filter'] = filter_term
499 return self.request.current_route_path(_query=kw)
504 return self.request.current_route_path(_query=kw)
500
505
501 c.audit_logs = h.Page(
506 c.audit_logs = h.Page(
502 user_log, page=p, items_per_page=10, url=url_generator)
507 user_log, page=p, items_per_page=10, url=url_generator)
503 c.filter_term = filter_term
508 c.filter_term = filter_term
504 return self._get_template_context(c)
509 return self._get_template_context(c)
505
510
@@ -1,521 +1,515 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY ROUTES
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
194 requirements=URL_NAME_REQUIREMENTS)
195
195
196 # ADMIN REPOSITORY GROUPS ROUTES
196 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
198 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
199 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
200 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
201 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
202 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
203 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
204 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
206 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
207 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
208 requirements=URL_NAME_REQUIREMENTS)
209
209
210 # EXTRAS REPO GROUP ROUTES
210 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
212 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
213 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
214 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
216 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
217 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
218 requirements=URL_NAME_REQUIREMENTS)
219
219
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
221 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
222 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
225 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
226 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
228
228
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
230 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
231 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
232 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
234 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
235 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
236 requirements=URL_NAME_REQUIREMENTS)
237
237
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
239 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
240 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN USER ROUTES
243 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
245 controller='admin/users') as m:
246 m.connect('users', '/users',
246 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
248 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
249 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
250 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
251 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
252 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
253 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
254 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
256 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
257 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
259 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
261 action='create_personal_repo_group', conditions={'method': ['POST']})
262
262
263 # EXTRAS USER ROUTES
263 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
265 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
267 action='update_advanced', conditions={'method': ['PUT']})
268
268
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
270 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
272 action='update_global_perms', conditions={'method': ['PUT']})
273
273
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 action='edit_perms_summary', conditions={'method': ['GET']})
275 action='edit_perms_summary', conditions={'method': ['GET']})
276
276
277 # ADMIN USER GROUPS REST ROUTES
277 # ADMIN USER GROUPS REST ROUTES
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 controller='admin/user_groups') as m:
279 controller='admin/user_groups') as m:
280 m.connect('users_groups', '/user_groups',
280 m.connect('users_groups', '/user_groups',
281 action='create', conditions={'method': ['POST']})
281 action='create', conditions={'method': ['POST']})
282 m.connect('users_groups', '/user_groups',
283 action='index', conditions={'method': ['GET']})
284 m.connect('new_users_group', '/user_groups/new',
282 m.connect('new_users_group', '/user_groups/new',
285 action='new', conditions={'method': ['GET']})
283 action='new', conditions={'method': ['GET']})
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
284 m.connect('update_users_group', '/user_groups/{user_group_id}',
287 action='update', conditions={'method': ['PUT']})
285 action='update', conditions={'method': ['PUT']})
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
286 m.connect('delete_users_group', '/user_groups/{user_group_id}',
289 action='delete', conditions={'method': ['DELETE']})
287 action='delete', conditions={'method': ['DELETE']})
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
288 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
291 action='edit', conditions={'method': ['GET']},
289 action='edit', conditions={'method': ['GET']},
292 function=check_user_group)
290 function=check_user_group)
293
291
294 # EXTRAS USER GROUP ROUTES
292 # EXTRAS USER GROUP ROUTES
295 m.connect('edit_user_group_global_perms',
293 m.connect('edit_user_group_global_perms',
296 '/user_groups/{user_group_id}/edit/global_permissions',
294 '/user_groups/{user_group_id}/edit/global_permissions',
297 action='edit_global_perms', conditions={'method': ['GET']})
295 action='edit_global_perms', conditions={'method': ['GET']})
298 m.connect('edit_user_group_global_perms',
296 m.connect('edit_user_group_global_perms',
299 '/user_groups/{user_group_id}/edit/global_permissions',
297 '/user_groups/{user_group_id}/edit/global_permissions',
300 action='update_global_perms', conditions={'method': ['PUT']})
298 action='update_global_perms', conditions={'method': ['PUT']})
301 m.connect('edit_user_group_perms_summary',
299 m.connect('edit_user_group_perms_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
300 '/user_groups/{user_group_id}/edit/permissions_summary',
303 action='edit_perms_summary', conditions={'method': ['GET']})
301 action='edit_perms_summary', conditions={'method': ['GET']})
304
302
305 m.connect('edit_user_group_perms',
303 m.connect('edit_user_group_perms',
306 '/user_groups/{user_group_id}/edit/permissions',
304 '/user_groups/{user_group_id}/edit/permissions',
307 action='edit_perms', conditions={'method': ['GET']})
305 action='edit_perms', conditions={'method': ['GET']})
308 m.connect('edit_user_group_perms',
306 m.connect('edit_user_group_perms',
309 '/user_groups/{user_group_id}/edit/permissions',
307 '/user_groups/{user_group_id}/edit/permissions',
310 action='update_perms', conditions={'method': ['PUT']})
308 action='update_perms', conditions={'method': ['PUT']})
311
309
312 m.connect('edit_user_group_advanced',
310 m.connect('edit_user_group_advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
311 '/user_groups/{user_group_id}/edit/advanced',
314 action='edit_advanced', conditions={'method': ['GET']})
312 action='edit_advanced', conditions={'method': ['GET']})
315
313
316 m.connect('edit_user_group_advanced_sync',
314 m.connect('edit_user_group_advanced_sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
315 '/user_groups/{user_group_id}/edit/advanced/sync',
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
316 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
319
317
320 m.connect('edit_user_group_members',
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 action='user_group_members', conditions={'method': ['GET']})
323
324 # ADMIN DEFAULTS REST ROUTES
318 # ADMIN DEFAULTS REST ROUTES
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
319 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 controller='admin/defaults') as m:
320 controller='admin/defaults') as m:
327 m.connect('admin_defaults_repositories', '/defaults/repositories',
321 m.connect('admin_defaults_repositories', '/defaults/repositories',
328 action='update_repository_defaults', conditions={'method': ['POST']})
322 action='update_repository_defaults', conditions={'method': ['POST']})
329 m.connect('admin_defaults_repositories', '/defaults/repositories',
323 m.connect('admin_defaults_repositories', '/defaults/repositories',
330 action='index', conditions={'method': ['GET']})
324 action='index', conditions={'method': ['GET']})
331
325
332 # ADMIN SETTINGS ROUTES
326 # ADMIN SETTINGS ROUTES
333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
334 controller='admin/settings') as m:
328 controller='admin/settings') as m:
335
329
336 # default
330 # default
337 m.connect('admin_settings', '/settings',
331 m.connect('admin_settings', '/settings',
338 action='settings_global_update',
332 action='settings_global_update',
339 conditions={'method': ['POST']})
333 conditions={'method': ['POST']})
340 m.connect('admin_settings', '/settings',
334 m.connect('admin_settings', '/settings',
341 action='settings_global', conditions={'method': ['GET']})
335 action='settings_global', conditions={'method': ['GET']})
342
336
343 m.connect('admin_settings_vcs', '/settings/vcs',
337 m.connect('admin_settings_vcs', '/settings/vcs',
344 action='settings_vcs_update',
338 action='settings_vcs_update',
345 conditions={'method': ['POST']})
339 conditions={'method': ['POST']})
346 m.connect('admin_settings_vcs', '/settings/vcs',
340 m.connect('admin_settings_vcs', '/settings/vcs',
347 action='settings_vcs',
341 action='settings_vcs',
348 conditions={'method': ['GET']})
342 conditions={'method': ['GET']})
349 m.connect('admin_settings_vcs', '/settings/vcs',
343 m.connect('admin_settings_vcs', '/settings/vcs',
350 action='delete_svn_pattern',
344 action='delete_svn_pattern',
351 conditions={'method': ['DELETE']})
345 conditions={'method': ['DELETE']})
352
346
353 m.connect('admin_settings_mapping', '/settings/mapping',
347 m.connect('admin_settings_mapping', '/settings/mapping',
354 action='settings_mapping_update',
348 action='settings_mapping_update',
355 conditions={'method': ['POST']})
349 conditions={'method': ['POST']})
356 m.connect('admin_settings_mapping', '/settings/mapping',
350 m.connect('admin_settings_mapping', '/settings/mapping',
357 action='settings_mapping', conditions={'method': ['GET']})
351 action='settings_mapping', conditions={'method': ['GET']})
358
352
359 m.connect('admin_settings_global', '/settings/global',
353 m.connect('admin_settings_global', '/settings/global',
360 action='settings_global_update',
354 action='settings_global_update',
361 conditions={'method': ['POST']})
355 conditions={'method': ['POST']})
362 m.connect('admin_settings_global', '/settings/global',
356 m.connect('admin_settings_global', '/settings/global',
363 action='settings_global', conditions={'method': ['GET']})
357 action='settings_global', conditions={'method': ['GET']})
364
358
365 m.connect('admin_settings_visual', '/settings/visual',
359 m.connect('admin_settings_visual', '/settings/visual',
366 action='settings_visual_update',
360 action='settings_visual_update',
367 conditions={'method': ['POST']})
361 conditions={'method': ['POST']})
368 m.connect('admin_settings_visual', '/settings/visual',
362 m.connect('admin_settings_visual', '/settings/visual',
369 action='settings_visual', conditions={'method': ['GET']})
363 action='settings_visual', conditions={'method': ['GET']})
370
364
371 m.connect('admin_settings_issuetracker',
365 m.connect('admin_settings_issuetracker',
372 '/settings/issue-tracker', action='settings_issuetracker',
366 '/settings/issue-tracker', action='settings_issuetracker',
373 conditions={'method': ['GET']})
367 conditions={'method': ['GET']})
374 m.connect('admin_settings_issuetracker_save',
368 m.connect('admin_settings_issuetracker_save',
375 '/settings/issue-tracker/save',
369 '/settings/issue-tracker/save',
376 action='settings_issuetracker_save',
370 action='settings_issuetracker_save',
377 conditions={'method': ['POST']})
371 conditions={'method': ['POST']})
378 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
372 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
379 action='settings_issuetracker_test',
373 action='settings_issuetracker_test',
380 conditions={'method': ['POST']})
374 conditions={'method': ['POST']})
381 m.connect('admin_issuetracker_delete',
375 m.connect('admin_issuetracker_delete',
382 '/settings/issue-tracker/delete',
376 '/settings/issue-tracker/delete',
383 action='settings_issuetracker_delete',
377 action='settings_issuetracker_delete',
384 conditions={'method': ['DELETE']})
378 conditions={'method': ['DELETE']})
385
379
386 m.connect('admin_settings_email', '/settings/email',
380 m.connect('admin_settings_email', '/settings/email',
387 action='settings_email_update',
381 action='settings_email_update',
388 conditions={'method': ['POST']})
382 conditions={'method': ['POST']})
389 m.connect('admin_settings_email', '/settings/email',
383 m.connect('admin_settings_email', '/settings/email',
390 action='settings_email', conditions={'method': ['GET']})
384 action='settings_email', conditions={'method': ['GET']})
391
385
392 m.connect('admin_settings_hooks', '/settings/hooks',
386 m.connect('admin_settings_hooks', '/settings/hooks',
393 action='settings_hooks_update',
387 action='settings_hooks_update',
394 conditions={'method': ['POST', 'DELETE']})
388 conditions={'method': ['POST', 'DELETE']})
395 m.connect('admin_settings_hooks', '/settings/hooks',
389 m.connect('admin_settings_hooks', '/settings/hooks',
396 action='settings_hooks', conditions={'method': ['GET']})
390 action='settings_hooks', conditions={'method': ['GET']})
397
391
398 m.connect('admin_settings_search', '/settings/search',
392 m.connect('admin_settings_search', '/settings/search',
399 action='settings_search', conditions={'method': ['GET']})
393 action='settings_search', conditions={'method': ['GET']})
400
394
401 m.connect('admin_settings_supervisor', '/settings/supervisor',
395 m.connect('admin_settings_supervisor', '/settings/supervisor',
402 action='settings_supervisor', conditions={'method': ['GET']})
396 action='settings_supervisor', conditions={'method': ['GET']})
403 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
397 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
404 action='settings_supervisor_log', conditions={'method': ['GET']})
398 action='settings_supervisor_log', conditions={'method': ['GET']})
405
399
406 m.connect('admin_settings_labs', '/settings/labs',
400 m.connect('admin_settings_labs', '/settings/labs',
407 action='settings_labs_update',
401 action='settings_labs_update',
408 conditions={'method': ['POST']})
402 conditions={'method': ['POST']})
409 m.connect('admin_settings_labs', '/settings/labs',
403 m.connect('admin_settings_labs', '/settings/labs',
410 action='settings_labs', conditions={'method': ['GET']})
404 action='settings_labs', conditions={'method': ['GET']})
411
405
412 # ADMIN MY ACCOUNT
406 # ADMIN MY ACCOUNT
413 with rmap.submapper(path_prefix=ADMIN_PREFIX,
407 with rmap.submapper(path_prefix=ADMIN_PREFIX,
414 controller='admin/my_account') as m:
408 controller='admin/my_account') as m:
415
409
416 # NOTE(marcink): this needs to be kept for password force flag to be
410 # NOTE(marcink): this needs to be kept for password force flag to be
417 # handled in pylons controllers, remove after full migration to pyramid
411 # handled in pylons controllers, remove after full migration to pyramid
418 m.connect('my_account_password', '/my_account/password',
412 m.connect('my_account_password', '/my_account/password',
419 action='my_account_password', conditions={'method': ['GET']})
413 action='my_account_password', conditions={'method': ['GET']})
420
414
421 #==========================================================================
415 #==========================================================================
422 # REPOSITORY ROUTES
416 # REPOSITORY ROUTES
423 #==========================================================================
417 #==========================================================================
424
418
425 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
419 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
426 controller='admin/repos', action='repo_creating',
420 controller='admin/repos', action='repo_creating',
427 requirements=URL_NAME_REQUIREMENTS)
421 requirements=URL_NAME_REQUIREMENTS)
428 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
422 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
429 controller='admin/repos', action='repo_check',
423 controller='admin/repos', action='repo_check',
430 requirements=URL_NAME_REQUIREMENTS)
424 requirements=URL_NAME_REQUIREMENTS)
431
425
432 # repo edit options
426 # repo edit options
433 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
427 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
434 controller='admin/repos', action='edit_fields',
428 controller='admin/repos', action='edit_fields',
435 conditions={'method': ['GET'], 'function': check_repo},
429 conditions={'method': ['GET'], 'function': check_repo},
436 requirements=URL_NAME_REQUIREMENTS)
430 requirements=URL_NAME_REQUIREMENTS)
437 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
431 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
438 controller='admin/repos', action='create_repo_field',
432 controller='admin/repos', action='create_repo_field',
439 conditions={'method': ['PUT'], 'function': check_repo},
433 conditions={'method': ['PUT'], 'function': check_repo},
440 requirements=URL_NAME_REQUIREMENTS)
434 requirements=URL_NAME_REQUIREMENTS)
441 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
435 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
442 controller='admin/repos', action='delete_repo_field',
436 controller='admin/repos', action='delete_repo_field',
443 conditions={'method': ['DELETE'], 'function': check_repo},
437 conditions={'method': ['DELETE'], 'function': check_repo},
444 requirements=URL_NAME_REQUIREMENTS)
438 requirements=URL_NAME_REQUIREMENTS)
445
439
446 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
440 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
447 controller='admin/repos', action='toggle_locking',
441 controller='admin/repos', action='toggle_locking',
448 conditions={'method': ['GET'], 'function': check_repo},
442 conditions={'method': ['GET'], 'function': check_repo},
449 requirements=URL_NAME_REQUIREMENTS)
443 requirements=URL_NAME_REQUIREMENTS)
450
444
451 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
445 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
452 controller='admin/repos', action='edit_remote_form',
446 controller='admin/repos', action='edit_remote_form',
453 conditions={'method': ['GET'], 'function': check_repo},
447 conditions={'method': ['GET'], 'function': check_repo},
454 requirements=URL_NAME_REQUIREMENTS)
448 requirements=URL_NAME_REQUIREMENTS)
455 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
449 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
456 controller='admin/repos', action='edit_remote',
450 controller='admin/repos', action='edit_remote',
457 conditions={'method': ['PUT'], 'function': check_repo},
451 conditions={'method': ['PUT'], 'function': check_repo},
458 requirements=URL_NAME_REQUIREMENTS)
452 requirements=URL_NAME_REQUIREMENTS)
459
453
460 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
454 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
461 controller='admin/repos', action='edit_statistics_form',
455 controller='admin/repos', action='edit_statistics_form',
462 conditions={'method': ['GET'], 'function': check_repo},
456 conditions={'method': ['GET'], 'function': check_repo},
463 requirements=URL_NAME_REQUIREMENTS)
457 requirements=URL_NAME_REQUIREMENTS)
464 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
458 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
465 controller='admin/repos', action='edit_statistics',
459 controller='admin/repos', action='edit_statistics',
466 conditions={'method': ['PUT'], 'function': check_repo},
460 conditions={'method': ['PUT'], 'function': check_repo},
467 requirements=URL_NAME_REQUIREMENTS)
461 requirements=URL_NAME_REQUIREMENTS)
468 rmap.connect('repo_settings_issuetracker',
462 rmap.connect('repo_settings_issuetracker',
469 '/{repo_name}/settings/issue-tracker',
463 '/{repo_name}/settings/issue-tracker',
470 controller='admin/repos', action='repo_issuetracker',
464 controller='admin/repos', action='repo_issuetracker',
471 conditions={'method': ['GET'], 'function': check_repo},
465 conditions={'method': ['GET'], 'function': check_repo},
472 requirements=URL_NAME_REQUIREMENTS)
466 requirements=URL_NAME_REQUIREMENTS)
473 rmap.connect('repo_issuetracker_test',
467 rmap.connect('repo_issuetracker_test',
474 '/{repo_name}/settings/issue-tracker/test',
468 '/{repo_name}/settings/issue-tracker/test',
475 controller='admin/repos', action='repo_issuetracker_test',
469 controller='admin/repos', action='repo_issuetracker_test',
476 conditions={'method': ['POST'], 'function': check_repo},
470 conditions={'method': ['POST'], 'function': check_repo},
477 requirements=URL_NAME_REQUIREMENTS)
471 requirements=URL_NAME_REQUIREMENTS)
478 rmap.connect('repo_issuetracker_delete',
472 rmap.connect('repo_issuetracker_delete',
479 '/{repo_name}/settings/issue-tracker/delete',
473 '/{repo_name}/settings/issue-tracker/delete',
480 controller='admin/repos', action='repo_issuetracker_delete',
474 controller='admin/repos', action='repo_issuetracker_delete',
481 conditions={'method': ['DELETE'], 'function': check_repo},
475 conditions={'method': ['DELETE'], 'function': check_repo},
482 requirements=URL_NAME_REQUIREMENTS)
476 requirements=URL_NAME_REQUIREMENTS)
483 rmap.connect('repo_issuetracker_save',
477 rmap.connect('repo_issuetracker_save',
484 '/{repo_name}/settings/issue-tracker/save',
478 '/{repo_name}/settings/issue-tracker/save',
485 controller='admin/repos', action='repo_issuetracker_save',
479 controller='admin/repos', action='repo_issuetracker_save',
486 conditions={'method': ['POST'], 'function': check_repo},
480 conditions={'method': ['POST'], 'function': check_repo},
487 requirements=URL_NAME_REQUIREMENTS)
481 requirements=URL_NAME_REQUIREMENTS)
488 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
482 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
489 controller='admin/repos', action='repo_settings_vcs_update',
483 controller='admin/repos', action='repo_settings_vcs_update',
490 conditions={'method': ['POST'], 'function': check_repo},
484 conditions={'method': ['POST'], 'function': check_repo},
491 requirements=URL_NAME_REQUIREMENTS)
485 requirements=URL_NAME_REQUIREMENTS)
492 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
486 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
493 controller='admin/repos', action='repo_settings_vcs',
487 controller='admin/repos', action='repo_settings_vcs',
494 conditions={'method': ['GET'], 'function': check_repo},
488 conditions={'method': ['GET'], 'function': check_repo},
495 requirements=URL_NAME_REQUIREMENTS)
489 requirements=URL_NAME_REQUIREMENTS)
496 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
490 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
497 controller='admin/repos', action='repo_delete_svn_pattern',
491 controller='admin/repos', action='repo_delete_svn_pattern',
498 conditions={'method': ['DELETE'], 'function': check_repo},
492 conditions={'method': ['DELETE'], 'function': check_repo},
499 requirements=URL_NAME_REQUIREMENTS)
493 requirements=URL_NAME_REQUIREMENTS)
500 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
494 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
501 controller='admin/repos', action='repo_settings_pullrequest',
495 controller='admin/repos', action='repo_settings_pullrequest',
502 conditions={'method': ['GET', 'POST'], 'function': check_repo},
496 conditions={'method': ['GET', 'POST'], 'function': check_repo},
503 requirements=URL_NAME_REQUIREMENTS)
497 requirements=URL_NAME_REQUIREMENTS)
504
498
505
499
506 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
500 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
507 controller='forks', action='fork_create',
501 controller='forks', action='fork_create',
508 conditions={'function': check_repo, 'method': ['POST']},
502 conditions={'function': check_repo, 'method': ['POST']},
509 requirements=URL_NAME_REQUIREMENTS)
503 requirements=URL_NAME_REQUIREMENTS)
510
504
511 rmap.connect('repo_fork_home', '/{repo_name}/fork',
505 rmap.connect('repo_fork_home', '/{repo_name}/fork',
512 controller='forks', action='fork',
506 controller='forks', action='fork',
513 conditions={'function': check_repo},
507 conditions={'function': check_repo},
514 requirements=URL_NAME_REQUIREMENTS)
508 requirements=URL_NAME_REQUIREMENTS)
515
509
516 rmap.connect('repo_forks_home', '/{repo_name}/forks',
510 rmap.connect('repo_forks_home', '/{repo_name}/forks',
517 controller='forks', action='forks',
511 controller='forks', action='forks',
518 conditions={'function': check_repo},
512 conditions={'function': check_repo},
519 requirements=URL_NAME_REQUIREMENTS)
513 requirements=URL_NAME_REQUIREMENTS)
520
514
521 return rmap
515 return rmap
@@ -1,514 +1,448 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 import peppercorn
28 import peppercorn
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, config
30 from pylons import request, tmpl_context as c, url, config
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35
35
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
41 RepoGroupAssignmentError
41 RepoGroupAssignmentError
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
46 HasPermissionAnyDecorator, XHRRequired)
46 HasPermissionAnyDecorator, XHRRequired)
47 from rhodecode.lib.base import BaseController, render
47 from rhodecode.lib.base import BaseController, render
48 from rhodecode.model.permission import PermissionModel
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.scm import UserGroupList
49 from rhodecode.model.scm import UserGroupList
50 from rhodecode.model.user_group import UserGroupModel
50 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
53 from rhodecode.model.forms import (
53 from rhodecode.model.forms import (
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
55 UserPermissionsForm)
55 UserPermissionsForm)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class UserGroupsController(BaseController):
62 class UserGroupsController(BaseController):
63 """REST Controller styled on the Atom Publishing Protocol"""
63 """REST Controller styled on the Atom Publishing Protocol"""
64
64
65 @LoginRequired()
65 @LoginRequired()
66 def __before__(self):
66 def __before__(self):
67 super(UserGroupsController, self).__before__()
67 super(UserGroupsController, self).__before__()
68 c.available_permissions = config['available_permissions']
68 c.available_permissions = config['available_permissions']
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
70
70
71 def __load_data(self, user_group_id):
71 def __load_data(self, user_group_id):
72 c.group_members_obj = [x.user for x in c.user_group.members]
72 c.group_members_obj = [x.user for x in c.user_group.members]
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
75
75
76 def __load_defaults(self, user_group_id):
76 def __load_defaults(self, user_group_id):
77 """
77 """
78 Load defaults settings for edit, and update
78 Load defaults settings for edit, and update
79
79
80 :param user_group_id:
80 :param user_group_id:
81 """
81 """
82 user_group = UserGroup.get_or_404(user_group_id)
82 user_group = UserGroup.get_or_404(user_group_id)
83 data = user_group.get_dict()
83 data = user_group.get_dict()
84 # fill owner
84 # fill owner
85 if user_group.user:
85 if user_group.user:
86 data.update({'user': user_group.user.username})
86 data.update({'user': user_group.user.username})
87 else:
87 else:
88 replacement_user = User.get_first_super_admin().username
88 replacement_user = User.get_first_super_admin().username
89 data.update({'user': replacement_user})
89 data.update({'user': replacement_user})
90 return data
90 return data
91
91
92 def _revoke_perms_on_yourself(self, form_result):
92 def _revoke_perms_on_yourself(self, form_result):
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
94 form_result['perm_updates'])
94 form_result['perm_updates'])
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 form_result['perm_additions'])
96 form_result['perm_additions'])
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 form_result['perm_deletions'])
98 form_result['perm_deletions'])
99 admin_perm = 'usergroup.admin'
99 admin_perm = 'usergroup.admin'
100 if _updates and _updates[0][1] != admin_perm or \
100 if _updates and _updates[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
102 _deletions and _deletions[0][1] != admin_perm:
102 _deletions and _deletions[0][1] != admin_perm:
103 return True
103 return True
104 return False
104 return False
105
105
106 # permission check inside
107 @NotAnonymous()
108 def index(self):
109 # TODO(marcink): remove bind to self.request after pyramid migration
110 self.request = c.pyramid_request
111 _render = self.request.get_partial_renderer(
112 'data_table/_dt_elements.mako')
113
114 def user_group_name(user_group_id, user_group_name):
115 return _render("user_group_name", user_group_id, user_group_name)
116
117 def user_group_actions(user_group_id, user_group_name):
118 return _render("user_group_actions", user_group_id, user_group_name)
119
120 # json generate
121 group_iter = UserGroupList(UserGroup.query().all(),
122 perm_set=['usergroup.admin'])
123
124 user_groups_data = []
125 for user_gr in group_iter:
126 user_groups_data.append({
127 "group_name": user_group_name(
128 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
129 "group_name_raw": user_gr.users_group_name,
130 "desc": h.escape(user_gr.user_group_description),
131 "members": len(user_gr.members),
132 "sync": user_gr.group_data.get('extern_type'),
133 "active": h.bool2icon(user_gr.users_group_active),
134 "owner": h.escape(h.link_to_user(user_gr.user.username)),
135 "action": user_group_actions(
136 user_gr.users_group_id, user_gr.users_group_name)
137 })
138
139 c.data = json.dumps(user_groups_data)
140 return render('admin/user_groups/user_groups.mako')
141
142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
106 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
143 @auth.CSRFRequired()
107 @auth.CSRFRequired()
144 def create(self):
108 def create(self):
145
109
146 users_group_form = UserGroupForm()()
110 users_group_form = UserGroupForm()()
147 try:
111 try:
148 form_result = users_group_form.to_python(dict(request.POST))
112 form_result = users_group_form.to_python(dict(request.POST))
149 user_group = UserGroupModel().create(
113 user_group = UserGroupModel().create(
150 name=form_result['users_group_name'],
114 name=form_result['users_group_name'],
151 description=form_result['user_group_description'],
115 description=form_result['user_group_description'],
152 owner=c.rhodecode_user.user_id,
116 owner=c.rhodecode_user.user_id,
153 active=form_result['users_group_active'])
117 active=form_result['users_group_active'])
154 Session().flush()
118 Session().flush()
155 creation_data = user_group.get_api_data()
119 creation_data = user_group.get_api_data()
156 user_group_name = form_result['users_group_name']
120 user_group_name = form_result['users_group_name']
157
121
158 audit_logger.store_web(
122 audit_logger.store_web(
159 'user_group.create', action_data={'data': creation_data},
123 'user_group.create', action_data={'data': creation_data},
160 user=c.rhodecode_user)
124 user=c.rhodecode_user)
161
125
162 user_group_link = h.link_to(
126 user_group_link = h.link_to(
163 h.escape(user_group_name),
127 h.escape(user_group_name),
164 url('edit_users_group', user_group_id=user_group.users_group_id))
128 url('edit_users_group', user_group_id=user_group.users_group_id))
165 h.flash(h.literal(_('Created user group %(user_group_link)s')
129 h.flash(h.literal(_('Created user group %(user_group_link)s')
166 % {'user_group_link': user_group_link}),
130 % {'user_group_link': user_group_link}),
167 category='success')
131 category='success')
168 Session().commit()
132 Session().commit()
169 except formencode.Invalid as errors:
133 except formencode.Invalid as errors:
170 return htmlfill.render(
134 return htmlfill.render(
171 render('admin/user_groups/user_group_add.mako'),
135 render('admin/user_groups/user_group_add.mako'),
172 defaults=errors.value,
136 defaults=errors.value,
173 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
174 prefix_error=False,
138 prefix_error=False,
175 encoding="UTF-8",
139 encoding="UTF-8",
176 force_defaults=False)
140 force_defaults=False)
177 except Exception:
141 except Exception:
178 log.exception("Exception creating user group")
142 log.exception("Exception creating user group")
179 h.flash(_('Error occurred during creation of user group %s') \
143 h.flash(_('Error occurred during creation of user group %s') \
180 % request.POST.get('users_group_name'), category='error')
144 % request.POST.get('users_group_name'), category='error')
181
145
182 return redirect(
146 return redirect(
183 url('edit_users_group', user_group_id=user_group.users_group_id))
147 url('edit_users_group', user_group_id=user_group.users_group_id))
184
148
185 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
149 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
186 def new(self):
150 def new(self):
187 """GET /user_groups/new: Form to create a new item"""
151 """GET /user_groups/new: Form to create a new item"""
188 # url('new_users_group')
152 # url('new_users_group')
189 return render('admin/user_groups/user_group_add.mako')
153 return render('admin/user_groups/user_group_add.mako')
190
154
191 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
155 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
192 @auth.CSRFRequired()
156 @auth.CSRFRequired()
193 def update(self, user_group_id):
157 def update(self, user_group_id):
194
158
195 user_group_id = safe_int(user_group_id)
159 user_group_id = safe_int(user_group_id)
196 c.user_group = UserGroup.get_or_404(user_group_id)
160 c.user_group = UserGroup.get_or_404(user_group_id)
197 c.active = 'settings'
161 c.active = 'settings'
198 self.__load_data(user_group_id)
162 self.__load_data(user_group_id)
199
163
200 users_group_form = UserGroupForm(
164 users_group_form = UserGroupForm(
201 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
165 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
202
166
203 old_values = c.user_group.get_api_data()
167 old_values = c.user_group.get_api_data()
204 try:
168 try:
205 form_result = users_group_form.to_python(request.POST)
169 form_result = users_group_form.to_python(request.POST)
206 pstruct = peppercorn.parse(request.POST.items())
170 pstruct = peppercorn.parse(request.POST.items())
207 form_result['users_group_members'] = pstruct['user_group_members']
171 form_result['users_group_members'] = pstruct['user_group_members']
208
172
209 user_group, added_members, removed_members = \
173 user_group, added_members, removed_members = \
210 UserGroupModel().update(c.user_group, form_result)
174 UserGroupModel().update(c.user_group, form_result)
211 updated_user_group = form_result['users_group_name']
175 updated_user_group = form_result['users_group_name']
212
176
213 audit_logger.store_web(
177 audit_logger.store_web(
214 'user_group.edit', action_data={'old_data': old_values},
178 'user_group.edit', action_data={'old_data': old_values},
215 user=c.rhodecode_user)
179 user=c.rhodecode_user)
216
180
217 # TODO(marcink): use added/removed to set user_group.edit.member.add
181 # TODO(marcink): use added/removed to set user_group.edit.member.add
218
182
219 h.flash(_('Updated user group %s') % updated_user_group,
183 h.flash(_('Updated user group %s') % updated_user_group,
220 category='success')
184 category='success')
221 Session().commit()
185 Session().commit()
222 except formencode.Invalid as errors:
186 except formencode.Invalid as errors:
223 defaults = errors.value
187 defaults = errors.value
224 e = errors.error_dict or {}
188 e = errors.error_dict or {}
225
189
226 return htmlfill.render(
190 return htmlfill.render(
227 render('admin/user_groups/user_group_edit.mako'),
191 render('admin/user_groups/user_group_edit.mako'),
228 defaults=defaults,
192 defaults=defaults,
229 errors=e,
193 errors=e,
230 prefix_error=False,
194 prefix_error=False,
231 encoding="UTF-8",
195 encoding="UTF-8",
232 force_defaults=False)
196 force_defaults=False)
233 except Exception:
197 except Exception:
234 log.exception("Exception during update of user group")
198 log.exception("Exception during update of user group")
235 h.flash(_('Error occurred during update of user group %s')
199 h.flash(_('Error occurred during update of user group %s')
236 % request.POST.get('users_group_name'), category='error')
200 % request.POST.get('users_group_name'), category='error')
237
201
238 return redirect(url('edit_users_group', user_group_id=user_group_id))
202 return redirect(url('edit_users_group', user_group_id=user_group_id))
239
203
240 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
204 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
241 @auth.CSRFRequired()
205 @auth.CSRFRequired()
242 def delete(self, user_group_id):
206 def delete(self, user_group_id):
243 user_group_id = safe_int(user_group_id)
207 user_group_id = safe_int(user_group_id)
244 c.user_group = UserGroup.get_or_404(user_group_id)
208 c.user_group = UserGroup.get_or_404(user_group_id)
245 force = str2bool(request.POST.get('force'))
209 force = str2bool(request.POST.get('force'))
246
210
247 old_values = c.user_group.get_api_data()
211 old_values = c.user_group.get_api_data()
248 try:
212 try:
249 UserGroupModel().delete(c.user_group, force=force)
213 UserGroupModel().delete(c.user_group, force=force)
250 audit_logger.store_web(
214 audit_logger.store_web(
251 'user.delete', action_data={'old_data': old_values},
215 'user.delete', action_data={'old_data': old_values},
252 user=c.rhodecode_user)
216 user=c.rhodecode_user)
253 Session().commit()
217 Session().commit()
254 h.flash(_('Successfully deleted user group'), category='success')
218 h.flash(_('Successfully deleted user group'), category='success')
255 except UserGroupAssignedException as e:
219 except UserGroupAssignedException as e:
256 h.flash(str(e), category='error')
220 h.flash(str(e), category='error')
257 except Exception:
221 except Exception:
258 log.exception("Exception during deletion of user group")
222 log.exception("Exception during deletion of user group")
259 h.flash(_('An error occurred during deletion of user group'),
223 h.flash(_('An error occurred during deletion of user group'),
260 category='error')
224 category='error')
261 return redirect(url('users_groups'))
225 return redirect(url('users_groups'))
262
226
263 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
227 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
264 def edit(self, user_group_id):
228 def edit(self, user_group_id):
265 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
229 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
266 # url('edit_users_group', user_group_id=ID)
230 # url('edit_users_group', user_group_id=ID)
267
231
268 user_group_id = safe_int(user_group_id)
232 user_group_id = safe_int(user_group_id)
269 c.user_group = UserGroup.get_or_404(user_group_id)
233 c.user_group = UserGroup.get_or_404(user_group_id)
270 c.active = 'settings'
234 c.active = 'settings'
271 self.__load_data(user_group_id)
235 self.__load_data(user_group_id)
272
236
273 defaults = self.__load_defaults(user_group_id)
237 defaults = self.__load_defaults(user_group_id)
274
238
275 return htmlfill.render(
239 return htmlfill.render(
276 render('admin/user_groups/user_group_edit.mako'),
240 render('admin/user_groups/user_group_edit.mako'),
277 defaults=defaults,
241 defaults=defaults,
278 encoding="UTF-8",
242 encoding="UTF-8",
279 force_defaults=False
243 force_defaults=False
280 )
244 )
281
245
282 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
246 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
283 def edit_perms(self, user_group_id):
247 def edit_perms(self, user_group_id):
284 user_group_id = safe_int(user_group_id)
248 user_group_id = safe_int(user_group_id)
285 c.user_group = UserGroup.get_or_404(user_group_id)
249 c.user_group = UserGroup.get_or_404(user_group_id)
286 c.active = 'perms'
250 c.active = 'perms'
287
251
288 defaults = {}
252 defaults = {}
289 # fill user group users
253 # fill user group users
290 for p in c.user_group.user_user_group_to_perm:
254 for p in c.user_group.user_user_group_to_perm:
291 defaults.update({'u_perm_%s' % p.user.user_id:
255 defaults.update({'u_perm_%s' % p.user.user_id:
292 p.permission.permission_name})
256 p.permission.permission_name})
293
257
294 for p in c.user_group.user_group_user_group_to_perm:
258 for p in c.user_group.user_group_user_group_to_perm:
295 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
259 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
296 p.permission.permission_name})
260 p.permission.permission_name})
297
261
298 return htmlfill.render(
262 return htmlfill.render(
299 render('admin/user_groups/user_group_edit.mako'),
263 render('admin/user_groups/user_group_edit.mako'),
300 defaults=defaults,
264 defaults=defaults,
301 encoding="UTF-8",
265 encoding="UTF-8",
302 force_defaults=False
266 force_defaults=False
303 )
267 )
304
268
305 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
306 @auth.CSRFRequired()
270 @auth.CSRFRequired()
307 def update_perms(self, user_group_id):
271 def update_perms(self, user_group_id):
308 """
272 """
309 grant permission for given usergroup
273 grant permission for given usergroup
310
274
311 :param user_group_id:
275 :param user_group_id:
312 """
276 """
313 user_group_id = safe_int(user_group_id)
277 user_group_id = safe_int(user_group_id)
314 c.user_group = UserGroup.get_or_404(user_group_id)
278 c.user_group = UserGroup.get_or_404(user_group_id)
315 form = UserGroupPermsForm()().to_python(request.POST)
279 form = UserGroupPermsForm()().to_python(request.POST)
316
280
317 if not c.rhodecode_user.is_admin:
281 if not c.rhodecode_user.is_admin:
318 if self._revoke_perms_on_yourself(form):
282 if self._revoke_perms_on_yourself(form):
319 msg = _('Cannot change permission for yourself as admin')
283 msg = _('Cannot change permission for yourself as admin')
320 h.flash(msg, category='warning')
284 h.flash(msg, category='warning')
321 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
285 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
322
286
323 try:
287 try:
324 UserGroupModel().update_permissions(user_group_id,
288 UserGroupModel().update_permissions(user_group_id,
325 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
289 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
326 except RepoGroupAssignmentError:
290 except RepoGroupAssignmentError:
327 h.flash(_('Target group cannot be the same'), category='error')
291 h.flash(_('Target group cannot be the same'), category='error')
328 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
292 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
329
293
330 # TODO(marcink): implement global permissions
294 # TODO(marcink): implement global permissions
331 # audit_log.store_web('user_group.edit.permissions')
295 # audit_log.store_web('user_group.edit.permissions')
332 Session().commit()
296 Session().commit()
333 h.flash(_('User Group permissions updated'), category='success')
297 h.flash(_('User Group permissions updated'), category='success')
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
298 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
335
299
336 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
300 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
337 def edit_perms_summary(self, user_group_id):
301 def edit_perms_summary(self, user_group_id):
338 user_group_id = safe_int(user_group_id)
302 user_group_id = safe_int(user_group_id)
339 c.user_group = UserGroup.get_or_404(user_group_id)
303 c.user_group = UserGroup.get_or_404(user_group_id)
340 c.active = 'perms_summary'
304 c.active = 'perms_summary'
341 permissions = {
305 permissions = {
342 'repositories': {},
306 'repositories': {},
343 'repositories_groups': {},
307 'repositories_groups': {},
344 }
308 }
345 ugroup_repo_perms = UserGroupRepoToPerm.query()\
309 ugroup_repo_perms = UserGroupRepoToPerm.query()\
346 .options(joinedload(UserGroupRepoToPerm.permission))\
310 .options(joinedload(UserGroupRepoToPerm.permission))\
347 .options(joinedload(UserGroupRepoToPerm.repository))\
311 .options(joinedload(UserGroupRepoToPerm.repository))\
348 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
312 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
349 .all()
313 .all()
350
314
351 for gr in ugroup_repo_perms:
315 for gr in ugroup_repo_perms:
352 permissions['repositories'][gr.repository.repo_name] \
316 permissions['repositories'][gr.repository.repo_name] \
353 = gr.permission.permission_name
317 = gr.permission.permission_name
354
318
355 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
319 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
356 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
320 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
357 .options(joinedload(UserGroupRepoGroupToPerm.group))\
321 .options(joinedload(UserGroupRepoGroupToPerm.group))\
358 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
322 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
359 .all()
323 .all()
360
324
361 for gr in ugroup_group_perms:
325 for gr in ugroup_group_perms:
362 permissions['repositories_groups'][gr.group.group_name] \
326 permissions['repositories_groups'][gr.group.group_name] \
363 = gr.permission.permission_name
327 = gr.permission.permission_name
364 c.permissions = permissions
328 c.permissions = permissions
365 return render('admin/user_groups/user_group_edit.mako')
329 return render('admin/user_groups/user_group_edit.mako')
366
330
367 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
331 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
368 def edit_global_perms(self, user_group_id):
332 def edit_global_perms(self, user_group_id):
369 user_group_id = safe_int(user_group_id)
333 user_group_id = safe_int(user_group_id)
370 c.user_group = UserGroup.get_or_404(user_group_id)
334 c.user_group = UserGroup.get_or_404(user_group_id)
371 c.active = 'global_perms'
335 c.active = 'global_perms'
372
336
373 c.default_user = User.get_default_user()
337 c.default_user = User.get_default_user()
374 defaults = c.user_group.get_dict()
338 defaults = c.user_group.get_dict()
375 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
339 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
376 defaults.update(c.user_group.get_default_perms())
340 defaults.update(c.user_group.get_default_perms())
377
341
378 return htmlfill.render(
342 return htmlfill.render(
379 render('admin/user_groups/user_group_edit.mako'),
343 render('admin/user_groups/user_group_edit.mako'),
380 defaults=defaults,
344 defaults=defaults,
381 encoding="UTF-8",
345 encoding="UTF-8",
382 force_defaults=False
346 force_defaults=False
383 )
347 )
384
348
385 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
349 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
386 @auth.CSRFRequired()
350 @auth.CSRFRequired()
387 def update_global_perms(self, user_group_id):
351 def update_global_perms(self, user_group_id):
388 user_group_id = safe_int(user_group_id)
352 user_group_id = safe_int(user_group_id)
389 user_group = UserGroup.get_or_404(user_group_id)
353 user_group = UserGroup.get_or_404(user_group_id)
390 c.active = 'global_perms'
354 c.active = 'global_perms'
391
355
392 try:
356 try:
393 # first stage that verifies the checkbox
357 # first stage that verifies the checkbox
394 _form = UserIndividualPermissionsForm()
358 _form = UserIndividualPermissionsForm()
395 form_result = _form.to_python(dict(request.POST))
359 form_result = _form.to_python(dict(request.POST))
396 inherit_perms = form_result['inherit_default_permissions']
360 inherit_perms = form_result['inherit_default_permissions']
397 user_group.inherit_default_permissions = inherit_perms
361 user_group.inherit_default_permissions = inherit_perms
398 Session().add(user_group)
362 Session().add(user_group)
399
363
400 if not inherit_perms:
364 if not inherit_perms:
401 # only update the individual ones if we un check the flag
365 # only update the individual ones if we un check the flag
402 _form = UserPermissionsForm(
366 _form = UserPermissionsForm(
403 [x[0] for x in c.repo_create_choices],
367 [x[0] for x in c.repo_create_choices],
404 [x[0] for x in c.repo_create_on_write_choices],
368 [x[0] for x in c.repo_create_on_write_choices],
405 [x[0] for x in c.repo_group_create_choices],
369 [x[0] for x in c.repo_group_create_choices],
406 [x[0] for x in c.user_group_create_choices],
370 [x[0] for x in c.user_group_create_choices],
407 [x[0] for x in c.fork_choices],
371 [x[0] for x in c.fork_choices],
408 [x[0] for x in c.inherit_default_permission_choices])()
372 [x[0] for x in c.inherit_default_permission_choices])()
409
373
410 form_result = _form.to_python(dict(request.POST))
374 form_result = _form.to_python(dict(request.POST))
411 form_result.update({'perm_user_group_id': user_group.users_group_id})
375 form_result.update({'perm_user_group_id': user_group.users_group_id})
412
376
413 PermissionModel().update_user_group_permissions(form_result)
377 PermissionModel().update_user_group_permissions(form_result)
414
378
415 Session().commit()
379 Session().commit()
416 h.flash(_('User Group global permissions updated successfully'),
380 h.flash(_('User Group global permissions updated successfully'),
417 category='success')
381 category='success')
418
382
419 except formencode.Invalid as errors:
383 except formencode.Invalid as errors:
420 defaults = errors.value
384 defaults = errors.value
421 c.user_group = user_group
385 c.user_group = user_group
422 return htmlfill.render(
386 return htmlfill.render(
423 render('admin/user_groups/user_group_edit.mako'),
387 render('admin/user_groups/user_group_edit.mako'),
424 defaults=defaults,
388 defaults=defaults,
425 errors=errors.error_dict or {},
389 errors=errors.error_dict or {},
426 prefix_error=False,
390 prefix_error=False,
427 encoding="UTF-8",
391 encoding="UTF-8",
428 force_defaults=False)
392 force_defaults=False)
429 except Exception:
393 except Exception:
430 log.exception("Exception during permissions saving")
394 log.exception("Exception during permissions saving")
431 h.flash(_('An error occurred during permissions saving'),
395 h.flash(_('An error occurred during permissions saving'),
432 category='error')
396 category='error')
433
397
434 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
398 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
435
399
436 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
400 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
437 def edit_advanced(self, user_group_id):
401 def edit_advanced(self, user_group_id):
438 user_group_id = safe_int(user_group_id)
402 user_group_id = safe_int(user_group_id)
439 c.user_group = UserGroup.get_or_404(user_group_id)
403 c.user_group = UserGroup.get_or_404(user_group_id)
440 c.active = 'advanced'
404 c.active = 'advanced'
441 c.group_members_obj = sorted(
405 c.group_members_obj = sorted(
442 (x.user for x in c.user_group.members),
406 (x.user for x in c.user_group.members),
443 key=lambda u: u.username.lower())
407 key=lambda u: u.username.lower())
444
408
445 c.group_to_repos = sorted(
409 c.group_to_repos = sorted(
446 (x.repository for x in c.user_group.users_group_repo_to_perm),
410 (x.repository for x in c.user_group.users_group_repo_to_perm),
447 key=lambda u: u.repo_name.lower())
411 key=lambda u: u.repo_name.lower())
448
412
449 c.group_to_repo_groups = sorted(
413 c.group_to_repo_groups = sorted(
450 (x.group for x in c.user_group.users_group_repo_group_to_perm),
414 (x.group for x in c.user_group.users_group_repo_group_to_perm),
451 key=lambda u: u.group_name.lower())
415 key=lambda u: u.group_name.lower())
452
416
453 return render('admin/user_groups/user_group_edit.mako')
417 return render('admin/user_groups/user_group_edit.mako')
454
418
455 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
419 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
456 def edit_advanced_set_synchronization(self, user_group_id):
420 def edit_advanced_set_synchronization(self, user_group_id):
457 user_group_id = safe_int(user_group_id)
421 user_group_id = safe_int(user_group_id)
458 user_group = UserGroup.get_or_404(user_group_id)
422 user_group = UserGroup.get_or_404(user_group_id)
459
423
460 existing = user_group.group_data.get('extern_type')
424 existing = user_group.group_data.get('extern_type')
461
425
462 if existing:
426 if existing:
463 new_state = user_group.group_data
427 new_state = user_group.group_data
464 new_state['extern_type'] = None
428 new_state['extern_type'] = None
465 else:
429 else:
466 new_state = user_group.group_data
430 new_state = user_group.group_data
467 new_state['extern_type'] = 'manual'
431 new_state['extern_type'] = 'manual'
468 new_state['extern_type_set_by'] = c.rhodecode_user.username
432 new_state['extern_type_set_by'] = c.rhodecode_user.username
469
433
470 try:
434 try:
471 user_group.group_data = new_state
435 user_group.group_data = new_state
472 Session().add(user_group)
436 Session().add(user_group)
473 Session().commit()
437 Session().commit()
474
438
475 h.flash(_('User Group synchronization updated successfully'),
439 h.flash(_('User Group synchronization updated successfully'),
476 category='success')
440 category='success')
477 except Exception:
441 except Exception:
478 log.exception("Exception during sync settings saving")
442 log.exception("Exception during sync settings saving")
479 h.flash(_('An error occurred during synchronization update'),
443 h.flash(_('An error occurred during synchronization update'),
480 category='error')
444 category='error')
481
445
482 return redirect(
446 return redirect(
483 url('edit_user_group_advanced', user_group_id=user_group_id))
447 url('edit_user_group_advanced', user_group_id=user_group_id))
484
448
485 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
486 @XHRRequired()
487 @jsonify
488 def user_group_members(self, user_group_id):
489 """
490 Return members of given user group
491 """
492 user_group_id = safe_int(user_group_id)
493 user_group = UserGroup.get_or_404(user_group_id)
494 group_members_obj = sorted((x.user for x in user_group.members),
495 key=lambda u: u.username.lower())
496
497 group_members = [
498 {
499 'id': user.user_id,
500 'first_name': user.first_name,
501 'last_name': user.last_name,
502 'username': user.username,
503 'icon_link': h.gravatar_url(user.email, 30),
504 'value_display': h.person(user.email),
505 'value': user.username,
506 'value_type': 'user',
507 'active': user.active,
508 }
509 for user in group_members_obj
510 ]
511
512 return {
513 'members': group_members
514 }
@@ -1,982 +1,982 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45 from pyramid.request import Request
45 from pyramid.request import Request
46
46
47 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.fakemod import create_module
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
100 # pyramid
100 # pyramid
101 _repo = request.db_repo.repo_name
101 _repo = request.db_repo.repo_name
102 else:
102 else:
103 # TODO(marcink): remove after pylons migration...
103 # TODO(marcink): remove after pylons migration...
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105
105
106 if _repo:
106 if _repo:
107 _repo = _repo.rstrip('/')
107 _repo = _repo.rstrip('/')
108 return _repo
108 return _repo
109
109
110
110
111 def get_repo_group_slug(request):
111 def get_repo_group_slug(request):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
113 # pyramid
113 # pyramid
114 _group = request.matchdict.get('repo_group_name')
114 _group = request.matchdict.get('repo_group_name')
115 else:
115 else:
116 _group = request.environ['pylons.routes_dict'].get('group_name')
116 _group = request.environ['pylons.routes_dict'].get('group_name')
117
117
118 if _group:
118 if _group:
119 _group = _group.rstrip('/')
119 _group = _group.rstrip('/')
120 return _group
120 return _group
121
121
122
122
123 def get_user_group_slug(request):
123 def get_user_group_slug(request):
124 if isinstance(request, Request) and getattr(request, 'matchdict', None):
124 if isinstance(request, Request) and getattr(request, 'matchdict', None):
125 # pyramid
125 # pyramid
126 _group = request.matchdict.get('user_group_id')
126 _group = request.matchdict.get('user_group_id')
127 else:
127 else:
128 _group = request.environ['pylons.routes_dict'].get('user_group_id')
128 _group = request.environ['pylons.routes_dict'].get('user_group_id')
129
129
130 try:
130 try:
131 _group = UserGroup.get(_group)
131 _group = UserGroup.get(_group)
132 if _group:
132 if _group:
133 _group = _group.users_group_name
133 _group = _group.users_group_name
134 except Exception:
134 except Exception:
135 log.debug(traceback.format_exc())
135 log.exception('Failed to get user group by id')
136 # catch all failures here
136 # catch all failures here
137 pass
137 return None
138
138
139 return _group
139 return _group
140
140
141
141
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
143 """
143 """
144 Scans given path for repos and return (name,(type,path)) tuple
144 Scans given path for repos and return (name,(type,path)) tuple
145
145
146 :param path: path to scan for repositories
146 :param path: path to scan for repositories
147 :param recursive: recursive search and return names with subdirs in front
147 :param recursive: recursive search and return names with subdirs in front
148 """
148 """
149
149
150 # remove ending slash for better results
150 # remove ending slash for better results
151 path = path.rstrip(os.sep)
151 path = path.rstrip(os.sep)
152 log.debug('now scanning in %s location recursive:%s...', path, recursive)
152 log.debug('now scanning in %s location recursive:%s...', path, recursive)
153
153
154 def _get_repos(p):
154 def _get_repos(p):
155 dirpaths = _get_dirpaths(p)
155 dirpaths = _get_dirpaths(p)
156 if not _is_dir_writable(p):
156 if not _is_dir_writable(p):
157 log.warning('repo path without write access: %s', p)
157 log.warning('repo path without write access: %s', p)
158
158
159 for dirpath in dirpaths:
159 for dirpath in dirpaths:
160 if os.path.isfile(os.path.join(p, dirpath)):
160 if os.path.isfile(os.path.join(p, dirpath)):
161 continue
161 continue
162 cur_path = os.path.join(p, dirpath)
162 cur_path = os.path.join(p, dirpath)
163
163
164 # skip removed repos
164 # skip removed repos
165 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
165 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
166 continue
166 continue
167
167
168 #skip .<somethin> dirs
168 #skip .<somethin> dirs
169 if dirpath.startswith('.'):
169 if dirpath.startswith('.'):
170 continue
170 continue
171
171
172 try:
172 try:
173 scm_info = get_scm(cur_path)
173 scm_info = get_scm(cur_path)
174 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
174 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
175 except VCSError:
175 except VCSError:
176 if not recursive:
176 if not recursive:
177 continue
177 continue
178 #check if this dir containts other repos for recursive scan
178 #check if this dir containts other repos for recursive scan
179 rec_path = os.path.join(p, dirpath)
179 rec_path = os.path.join(p, dirpath)
180 if os.path.isdir(rec_path):
180 if os.path.isdir(rec_path):
181 for inner_scm in _get_repos(rec_path):
181 for inner_scm in _get_repos(rec_path):
182 yield inner_scm
182 yield inner_scm
183
183
184 return _get_repos(path)
184 return _get_repos(path)
185
185
186
186
187 def _get_dirpaths(p):
187 def _get_dirpaths(p):
188 try:
188 try:
189 # OS-independable way of checking if we have at least read-only
189 # OS-independable way of checking if we have at least read-only
190 # access or not.
190 # access or not.
191 dirpaths = os.listdir(p)
191 dirpaths = os.listdir(p)
192 except OSError:
192 except OSError:
193 log.warning('ignoring repo path without read access: %s', p)
193 log.warning('ignoring repo path without read access: %s', p)
194 return []
194 return []
195
195
196 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
196 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
197 # decode paths and suddenly returns unicode objects itself. The items it
197 # decode paths and suddenly returns unicode objects itself. The items it
198 # cannot decode are returned as strings and cause issues.
198 # cannot decode are returned as strings and cause issues.
199 #
199 #
200 # Those paths are ignored here until a solid solution for path handling has
200 # Those paths are ignored here until a solid solution for path handling has
201 # been built.
201 # been built.
202 expected_type = type(p)
202 expected_type = type(p)
203
203
204 def _has_correct_type(item):
204 def _has_correct_type(item):
205 if type(item) is not expected_type:
205 if type(item) is not expected_type:
206 log.error(
206 log.error(
207 u"Ignoring path %s since it cannot be decoded into unicode.",
207 u"Ignoring path %s since it cannot be decoded into unicode.",
208 # Using "repr" to make sure that we see the byte value in case
208 # Using "repr" to make sure that we see the byte value in case
209 # of support.
209 # of support.
210 repr(item))
210 repr(item))
211 return False
211 return False
212 return True
212 return True
213
213
214 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
214 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
215
215
216 return dirpaths
216 return dirpaths
217
217
218
218
219 def _is_dir_writable(path):
219 def _is_dir_writable(path):
220 """
220 """
221 Probe if `path` is writable.
221 Probe if `path` is writable.
222
222
223 Due to trouble on Cygwin / Windows, this is actually probing if it is
223 Due to trouble on Cygwin / Windows, this is actually probing if it is
224 possible to create a file inside of `path`, stat does not produce reliable
224 possible to create a file inside of `path`, stat does not produce reliable
225 results in this case.
225 results in this case.
226 """
226 """
227 try:
227 try:
228 with tempfile.TemporaryFile(dir=path):
228 with tempfile.TemporaryFile(dir=path):
229 pass
229 pass
230 except OSError:
230 except OSError:
231 return False
231 return False
232 return True
232 return True
233
233
234
234
235 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
235 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
236 """
236 """
237 Returns True if given path is a valid repository False otherwise.
237 Returns True if given path is a valid repository False otherwise.
238 If expect_scm param is given also, compare if given scm is the same
238 If expect_scm param is given also, compare if given scm is the same
239 as expected from scm parameter. If explicit_scm is given don't try to
239 as expected from scm parameter. If explicit_scm is given don't try to
240 detect the scm, just use the given one to check if repo is valid
240 detect the scm, just use the given one to check if repo is valid
241
241
242 :param repo_name:
242 :param repo_name:
243 :param base_path:
243 :param base_path:
244 :param expect_scm:
244 :param expect_scm:
245 :param explicit_scm:
245 :param explicit_scm:
246
246
247 :return True: if given path is a valid repository
247 :return True: if given path is a valid repository
248 """
248 """
249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
250 log.debug('Checking if `%s` is a valid path for repository. '
250 log.debug('Checking if `%s` is a valid path for repository. '
251 'Explicit type: %s', repo_name, explicit_scm)
251 'Explicit type: %s', repo_name, explicit_scm)
252
252
253 try:
253 try:
254 if explicit_scm:
254 if explicit_scm:
255 detected_scms = [get_scm_backend(explicit_scm)]
255 detected_scms = [get_scm_backend(explicit_scm)]
256 else:
256 else:
257 detected_scms = get_scm(full_path)
257 detected_scms = get_scm(full_path)
258
258
259 if expect_scm:
259 if expect_scm:
260 return detected_scms[0] == expect_scm
260 return detected_scms[0] == expect_scm
261 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
261 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
262 return True
262 return True
263 except VCSError:
263 except VCSError:
264 log.debug('path: %s is not a valid repo !', full_path)
264 log.debug('path: %s is not a valid repo !', full_path)
265 return False
265 return False
266
266
267
267
268 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
268 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
269 """
269 """
270 Returns True if given path is a repository group, False otherwise
270 Returns True if given path is a repository group, False otherwise
271
271
272 :param repo_name:
272 :param repo_name:
273 :param base_path:
273 :param base_path:
274 """
274 """
275 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
275 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
276 log.debug('Checking if `%s` is a valid path for repository group',
276 log.debug('Checking if `%s` is a valid path for repository group',
277 repo_group_name)
277 repo_group_name)
278
278
279 # check if it's not a repo
279 # check if it's not a repo
280 if is_valid_repo(repo_group_name, base_path):
280 if is_valid_repo(repo_group_name, base_path):
281 log.debug('Repo called %s exist, it is not a valid '
281 log.debug('Repo called %s exist, it is not a valid '
282 'repo group' % repo_group_name)
282 'repo group' % repo_group_name)
283 return False
283 return False
284
284
285 try:
285 try:
286 # we need to check bare git repos at higher level
286 # we need to check bare git repos at higher level
287 # since we might match branches/hooks/info/objects or possible
287 # since we might match branches/hooks/info/objects or possible
288 # other things inside bare git repo
288 # other things inside bare git repo
289 scm_ = get_scm(os.path.dirname(full_path))
289 scm_ = get_scm(os.path.dirname(full_path))
290 log.debug('path: %s is a vcs object:%s, not valid '
290 log.debug('path: %s is a vcs object:%s, not valid '
291 'repo group' % (full_path, scm_))
291 'repo group' % (full_path, scm_))
292 return False
292 return False
293 except VCSError:
293 except VCSError:
294 pass
294 pass
295
295
296 # check if it's a valid path
296 # check if it's a valid path
297 if skip_path_check or os.path.isdir(full_path):
297 if skip_path_check or os.path.isdir(full_path):
298 log.debug('path: %s is a valid repo group !', full_path)
298 log.debug('path: %s is a valid repo group !', full_path)
299 return True
299 return True
300
300
301 log.debug('path: %s is not a valid repo group !', full_path)
301 log.debug('path: %s is not a valid repo group !', full_path)
302 return False
302 return False
303
303
304
304
305 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
305 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
306 while True:
306 while True:
307 ok = raw_input(prompt)
307 ok = raw_input(prompt)
308 if ok.lower() in ('y', 'ye', 'yes'):
308 if ok.lower() in ('y', 'ye', 'yes'):
309 return True
309 return True
310 if ok.lower() in ('n', 'no', 'nop', 'nope'):
310 if ok.lower() in ('n', 'no', 'nop', 'nope'):
311 return False
311 return False
312 retries = retries - 1
312 retries = retries - 1
313 if retries < 0:
313 if retries < 0:
314 raise IOError
314 raise IOError
315 print(complaint)
315 print(complaint)
316
316
317 # propagated from mercurial documentation
317 # propagated from mercurial documentation
318 ui_sections = [
318 ui_sections = [
319 'alias', 'auth',
319 'alias', 'auth',
320 'decode/encode', 'defaults',
320 'decode/encode', 'defaults',
321 'diff', 'email',
321 'diff', 'email',
322 'extensions', 'format',
322 'extensions', 'format',
323 'merge-patterns', 'merge-tools',
323 'merge-patterns', 'merge-tools',
324 'hooks', 'http_proxy',
324 'hooks', 'http_proxy',
325 'smtp', 'patch',
325 'smtp', 'patch',
326 'paths', 'profiling',
326 'paths', 'profiling',
327 'server', 'trusted',
327 'server', 'trusted',
328 'ui', 'web', ]
328 'ui', 'web', ]
329
329
330
330
331 def config_data_from_db(clear_session=True, repo=None):
331 def config_data_from_db(clear_session=True, repo=None):
332 """
332 """
333 Read the configuration data from the database and return configuration
333 Read the configuration data from the database and return configuration
334 tuples.
334 tuples.
335 """
335 """
336 from rhodecode.model.settings import VcsSettingsModel
336 from rhodecode.model.settings import VcsSettingsModel
337
337
338 config = []
338 config = []
339
339
340 sa = meta.Session()
340 sa = meta.Session()
341 settings_model = VcsSettingsModel(repo=repo, sa=sa)
341 settings_model = VcsSettingsModel(repo=repo, sa=sa)
342
342
343 ui_settings = settings_model.get_ui_settings()
343 ui_settings = settings_model.get_ui_settings()
344
344
345 for setting in ui_settings:
345 for setting in ui_settings:
346 if setting.active:
346 if setting.active:
347 log.debug(
347 log.debug(
348 'settings ui from db: [%s] %s=%s',
348 'settings ui from db: [%s] %s=%s',
349 setting.section, setting.key, setting.value)
349 setting.section, setting.key, setting.value)
350 config.append((
350 config.append((
351 safe_str(setting.section), safe_str(setting.key),
351 safe_str(setting.section), safe_str(setting.key),
352 safe_str(setting.value)))
352 safe_str(setting.value)))
353 if setting.key == 'push_ssl':
353 if setting.key == 'push_ssl':
354 # force set push_ssl requirement to False, rhodecode
354 # force set push_ssl requirement to False, rhodecode
355 # handles that
355 # handles that
356 config.append((
356 config.append((
357 safe_str(setting.section), safe_str(setting.key), False))
357 safe_str(setting.section), safe_str(setting.key), False))
358 if clear_session:
358 if clear_session:
359 meta.Session.remove()
359 meta.Session.remove()
360
360
361 # TODO: mikhail: probably it makes no sense to re-read hooks information.
361 # TODO: mikhail: probably it makes no sense to re-read hooks information.
362 # It's already there and activated/deactivated
362 # It's already there and activated/deactivated
363 skip_entries = []
363 skip_entries = []
364 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
364 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
365 if 'pull' not in enabled_hook_classes:
365 if 'pull' not in enabled_hook_classes:
366 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
366 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
367 if 'push' not in enabled_hook_classes:
367 if 'push' not in enabled_hook_classes:
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
371
371
372 config = [entry for entry in config if entry[:2] not in skip_entries]
372 config = [entry for entry in config if entry[:2] not in skip_entries]
373
373
374 return config
374 return config
375
375
376
376
377 def make_db_config(clear_session=True, repo=None):
377 def make_db_config(clear_session=True, repo=None):
378 """
378 """
379 Create a :class:`Config` instance based on the values in the database.
379 Create a :class:`Config` instance based on the values in the database.
380 """
380 """
381 config = Config()
381 config = Config()
382 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
382 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
383 for section, option, value in config_data:
383 for section, option, value in config_data:
384 config.set(section, option, value)
384 config.set(section, option, value)
385 return config
385 return config
386
386
387
387
388 def get_enabled_hook_classes(ui_settings):
388 def get_enabled_hook_classes(ui_settings):
389 """
389 """
390 Return the enabled hook classes.
390 Return the enabled hook classes.
391
391
392 :param ui_settings: List of ui_settings as returned
392 :param ui_settings: List of ui_settings as returned
393 by :meth:`VcsSettingsModel.get_ui_settings`
393 by :meth:`VcsSettingsModel.get_ui_settings`
394
394
395 :return: a list with the enabled hook classes. The order is not guaranteed.
395 :return: a list with the enabled hook classes. The order is not guaranteed.
396 :rtype: list
396 :rtype: list
397 """
397 """
398 enabled_hooks = []
398 enabled_hooks = []
399 active_hook_keys = [
399 active_hook_keys = [
400 key for section, key, value, active in ui_settings
400 key for section, key, value, active in ui_settings
401 if section == 'hooks' and active]
401 if section == 'hooks' and active]
402
402
403 hook_names = {
403 hook_names = {
404 RhodeCodeUi.HOOK_PUSH: 'push',
404 RhodeCodeUi.HOOK_PUSH: 'push',
405 RhodeCodeUi.HOOK_PULL: 'pull',
405 RhodeCodeUi.HOOK_PULL: 'pull',
406 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
406 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
407 }
407 }
408
408
409 for key in active_hook_keys:
409 for key in active_hook_keys:
410 hook = hook_names.get(key)
410 hook = hook_names.get(key)
411 if hook:
411 if hook:
412 enabled_hooks.append(hook)
412 enabled_hooks.append(hook)
413
413
414 return enabled_hooks
414 return enabled_hooks
415
415
416
416
417 def set_rhodecode_config(config):
417 def set_rhodecode_config(config):
418 """
418 """
419 Updates pylons config with new settings from database
419 Updates pylons config with new settings from database
420
420
421 :param config:
421 :param config:
422 """
422 """
423 from rhodecode.model.settings import SettingsModel
423 from rhodecode.model.settings import SettingsModel
424 app_settings = SettingsModel().get_all_settings()
424 app_settings = SettingsModel().get_all_settings()
425
425
426 for k, v in app_settings.items():
426 for k, v in app_settings.items():
427 config[k] = v
427 config[k] = v
428
428
429
429
430 def get_rhodecode_realm():
430 def get_rhodecode_realm():
431 """
431 """
432 Return the rhodecode realm from database.
432 Return the rhodecode realm from database.
433 """
433 """
434 from rhodecode.model.settings import SettingsModel
434 from rhodecode.model.settings import SettingsModel
435 realm = SettingsModel().get_setting_by_name('realm')
435 realm = SettingsModel().get_setting_by_name('realm')
436 return safe_str(realm.app_settings_value)
436 return safe_str(realm.app_settings_value)
437
437
438
438
439 def get_rhodecode_base_path():
439 def get_rhodecode_base_path():
440 """
440 """
441 Returns the base path. The base path is the filesystem path which points
441 Returns the base path. The base path is the filesystem path which points
442 to the repository store.
442 to the repository store.
443 """
443 """
444 from rhodecode.model.settings import SettingsModel
444 from rhodecode.model.settings import SettingsModel
445 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
445 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
446 return safe_str(paths_ui.ui_value)
446 return safe_str(paths_ui.ui_value)
447
447
448
448
449 def map_groups(path):
449 def map_groups(path):
450 """
450 """
451 Given a full path to a repository, create all nested groups that this
451 Given a full path to a repository, create all nested groups that this
452 repo is inside. This function creates parent-child relationships between
452 repo is inside. This function creates parent-child relationships between
453 groups and creates default perms for all new groups.
453 groups and creates default perms for all new groups.
454
454
455 :param paths: full path to repository
455 :param paths: full path to repository
456 """
456 """
457 from rhodecode.model.repo_group import RepoGroupModel
457 from rhodecode.model.repo_group import RepoGroupModel
458 sa = meta.Session()
458 sa = meta.Session()
459 groups = path.split(Repository.NAME_SEP)
459 groups = path.split(Repository.NAME_SEP)
460 parent = None
460 parent = None
461 group = None
461 group = None
462
462
463 # last element is repo in nested groups structure
463 # last element is repo in nested groups structure
464 groups = groups[:-1]
464 groups = groups[:-1]
465 rgm = RepoGroupModel(sa)
465 rgm = RepoGroupModel(sa)
466 owner = User.get_first_super_admin()
466 owner = User.get_first_super_admin()
467 for lvl, group_name in enumerate(groups):
467 for lvl, group_name in enumerate(groups):
468 group_name = '/'.join(groups[:lvl] + [group_name])
468 group_name = '/'.join(groups[:lvl] + [group_name])
469 group = RepoGroup.get_by_group_name(group_name)
469 group = RepoGroup.get_by_group_name(group_name)
470 desc = '%s group' % group_name
470 desc = '%s group' % group_name
471
471
472 # skip folders that are now removed repos
472 # skip folders that are now removed repos
473 if REMOVED_REPO_PAT.match(group_name):
473 if REMOVED_REPO_PAT.match(group_name):
474 break
474 break
475
475
476 if group is None:
476 if group is None:
477 log.debug('creating group level: %s group_name: %s',
477 log.debug('creating group level: %s group_name: %s',
478 lvl, group_name)
478 lvl, group_name)
479 group = RepoGroup(group_name, parent)
479 group = RepoGroup(group_name, parent)
480 group.group_description = desc
480 group.group_description = desc
481 group.user = owner
481 group.user = owner
482 sa.add(group)
482 sa.add(group)
483 perm_obj = rgm._create_default_perms(group)
483 perm_obj = rgm._create_default_perms(group)
484 sa.add(perm_obj)
484 sa.add(perm_obj)
485 sa.flush()
485 sa.flush()
486
486
487 parent = group
487 parent = group
488 return group
488 return group
489
489
490
490
491 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
491 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
492 """
492 """
493 maps all repos given in initial_repo_list, non existing repositories
493 maps all repos given in initial_repo_list, non existing repositories
494 are created, if remove_obsolete is True it also checks for db entries
494 are created, if remove_obsolete is True it also checks for db entries
495 that are not in initial_repo_list and removes them.
495 that are not in initial_repo_list and removes them.
496
496
497 :param initial_repo_list: list of repositories found by scanning methods
497 :param initial_repo_list: list of repositories found by scanning methods
498 :param remove_obsolete: check for obsolete entries in database
498 :param remove_obsolete: check for obsolete entries in database
499 """
499 """
500 from rhodecode.model.repo import RepoModel
500 from rhodecode.model.repo import RepoModel
501 from rhodecode.model.scm import ScmModel
501 from rhodecode.model.scm import ScmModel
502 from rhodecode.model.repo_group import RepoGroupModel
502 from rhodecode.model.repo_group import RepoGroupModel
503 from rhodecode.model.settings import SettingsModel
503 from rhodecode.model.settings import SettingsModel
504
504
505 sa = meta.Session()
505 sa = meta.Session()
506 repo_model = RepoModel()
506 repo_model = RepoModel()
507 user = User.get_first_super_admin()
507 user = User.get_first_super_admin()
508 added = []
508 added = []
509
509
510 # creation defaults
510 # creation defaults
511 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
511 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
512 enable_statistics = defs.get('repo_enable_statistics')
512 enable_statistics = defs.get('repo_enable_statistics')
513 enable_locking = defs.get('repo_enable_locking')
513 enable_locking = defs.get('repo_enable_locking')
514 enable_downloads = defs.get('repo_enable_downloads')
514 enable_downloads = defs.get('repo_enable_downloads')
515 private = defs.get('repo_private')
515 private = defs.get('repo_private')
516
516
517 for name, repo in initial_repo_list.items():
517 for name, repo in initial_repo_list.items():
518 group = map_groups(name)
518 group = map_groups(name)
519 unicode_name = safe_unicode(name)
519 unicode_name = safe_unicode(name)
520 db_repo = repo_model.get_by_repo_name(unicode_name)
520 db_repo = repo_model.get_by_repo_name(unicode_name)
521 # found repo that is on filesystem not in RhodeCode database
521 # found repo that is on filesystem not in RhodeCode database
522 if not db_repo:
522 if not db_repo:
523 log.info('repository %s not found, creating now', name)
523 log.info('repository %s not found, creating now', name)
524 added.append(name)
524 added.append(name)
525 desc = (repo.description
525 desc = (repo.description
526 if repo.description != 'unknown'
526 if repo.description != 'unknown'
527 else '%s repository' % name)
527 else '%s repository' % name)
528
528
529 db_repo = repo_model._create_repo(
529 db_repo = repo_model._create_repo(
530 repo_name=name,
530 repo_name=name,
531 repo_type=repo.alias,
531 repo_type=repo.alias,
532 description=desc,
532 description=desc,
533 repo_group=getattr(group, 'group_id', None),
533 repo_group=getattr(group, 'group_id', None),
534 owner=user,
534 owner=user,
535 enable_locking=enable_locking,
535 enable_locking=enable_locking,
536 enable_downloads=enable_downloads,
536 enable_downloads=enable_downloads,
537 enable_statistics=enable_statistics,
537 enable_statistics=enable_statistics,
538 private=private,
538 private=private,
539 state=Repository.STATE_CREATED
539 state=Repository.STATE_CREATED
540 )
540 )
541 sa.commit()
541 sa.commit()
542 # we added that repo just now, and make sure we updated server info
542 # we added that repo just now, and make sure we updated server info
543 if db_repo.repo_type == 'git':
543 if db_repo.repo_type == 'git':
544 git_repo = db_repo.scm_instance()
544 git_repo = db_repo.scm_instance()
545 # update repository server-info
545 # update repository server-info
546 log.debug('Running update server info')
546 log.debug('Running update server info')
547 git_repo._update_server_info()
547 git_repo._update_server_info()
548
548
549 db_repo.update_commit_cache()
549 db_repo.update_commit_cache()
550
550
551 config = db_repo._config
551 config = db_repo._config
552 config.set('extensions', 'largefiles', '')
552 config.set('extensions', 'largefiles', '')
553 ScmModel().install_hooks(
553 ScmModel().install_hooks(
554 db_repo.scm_instance(config=config),
554 db_repo.scm_instance(config=config),
555 repo_type=db_repo.repo_type)
555 repo_type=db_repo.repo_type)
556
556
557 removed = []
557 removed = []
558 if remove_obsolete:
558 if remove_obsolete:
559 # remove from database those repositories that are not in the filesystem
559 # remove from database those repositories that are not in the filesystem
560 for repo in sa.query(Repository).all():
560 for repo in sa.query(Repository).all():
561 if repo.repo_name not in initial_repo_list.keys():
561 if repo.repo_name not in initial_repo_list.keys():
562 log.debug("Removing non-existing repository found in db `%s`",
562 log.debug("Removing non-existing repository found in db `%s`",
563 repo.repo_name)
563 repo.repo_name)
564 try:
564 try:
565 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
565 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
566 sa.commit()
566 sa.commit()
567 removed.append(repo.repo_name)
567 removed.append(repo.repo_name)
568 except Exception:
568 except Exception:
569 # don't hold further removals on error
569 # don't hold further removals on error
570 log.error(traceback.format_exc())
570 log.error(traceback.format_exc())
571 sa.rollback()
571 sa.rollback()
572
572
573 def splitter(full_repo_name):
573 def splitter(full_repo_name):
574 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
574 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
575 gr_name = None
575 gr_name = None
576 if len(_parts) == 2:
576 if len(_parts) == 2:
577 gr_name = _parts[0]
577 gr_name = _parts[0]
578 return gr_name
578 return gr_name
579
579
580 initial_repo_group_list = [splitter(x) for x in
580 initial_repo_group_list = [splitter(x) for x in
581 initial_repo_list.keys() if splitter(x)]
581 initial_repo_list.keys() if splitter(x)]
582
582
583 # remove from database those repository groups that are not in the
583 # remove from database those repository groups that are not in the
584 # filesystem due to parent child relationships we need to delete them
584 # filesystem due to parent child relationships we need to delete them
585 # in a specific order of most nested first
585 # in a specific order of most nested first
586 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
586 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
587 nested_sort = lambda gr: len(gr.split('/'))
587 nested_sort = lambda gr: len(gr.split('/'))
588 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
588 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
589 if group_name not in initial_repo_group_list:
589 if group_name not in initial_repo_group_list:
590 repo_group = RepoGroup.get_by_group_name(group_name)
590 repo_group = RepoGroup.get_by_group_name(group_name)
591 if (repo_group.children.all() or
591 if (repo_group.children.all() or
592 not RepoGroupModel().check_exist_filesystem(
592 not RepoGroupModel().check_exist_filesystem(
593 group_name=group_name, exc_on_failure=False)):
593 group_name=group_name, exc_on_failure=False)):
594 continue
594 continue
595
595
596 log.info(
596 log.info(
597 'Removing non-existing repository group found in db `%s`',
597 'Removing non-existing repository group found in db `%s`',
598 group_name)
598 group_name)
599 try:
599 try:
600 RepoGroupModel(sa).delete(group_name, fs_remove=False)
600 RepoGroupModel(sa).delete(group_name, fs_remove=False)
601 sa.commit()
601 sa.commit()
602 removed.append(group_name)
602 removed.append(group_name)
603 except Exception:
603 except Exception:
604 # don't hold further removals on error
604 # don't hold further removals on error
605 log.exception(
605 log.exception(
606 'Unable to remove repository group `%s`',
606 'Unable to remove repository group `%s`',
607 group_name)
607 group_name)
608 sa.rollback()
608 sa.rollback()
609 raise
609 raise
610
610
611 return added, removed
611 return added, removed
612
612
613
613
614 def get_default_cache_settings(settings):
614 def get_default_cache_settings(settings):
615 cache_settings = {}
615 cache_settings = {}
616 for key in settings.keys():
616 for key in settings.keys():
617 for prefix in ['beaker.cache.', 'cache.']:
617 for prefix in ['beaker.cache.', 'cache.']:
618 if key.startswith(prefix):
618 if key.startswith(prefix):
619 name = key.split(prefix)[1].strip()
619 name = key.split(prefix)[1].strip()
620 cache_settings[name] = settings[key].strip()
620 cache_settings[name] = settings[key].strip()
621 return cache_settings
621 return cache_settings
622
622
623
623
624 # set cache regions for beaker so celery can utilise it
624 # set cache regions for beaker so celery can utilise it
625 def add_cache(settings):
625 def add_cache(settings):
626 from rhodecode.lib import caches
626 from rhodecode.lib import caches
627 cache_settings = {'regions': None}
627 cache_settings = {'regions': None}
628 # main cache settings used as default ...
628 # main cache settings used as default ...
629 cache_settings.update(get_default_cache_settings(settings))
629 cache_settings.update(get_default_cache_settings(settings))
630
630
631 if cache_settings['regions']:
631 if cache_settings['regions']:
632 for region in cache_settings['regions'].split(','):
632 for region in cache_settings['regions'].split(','):
633 region = region.strip()
633 region = region.strip()
634 region_settings = {}
634 region_settings = {}
635 for key, value in cache_settings.items():
635 for key, value in cache_settings.items():
636 if key.startswith(region):
636 if key.startswith(region):
637 region_settings[key.split('.')[1]] = value
637 region_settings[key.split('.')[1]] = value
638
638
639 caches.configure_cache_region(
639 caches.configure_cache_region(
640 region, region_settings, cache_settings)
640 region, region_settings, cache_settings)
641
641
642
642
643 def load_rcextensions(root_path):
643 def load_rcextensions(root_path):
644 import rhodecode
644 import rhodecode
645 from rhodecode.config import conf
645 from rhodecode.config import conf
646
646
647 path = os.path.join(root_path, 'rcextensions', '__init__.py')
647 path = os.path.join(root_path, 'rcextensions', '__init__.py')
648 if os.path.isfile(path):
648 if os.path.isfile(path):
649 rcext = create_module('rc', path)
649 rcext = create_module('rc', path)
650 EXT = rhodecode.EXTENSIONS = rcext
650 EXT = rhodecode.EXTENSIONS = rcext
651 log.debug('Found rcextensions now loading %s...', rcext)
651 log.debug('Found rcextensions now loading %s...', rcext)
652
652
653 # Additional mappings that are not present in the pygments lexers
653 # Additional mappings that are not present in the pygments lexers
654 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
654 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
655
655
656 # auto check if the module is not missing any data, set to default if is
656 # auto check if the module is not missing any data, set to default if is
657 # this will help autoupdate new feature of rcext module
657 # this will help autoupdate new feature of rcext module
658 #from rhodecode.config import rcextensions
658 #from rhodecode.config import rcextensions
659 #for k in dir(rcextensions):
659 #for k in dir(rcextensions):
660 # if not k.startswith('_') and not hasattr(EXT, k):
660 # if not k.startswith('_') and not hasattr(EXT, k):
661 # setattr(EXT, k, getattr(rcextensions, k))
661 # setattr(EXT, k, getattr(rcextensions, k))
662
662
663
663
664 def get_custom_lexer(extension):
664 def get_custom_lexer(extension):
665 """
665 """
666 returns a custom lexer if it is defined in rcextensions module, or None
666 returns a custom lexer if it is defined in rcextensions module, or None
667 if there's no custom lexer defined
667 if there's no custom lexer defined
668 """
668 """
669 import rhodecode
669 import rhodecode
670 from pygments import lexers
670 from pygments import lexers
671
671
672 # custom override made by RhodeCode
672 # custom override made by RhodeCode
673 if extension in ['mako']:
673 if extension in ['mako']:
674 return lexers.get_lexer_by_name('html+mako')
674 return lexers.get_lexer_by_name('html+mako')
675
675
676 # check if we didn't define this extension as other lexer
676 # check if we didn't define this extension as other lexer
677 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
677 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
678 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
678 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
679 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
679 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
680 return lexers.get_lexer_by_name(_lexer_name)
680 return lexers.get_lexer_by_name(_lexer_name)
681
681
682
682
683 #==============================================================================
683 #==============================================================================
684 # TEST FUNCTIONS AND CREATORS
684 # TEST FUNCTIONS AND CREATORS
685 #==============================================================================
685 #==============================================================================
686 def create_test_index(repo_location, config):
686 def create_test_index(repo_location, config):
687 """
687 """
688 Makes default test index.
688 Makes default test index.
689 """
689 """
690 import rc_testdata
690 import rc_testdata
691
691
692 rc_testdata.extract_search_index(
692 rc_testdata.extract_search_index(
693 'vcs_search_index', os.path.dirname(config['search.location']))
693 'vcs_search_index', os.path.dirname(config['search.location']))
694
694
695
695
696 def create_test_directory(test_path):
696 def create_test_directory(test_path):
697 """
697 """
698 Create test directory if it doesn't exist.
698 Create test directory if it doesn't exist.
699 """
699 """
700 if not os.path.isdir(test_path):
700 if not os.path.isdir(test_path):
701 log.debug('Creating testdir %s', test_path)
701 log.debug('Creating testdir %s', test_path)
702 os.makedirs(test_path)
702 os.makedirs(test_path)
703
703
704
704
705 def create_test_database(test_path, config):
705 def create_test_database(test_path, config):
706 """
706 """
707 Makes a fresh database.
707 Makes a fresh database.
708 """
708 """
709 from rhodecode.lib.db_manage import DbManage
709 from rhodecode.lib.db_manage import DbManage
710
710
711 # PART ONE create db
711 # PART ONE create db
712 dbconf = config['sqlalchemy.db1.url']
712 dbconf = config['sqlalchemy.db1.url']
713 log.debug('making test db %s', dbconf)
713 log.debug('making test db %s', dbconf)
714
714
715 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
715 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
716 tests=True, cli_args={'force_ask': True})
716 tests=True, cli_args={'force_ask': True})
717 dbmanage.create_tables(override=True)
717 dbmanage.create_tables(override=True)
718 dbmanage.set_db_version()
718 dbmanage.set_db_version()
719 # for tests dynamically set new root paths based on generated content
719 # for tests dynamically set new root paths based on generated content
720 dbmanage.create_settings(dbmanage.config_prompt(test_path))
720 dbmanage.create_settings(dbmanage.config_prompt(test_path))
721 dbmanage.create_default_user()
721 dbmanage.create_default_user()
722 dbmanage.create_test_admin_and_users()
722 dbmanage.create_test_admin_and_users()
723 dbmanage.create_permissions()
723 dbmanage.create_permissions()
724 dbmanage.populate_default_permissions()
724 dbmanage.populate_default_permissions()
725 Session().commit()
725 Session().commit()
726
726
727
727
728 def create_test_repositories(test_path, config):
728 def create_test_repositories(test_path, config):
729 """
729 """
730 Creates test repositories in the temporary directory. Repositories are
730 Creates test repositories in the temporary directory. Repositories are
731 extracted from archives within the rc_testdata package.
731 extracted from archives within the rc_testdata package.
732 """
732 """
733 import rc_testdata
733 import rc_testdata
734 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
734 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
735
735
736 log.debug('making test vcs repositories')
736 log.debug('making test vcs repositories')
737
737
738 idx_path = config['search.location']
738 idx_path = config['search.location']
739 data_path = config['cache_dir']
739 data_path = config['cache_dir']
740
740
741 # clean index and data
741 # clean index and data
742 if idx_path and os.path.exists(idx_path):
742 if idx_path and os.path.exists(idx_path):
743 log.debug('remove %s', idx_path)
743 log.debug('remove %s', idx_path)
744 shutil.rmtree(idx_path)
744 shutil.rmtree(idx_path)
745
745
746 if data_path and os.path.exists(data_path):
746 if data_path and os.path.exists(data_path):
747 log.debug('remove %s', data_path)
747 log.debug('remove %s', data_path)
748 shutil.rmtree(data_path)
748 shutil.rmtree(data_path)
749
749
750 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
750 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
751 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
751 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
752
752
753 # Note: Subversion is in the process of being integrated with the system,
753 # Note: Subversion is in the process of being integrated with the system,
754 # until we have a properly packed version of the test svn repository, this
754 # until we have a properly packed version of the test svn repository, this
755 # tries to copy over the repo from a package "rc_testdata"
755 # tries to copy over the repo from a package "rc_testdata"
756 svn_repo_path = rc_testdata.get_svn_repo_archive()
756 svn_repo_path = rc_testdata.get_svn_repo_archive()
757 with tarfile.open(svn_repo_path) as tar:
757 with tarfile.open(svn_repo_path) as tar:
758 tar.extractall(jn(test_path, SVN_REPO))
758 tar.extractall(jn(test_path, SVN_REPO))
759
759
760
760
761 #==============================================================================
761 #==============================================================================
762 # PASTER COMMANDS
762 # PASTER COMMANDS
763 #==============================================================================
763 #==============================================================================
764 class BasePasterCommand(Command):
764 class BasePasterCommand(Command):
765 """
765 """
766 Abstract Base Class for paster commands.
766 Abstract Base Class for paster commands.
767
767
768 The celery commands are somewhat aggressive about loading
768 The celery commands are somewhat aggressive about loading
769 celery.conf, and since our module sets the `CELERY_LOADER`
769 celery.conf, and since our module sets the `CELERY_LOADER`
770 environment variable to our loader, we have to bootstrap a bit and
770 environment variable to our loader, we have to bootstrap a bit and
771 make sure we've had a chance to load the pylons config off of the
771 make sure we've had a chance to load the pylons config off of the
772 command line, otherwise everything fails.
772 command line, otherwise everything fails.
773 """
773 """
774 min_args = 1
774 min_args = 1
775 min_args_error = "Please provide a paster config file as an argument."
775 min_args_error = "Please provide a paster config file as an argument."
776 takes_config_file = 1
776 takes_config_file = 1
777 requires_config_file = True
777 requires_config_file = True
778
778
779 def notify_msg(self, msg, log=False):
779 def notify_msg(self, msg, log=False):
780 """Make a notification to user, additionally if logger is passed
780 """Make a notification to user, additionally if logger is passed
781 it logs this action using given logger
781 it logs this action using given logger
782
782
783 :param msg: message that will be printed to user
783 :param msg: message that will be printed to user
784 :param log: logging instance, to use to additionally log this message
784 :param log: logging instance, to use to additionally log this message
785
785
786 """
786 """
787 if log and isinstance(log, logging):
787 if log and isinstance(log, logging):
788 log(msg)
788 log(msg)
789
789
790 def run(self, args):
790 def run(self, args):
791 """
791 """
792 Overrides Command.run
792 Overrides Command.run
793
793
794 Checks for a config file argument and loads it.
794 Checks for a config file argument and loads it.
795 """
795 """
796 if len(args) < self.min_args:
796 if len(args) < self.min_args:
797 raise BadCommand(
797 raise BadCommand(
798 self.min_args_error % {'min_args': self.min_args,
798 self.min_args_error % {'min_args': self.min_args,
799 'actual_args': len(args)})
799 'actual_args': len(args)})
800
800
801 # Decrement because we're going to lob off the first argument.
801 # Decrement because we're going to lob off the first argument.
802 # @@ This is hacky
802 # @@ This is hacky
803 self.min_args -= 1
803 self.min_args -= 1
804 self.bootstrap_config(args[0])
804 self.bootstrap_config(args[0])
805 self.update_parser()
805 self.update_parser()
806 return super(BasePasterCommand, self).run(args[1:])
806 return super(BasePasterCommand, self).run(args[1:])
807
807
808 def update_parser(self):
808 def update_parser(self):
809 """
809 """
810 Abstract method. Allows for the class' parser to be updated
810 Abstract method. Allows for the class' parser to be updated
811 before the superclass' `run` method is called. Necessary to
811 before the superclass' `run` method is called. Necessary to
812 allow options/arguments to be passed through to the underlying
812 allow options/arguments to be passed through to the underlying
813 celery command.
813 celery command.
814 """
814 """
815 raise NotImplementedError("Abstract Method.")
815 raise NotImplementedError("Abstract Method.")
816
816
817 def bootstrap_config(self, conf):
817 def bootstrap_config(self, conf):
818 """
818 """
819 Loads the pylons configuration.
819 Loads the pylons configuration.
820 """
820 """
821 from pylons import config as pylonsconfig
821 from pylons import config as pylonsconfig
822
822
823 self.path_to_ini_file = os.path.realpath(conf)
823 self.path_to_ini_file = os.path.realpath(conf)
824 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
824 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
825 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
825 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
826
826
827 def _init_session(self):
827 def _init_session(self):
828 """
828 """
829 Inits SqlAlchemy Session
829 Inits SqlAlchemy Session
830 """
830 """
831 logging.config.fileConfig(self.path_to_ini_file)
831 logging.config.fileConfig(self.path_to_ini_file)
832 from pylons import config
832 from pylons import config
833 from rhodecode.config.utils import initialize_database
833 from rhodecode.config.utils import initialize_database
834
834
835 # get to remove repos !!
835 # get to remove repos !!
836 add_cache(config)
836 add_cache(config)
837 initialize_database(config)
837 initialize_database(config)
838
838
839
839
840 @decorator.decorator
840 @decorator.decorator
841 def jsonify(func, *args, **kwargs):
841 def jsonify(func, *args, **kwargs):
842 """Action decorator that formats output for JSON
842 """Action decorator that formats output for JSON
843
843
844 Given a function that will return content, this decorator will turn
844 Given a function that will return content, this decorator will turn
845 the result into JSON, with a content-type of 'application/json' and
845 the result into JSON, with a content-type of 'application/json' and
846 output it.
846 output it.
847
847
848 """
848 """
849 from pylons.decorators.util import get_pylons
849 from pylons.decorators.util import get_pylons
850 from rhodecode.lib.ext_json import json
850 from rhodecode.lib.ext_json import json
851 pylons = get_pylons(args)
851 pylons = get_pylons(args)
852 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
852 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
853 data = func(*args, **kwargs)
853 data = func(*args, **kwargs)
854 if isinstance(data, (list, tuple)):
854 if isinstance(data, (list, tuple)):
855 msg = "JSON responses with Array envelopes are susceptible to " \
855 msg = "JSON responses with Array envelopes are susceptible to " \
856 "cross-site data leak attacks, see " \
856 "cross-site data leak attacks, see " \
857 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
857 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
858 warnings.warn(msg, Warning, 2)
858 warnings.warn(msg, Warning, 2)
859 log.warning(msg)
859 log.warning(msg)
860 log.debug("Returning JSON wrapped action output")
860 log.debug("Returning JSON wrapped action output")
861 return json.dumps(data, encoding='utf-8')
861 return json.dumps(data, encoding='utf-8')
862
862
863
863
864 class PartialRenderer(object):
864 class PartialRenderer(object):
865 """
865 """
866 Partial renderer used to render chunks of html used in datagrids
866 Partial renderer used to render chunks of html used in datagrids
867 use like::
867 use like::
868
868
869 _render = PartialRenderer('data_table/_dt_elements.mako')
869 _render = PartialRenderer('data_table/_dt_elements.mako')
870 _render('quick_menu', args, kwargs)
870 _render('quick_menu', args, kwargs)
871 PartialRenderer.h,
871 PartialRenderer.h,
872 c,
872 c,
873 _,
873 _,
874 ungettext
874 ungettext
875 are the template stuff initialized inside and can be re-used later
875 are the template stuff initialized inside and can be re-used later
876
876
877 :param tmpl_name: template path relate to /templates/ dir
877 :param tmpl_name: template path relate to /templates/ dir
878 """
878 """
879
879
880 def __init__(self, tmpl_name):
880 def __init__(self, tmpl_name):
881 import rhodecode
881 import rhodecode
882 from pylons import request, tmpl_context as c
882 from pylons import request, tmpl_context as c
883 from pylons.i18n.translation import _, ungettext
883 from pylons.i18n.translation import _, ungettext
884 from rhodecode.lib import helpers as h
884 from rhodecode.lib import helpers as h
885
885
886 self.tmpl_name = tmpl_name
886 self.tmpl_name = tmpl_name
887 self.rhodecode = rhodecode
887 self.rhodecode = rhodecode
888 self.c = c
888 self.c = c
889 self._ = _
889 self._ = _
890 self.ungettext = ungettext
890 self.ungettext = ungettext
891 self.h = h
891 self.h = h
892 self.request = request
892 self.request = request
893
893
894 def _mako_lookup(self):
894 def _mako_lookup(self):
895 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
895 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
896 return _tmpl_lookup.get_template(self.tmpl_name)
896 return _tmpl_lookup.get_template(self.tmpl_name)
897
897
898 def _update_kwargs_for_render(self, kwargs):
898 def _update_kwargs_for_render(self, kwargs):
899 """
899 """
900 Inject params required for Mako rendering
900 Inject params required for Mako rendering
901 """
901 """
902 _kwargs = {
902 _kwargs = {
903 '_': self._,
903 '_': self._,
904 'h': self.h,
904 'h': self.h,
905 'c': self.c,
905 'c': self.c,
906 'request': self.request,
906 'request': self.request,
907 '_ungettext': self.ungettext,
907 '_ungettext': self.ungettext,
908 }
908 }
909 _kwargs.update(kwargs)
909 _kwargs.update(kwargs)
910 return _kwargs
910 return _kwargs
911
911
912 def _render_with_exc(self, render_func, args, kwargs):
912 def _render_with_exc(self, render_func, args, kwargs):
913 try:
913 try:
914 return render_func.render(*args, **kwargs)
914 return render_func.render(*args, **kwargs)
915 except:
915 except:
916 log.error(exceptions.text_error_template().render())
916 log.error(exceptions.text_error_template().render())
917 raise
917 raise
918
918
919 def _get_template(self, template_obj, def_name):
919 def _get_template(self, template_obj, def_name):
920 if def_name:
920 if def_name:
921 tmpl = template_obj.get_def(def_name)
921 tmpl = template_obj.get_def(def_name)
922 else:
922 else:
923 tmpl = template_obj
923 tmpl = template_obj
924 return tmpl
924 return tmpl
925
925
926 def render(self, def_name, *args, **kwargs):
926 def render(self, def_name, *args, **kwargs):
927 lookup_obj = self._mako_lookup()
927 lookup_obj = self._mako_lookup()
928 tmpl = self._get_template(lookup_obj, def_name=def_name)
928 tmpl = self._get_template(lookup_obj, def_name=def_name)
929 kwargs = self._update_kwargs_for_render(kwargs)
929 kwargs = self._update_kwargs_for_render(kwargs)
930 return self._render_with_exc(tmpl, args, kwargs)
930 return self._render_with_exc(tmpl, args, kwargs)
931
931
932 def __call__(self, tmpl, *args, **kwargs):
932 def __call__(self, tmpl, *args, **kwargs):
933 return self.render(tmpl, *args, **kwargs)
933 return self.render(tmpl, *args, **kwargs)
934
934
935
935
936 def password_changed(auth_user, session):
936 def password_changed(auth_user, session):
937 # Never report password change in case of default user or anonymous user.
937 # Never report password change in case of default user or anonymous user.
938 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
938 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
939 return False
939 return False
940
940
941 password_hash = md5(auth_user.password) if auth_user.password else None
941 password_hash = md5(auth_user.password) if auth_user.password else None
942 rhodecode_user = session.get('rhodecode_user', {})
942 rhodecode_user = session.get('rhodecode_user', {})
943 session_password_hash = rhodecode_user.get('password', '')
943 session_password_hash = rhodecode_user.get('password', '')
944 return password_hash != session_password_hash
944 return password_hash != session_password_hash
945
945
946
946
947 def read_opensource_licenses():
947 def read_opensource_licenses():
948 global _license_cache
948 global _license_cache
949
949
950 if not _license_cache:
950 if not _license_cache:
951 licenses = pkg_resources.resource_string(
951 licenses = pkg_resources.resource_string(
952 'rhodecode', 'config/licenses.json')
952 'rhodecode', 'config/licenses.json')
953 _license_cache = json.loads(licenses)
953 _license_cache = json.loads(licenses)
954
954
955 return _license_cache
955 return _license_cache
956
956
957
957
958 def get_registry(request):
958 def get_registry(request):
959 """
959 """
960 Utility to get the pyramid registry from a request. During migration to
960 Utility to get the pyramid registry from a request. During migration to
961 pyramid we sometimes want to use the pyramid registry from pylons context.
961 pyramid we sometimes want to use the pyramid registry from pylons context.
962 Therefore this utility returns `request.registry` for pyramid requests and
962 Therefore this utility returns `request.registry` for pyramid requests and
963 uses `get_current_registry()` for pylons requests.
963 uses `get_current_registry()` for pylons requests.
964 """
964 """
965 try:
965 try:
966 return request.registry
966 return request.registry
967 except AttributeError:
967 except AttributeError:
968 return get_current_registry()
968 return get_current_registry()
969
969
970
970
971 def generate_platform_uuid():
971 def generate_platform_uuid():
972 """
972 """
973 Generates platform UUID based on it's name
973 Generates platform UUID based on it's name
974 """
974 """
975 import platform
975 import platform
976
976
977 try:
977 try:
978 uuid_list = [platform.platform()]
978 uuid_list = [platform.platform()]
979 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
979 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
980 except Exception as e:
980 except Exception as e:
981 log.error('Failed to generate host uuid: %s' % e)
981 log.error('Failed to generate host uuid: %s' % e)
982 return 'UNDEFINED'
982 return 'UNDEFINED'
@@ -1,4119 +1,4128 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from beaker.cache import cache_region
45 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46
47
47 from pyramid.threadlocal import get_current_request
48 from pyramid.threadlocal import get_current_request
48
49
49 from rhodecode.translation import _
50 from rhodecode.translation import _
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
60
61
61 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
62
63
63 URL_SEP = '/'
64 URL_SEP = '/'
64 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
65
66
66 # =============================================================================
67 # =============================================================================
67 # BASE CLASSES
68 # BASE CLASSES
68 # =============================================================================
69 # =============================================================================
69
70
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
73 # and initialized at environment.py
73 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
74
75
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
78 'admin': '####',
79 'admin': '####',
79 'write': '###',
80 'write': '###',
80 'read': '##',
81 'read': '##',
81 'none': '#',
82 'none': '#',
82 }
83 }
83
84
84
85
85 def display_sort(obj):
86 def display_sort(obj):
86 """
87 """
87 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
90 of all other resources
90 """
91 """
91
92
92 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
93 return '#####'
94 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
96 return prefix + obj.username
96
97
97
98
98 def _hash_key(k):
99 def _hash_key(k):
99 return md5_safe(k)
100 return md5_safe(k)
100
101
101
102
102 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
103 """
104 """
104 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
105
106
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
108
108 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
110 """
111 """
111 impl = Text
112 impl = Text
112
113
113 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
114 if not value:
115 if not value:
115 return value
116 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
118 # doing
119 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
121 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
124
124 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
125 import rhodecode
126 import rhodecode
126
127
127 if not value:
128 if not value:
128 return value
129 return value
129
130
130 parts = value.split('$', 3)
131 parts = value.split('$', 3)
131 if not len(parts) == 3:
132 if not len(parts) == 3:
132 # probably not encrypted values
133 # probably not encrypted values
133 return value
134 return value
134 else:
135 else:
135 if parts[0] != 'enc':
136 if parts[0] != 'enc':
136 # parts ok but without our header ?
137 # parts ok but without our header ?
137 return value
138 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
142 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
148 else:
148 raise ValueError(
149 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
152 return decrypted_data
152
153
153
154
154 class BaseModel(object):
155 class BaseModel(object):
155 """
156 """
156 Base Model for all classes
157 Base Model for all classes
157 """
158 """
158
159
159 @classmethod
160 @classmethod
160 def _get_keys(cls):
161 def _get_keys(cls):
161 """return column names for this model """
162 """return column names for this model """
162 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
163
164
164 def get_dict(self):
165 def get_dict(self):
165 """
166 """
166 return dict with keys and values corresponding
167 return dict with keys and values corresponding
167 to this model data """
168 to this model data """
168
169
169 d = {}
170 d = {}
170 for k in self._get_keys():
171 for k in self._get_keys():
171 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
172
173
173 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
176 if _json_attr:
176 # update with attributes from __json__
177 # update with attributes from __json__
177 if callable(_json_attr):
178 if callable(_json_attr):
178 _json_attr = _json_attr()
179 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
180 d[k] = val
181 d[k] = val
181 return d
182 return d
182
183
183 def get_appstruct(self):
184 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
185 to this model data """
186 to this model data """
186
187
187 l = []
188 l = []
188 for k in self._get_keys():
189 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
190 return l
191 return l
191
192
192 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
194
195
195 for k in self._get_keys():
196 for k in self._get_keys():
196 if k in populate_dict:
197 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
198
199
199 @classmethod
200 @classmethod
200 def query(cls):
201 def query(cls):
201 return Session().query(cls)
202 return Session().query(cls)
202
203
203 @classmethod
204 @classmethod
204 def get(cls, id_):
205 def get(cls, id_):
205 if id_:
206 if id_:
206 return cls.query().get(id_)
207 return cls.query().get(id_)
207
208
208 @classmethod
209 @classmethod
209 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
210 from pyramid.httpexceptions import HTTPNotFound
211 from pyramid.httpexceptions import HTTPNotFound
211
212
212 try:
213 try:
213 id_ = int(id_)
214 id_ = int(id_)
214 except (TypeError, ValueError):
215 except (TypeError, ValueError):
215 raise HTTPNotFound()
216 raise HTTPNotFound()
216
217
217 res = cls.query().get(id_)
218 res = cls.query().get(id_)
218 if not res:
219 if not res:
219 raise HTTPNotFound()
220 raise HTTPNotFound()
220 return res
221 return res
221
222
222 @classmethod
223 @classmethod
223 def getAll(cls):
224 def getAll(cls):
224 # deprecated and left for backward compatibility
225 # deprecated and left for backward compatibility
225 return cls.get_all()
226 return cls.get_all()
226
227
227 @classmethod
228 @classmethod
228 def get_all(cls):
229 def get_all(cls):
229 return cls.query().all()
230 return cls.query().all()
230
231
231 @classmethod
232 @classmethod
232 def delete(cls, id_):
233 def delete(cls, id_):
233 obj = cls.query().get(id_)
234 obj = cls.query().get(id_)
234 Session().delete(obj)
235 Session().delete(obj)
235
236
236 @classmethod
237 @classmethod
237 def identity_cache(cls, session, attr_name, value):
238 def identity_cache(cls, session, attr_name, value):
238 exist_in_session = []
239 exist_in_session = []
239 for (item_cls, pkey), instance in session.identity_map.items():
240 for (item_cls, pkey), instance in session.identity_map.items():
240 if cls == item_cls and getattr(instance, attr_name) == value:
241 if cls == item_cls and getattr(instance, attr_name) == value:
241 exist_in_session.append(instance)
242 exist_in_session.append(instance)
242 if exist_in_session:
243 if exist_in_session:
243 if len(exist_in_session) == 1:
244 if len(exist_in_session) == 1:
244 return exist_in_session[0]
245 return exist_in_session[0]
245 log.exception(
246 log.exception(
246 'multiple objects with attr %s and '
247 'multiple objects with attr %s and '
247 'value %s found with same name: %r',
248 'value %s found with same name: %r',
248 attr_name, value, exist_in_session)
249 attr_name, value, exist_in_session)
249
250
250 def __repr__(self):
251 def __repr__(self):
251 if hasattr(self, '__unicode__'):
252 if hasattr(self, '__unicode__'):
252 # python repr needs to return str
253 # python repr needs to return str
253 try:
254 try:
254 return safe_str(self.__unicode__())
255 return safe_str(self.__unicode__())
255 except UnicodeDecodeError:
256 except UnicodeDecodeError:
256 pass
257 pass
257 return '<DB:%s>' % (self.__class__.__name__)
258 return '<DB:%s>' % (self.__class__.__name__)
258
259
259
260
260 class RhodeCodeSetting(Base, BaseModel):
261 class RhodeCodeSetting(Base, BaseModel):
261 __tablename__ = 'rhodecode_settings'
262 __tablename__ = 'rhodecode_settings'
262 __table_args__ = (
263 __table_args__ = (
263 UniqueConstraint('app_settings_name'),
264 UniqueConstraint('app_settings_name'),
264 {'extend_existing': True, 'mysql_engine': 'InnoDB',
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
265 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
266 )
267 )
267
268
268 SETTINGS_TYPES = {
269 SETTINGS_TYPES = {
269 'str': safe_str,
270 'str': safe_str,
270 'int': safe_int,
271 'int': safe_int,
271 'unicode': safe_unicode,
272 'unicode': safe_unicode,
272 'bool': str2bool,
273 'bool': str2bool,
273 'list': functools.partial(aslist, sep=',')
274 'list': functools.partial(aslist, sep=',')
274 }
275 }
275 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
276 GLOBAL_CONF_KEY = 'app_settings'
277 GLOBAL_CONF_KEY = 'app_settings'
277
278
278 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
280 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
281 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
282
283
283 def __init__(self, key='', val='', type='unicode'):
284 def __init__(self, key='', val='', type='unicode'):
284 self.app_settings_name = key
285 self.app_settings_name = key
285 self.app_settings_type = type
286 self.app_settings_type = type
286 self.app_settings_value = val
287 self.app_settings_value = val
287
288
288 @validates('_app_settings_value')
289 @validates('_app_settings_value')
289 def validate_settings_value(self, key, val):
290 def validate_settings_value(self, key, val):
290 assert type(val) == unicode
291 assert type(val) == unicode
291 return val
292 return val
292
293
293 @hybrid_property
294 @hybrid_property
294 def app_settings_value(self):
295 def app_settings_value(self):
295 v = self._app_settings_value
296 v = self._app_settings_value
296 _type = self.app_settings_type
297 _type = self.app_settings_type
297 if _type:
298 if _type:
298 _type = self.app_settings_type.split('.')[0]
299 _type = self.app_settings_type.split('.')[0]
299 # decode the encrypted value
300 # decode the encrypted value
300 if 'encrypted' in self.app_settings_type:
301 if 'encrypted' in self.app_settings_type:
301 cipher = EncryptedTextValue()
302 cipher = EncryptedTextValue()
302 v = safe_unicode(cipher.process_result_value(v, None))
303 v = safe_unicode(cipher.process_result_value(v, None))
303
304
304 converter = self.SETTINGS_TYPES.get(_type) or \
305 converter = self.SETTINGS_TYPES.get(_type) or \
305 self.SETTINGS_TYPES['unicode']
306 self.SETTINGS_TYPES['unicode']
306 return converter(v)
307 return converter(v)
307
308
308 @app_settings_value.setter
309 @app_settings_value.setter
309 def app_settings_value(self, val):
310 def app_settings_value(self, val):
310 """
311 """
311 Setter that will always make sure we use unicode in app_settings_value
312 Setter that will always make sure we use unicode in app_settings_value
312
313
313 :param val:
314 :param val:
314 """
315 """
315 val = safe_unicode(val)
316 val = safe_unicode(val)
316 # encode the encrypted value
317 # encode the encrypted value
317 if 'encrypted' in self.app_settings_type:
318 if 'encrypted' in self.app_settings_type:
318 cipher = EncryptedTextValue()
319 cipher = EncryptedTextValue()
319 val = safe_unicode(cipher.process_bind_param(val, None))
320 val = safe_unicode(cipher.process_bind_param(val, None))
320 self._app_settings_value = val
321 self._app_settings_value = val
321
322
322 @hybrid_property
323 @hybrid_property
323 def app_settings_type(self):
324 def app_settings_type(self):
324 return self._app_settings_type
325 return self._app_settings_type
325
326
326 @app_settings_type.setter
327 @app_settings_type.setter
327 def app_settings_type(self, val):
328 def app_settings_type(self, val):
328 if val.split('.')[0] not in self.SETTINGS_TYPES:
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
329 raise Exception('type must be one of %s got %s'
330 raise Exception('type must be one of %s got %s'
330 % (self.SETTINGS_TYPES.keys(), val))
331 % (self.SETTINGS_TYPES.keys(), val))
331 self._app_settings_type = val
332 self._app_settings_type = val
332
333
333 def __unicode__(self):
334 def __unicode__(self):
334 return u"<%s('%s:%s[%s]')>" % (
335 return u"<%s('%s:%s[%s]')>" % (
335 self.__class__.__name__,
336 self.__class__.__name__,
336 self.app_settings_name, self.app_settings_value,
337 self.app_settings_name, self.app_settings_value,
337 self.app_settings_type
338 self.app_settings_type
338 )
339 )
339
340
340
341
341 class RhodeCodeUi(Base, BaseModel):
342 class RhodeCodeUi(Base, BaseModel):
342 __tablename__ = 'rhodecode_ui'
343 __tablename__ = 'rhodecode_ui'
343 __table_args__ = (
344 __table_args__ = (
344 UniqueConstraint('ui_key'),
345 UniqueConstraint('ui_key'),
345 {'extend_existing': True, 'mysql_engine': 'InnoDB',
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
346 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
347 )
348 )
348
349
349 HOOK_REPO_SIZE = 'changegroup.repo_size'
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
350 # HG
351 # HG
351 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
352 HOOK_PULL = 'outgoing.pull_logger'
353 HOOK_PULL = 'outgoing.pull_logger'
353 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
354 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
355 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
355 HOOK_PUSH = 'changegroup.push_logger'
356 HOOK_PUSH = 'changegroup.push_logger'
356 HOOK_PUSH_KEY = 'pushkey.key_push'
357 HOOK_PUSH_KEY = 'pushkey.key_push'
357
358
358 # TODO: johbo: Unify way how hooks are configured for git and hg,
359 # TODO: johbo: Unify way how hooks are configured for git and hg,
359 # git part is currently hardcoded.
360 # git part is currently hardcoded.
360
361
361 # SVN PATTERNS
362 # SVN PATTERNS
362 SVN_BRANCH_ID = 'vcs_svn_branch'
363 SVN_BRANCH_ID = 'vcs_svn_branch'
363 SVN_TAG_ID = 'vcs_svn_tag'
364 SVN_TAG_ID = 'vcs_svn_tag'
364
365
365 ui_id = Column(
366 ui_id = Column(
366 "ui_id", Integer(), nullable=False, unique=True, default=None,
367 "ui_id", Integer(), nullable=False, unique=True, default=None,
367 primary_key=True)
368 primary_key=True)
368 ui_section = Column(
369 ui_section = Column(
369 "ui_section", String(255), nullable=True, unique=None, default=None)
370 "ui_section", String(255), nullable=True, unique=None, default=None)
370 ui_key = Column(
371 ui_key = Column(
371 "ui_key", String(255), nullable=True, unique=None, default=None)
372 "ui_key", String(255), nullable=True, unique=None, default=None)
372 ui_value = Column(
373 ui_value = Column(
373 "ui_value", String(255), nullable=True, unique=None, default=None)
374 "ui_value", String(255), nullable=True, unique=None, default=None)
374 ui_active = Column(
375 ui_active = Column(
375 "ui_active", Boolean(), nullable=True, unique=None, default=True)
376 "ui_active", Boolean(), nullable=True, unique=None, default=True)
376
377
377 def __repr__(self):
378 def __repr__(self):
378 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
379 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
379 self.ui_key, self.ui_value)
380 self.ui_key, self.ui_value)
380
381
381
382
382 class RepoRhodeCodeSetting(Base, BaseModel):
383 class RepoRhodeCodeSetting(Base, BaseModel):
383 __tablename__ = 'repo_rhodecode_settings'
384 __tablename__ = 'repo_rhodecode_settings'
384 __table_args__ = (
385 __table_args__ = (
385 UniqueConstraint(
386 UniqueConstraint(
386 'app_settings_name', 'repository_id',
387 'app_settings_name', 'repository_id',
387 name='uq_repo_rhodecode_setting_name_repo_id'),
388 name='uq_repo_rhodecode_setting_name_repo_id'),
388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
389 {'extend_existing': True, 'mysql_engine': 'InnoDB',
389 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
390 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
390 )
391 )
391
392
392 repository_id = Column(
393 repository_id = Column(
393 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
394 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
394 nullable=False)
395 nullable=False)
395 app_settings_id = Column(
396 app_settings_id = Column(
396 "app_settings_id", Integer(), nullable=False, unique=True,
397 "app_settings_id", Integer(), nullable=False, unique=True,
397 default=None, primary_key=True)
398 default=None, primary_key=True)
398 app_settings_name = Column(
399 app_settings_name = Column(
399 "app_settings_name", String(255), nullable=True, unique=None,
400 "app_settings_name", String(255), nullable=True, unique=None,
400 default=None)
401 default=None)
401 _app_settings_value = Column(
402 _app_settings_value = Column(
402 "app_settings_value", String(4096), nullable=True, unique=None,
403 "app_settings_value", String(4096), nullable=True, unique=None,
403 default=None)
404 default=None)
404 _app_settings_type = Column(
405 _app_settings_type = Column(
405 "app_settings_type", String(255), nullable=True, unique=None,
406 "app_settings_type", String(255), nullable=True, unique=None,
406 default=None)
407 default=None)
407
408
408 repository = relationship('Repository')
409 repository = relationship('Repository')
409
410
410 def __init__(self, repository_id, key='', val='', type='unicode'):
411 def __init__(self, repository_id, key='', val='', type='unicode'):
411 self.repository_id = repository_id
412 self.repository_id = repository_id
412 self.app_settings_name = key
413 self.app_settings_name = key
413 self.app_settings_type = type
414 self.app_settings_type = type
414 self.app_settings_value = val
415 self.app_settings_value = val
415
416
416 @validates('_app_settings_value')
417 @validates('_app_settings_value')
417 def validate_settings_value(self, key, val):
418 def validate_settings_value(self, key, val):
418 assert type(val) == unicode
419 assert type(val) == unicode
419 return val
420 return val
420
421
421 @hybrid_property
422 @hybrid_property
422 def app_settings_value(self):
423 def app_settings_value(self):
423 v = self._app_settings_value
424 v = self._app_settings_value
424 type_ = self.app_settings_type
425 type_ = self.app_settings_type
425 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
426 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
426 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
427 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
427 return converter(v)
428 return converter(v)
428
429
429 @app_settings_value.setter
430 @app_settings_value.setter
430 def app_settings_value(self, val):
431 def app_settings_value(self, val):
431 """
432 """
432 Setter that will always make sure we use unicode in app_settings_value
433 Setter that will always make sure we use unicode in app_settings_value
433
434
434 :param val:
435 :param val:
435 """
436 """
436 self._app_settings_value = safe_unicode(val)
437 self._app_settings_value = safe_unicode(val)
437
438
438 @hybrid_property
439 @hybrid_property
439 def app_settings_type(self):
440 def app_settings_type(self):
440 return self._app_settings_type
441 return self._app_settings_type
441
442
442 @app_settings_type.setter
443 @app_settings_type.setter
443 def app_settings_type(self, val):
444 def app_settings_type(self, val):
444 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
445 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
445 if val not in SETTINGS_TYPES:
446 if val not in SETTINGS_TYPES:
446 raise Exception('type must be one of %s got %s'
447 raise Exception('type must be one of %s got %s'
447 % (SETTINGS_TYPES.keys(), val))
448 % (SETTINGS_TYPES.keys(), val))
448 self._app_settings_type = val
449 self._app_settings_type = val
449
450
450 def __unicode__(self):
451 def __unicode__(self):
451 return u"<%s('%s:%s:%s[%s]')>" % (
452 return u"<%s('%s:%s:%s[%s]')>" % (
452 self.__class__.__name__, self.repository.repo_name,
453 self.__class__.__name__, self.repository.repo_name,
453 self.app_settings_name, self.app_settings_value,
454 self.app_settings_name, self.app_settings_value,
454 self.app_settings_type
455 self.app_settings_type
455 )
456 )
456
457
457
458
458 class RepoRhodeCodeUi(Base, BaseModel):
459 class RepoRhodeCodeUi(Base, BaseModel):
459 __tablename__ = 'repo_rhodecode_ui'
460 __tablename__ = 'repo_rhodecode_ui'
460 __table_args__ = (
461 __table_args__ = (
461 UniqueConstraint(
462 UniqueConstraint(
462 'repository_id', 'ui_section', 'ui_key',
463 'repository_id', 'ui_section', 'ui_key',
463 name='uq_repo_rhodecode_ui_repository_id_section_key'),
464 name='uq_repo_rhodecode_ui_repository_id_section_key'),
464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
466 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
466 )
467 )
467
468
468 repository_id = Column(
469 repository_id = Column(
469 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
470 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
470 nullable=False)
471 nullable=False)
471 ui_id = Column(
472 ui_id = Column(
472 "ui_id", Integer(), nullable=False, unique=True, default=None,
473 "ui_id", Integer(), nullable=False, unique=True, default=None,
473 primary_key=True)
474 primary_key=True)
474 ui_section = Column(
475 ui_section = Column(
475 "ui_section", String(255), nullable=True, unique=None, default=None)
476 "ui_section", String(255), nullable=True, unique=None, default=None)
476 ui_key = Column(
477 ui_key = Column(
477 "ui_key", String(255), nullable=True, unique=None, default=None)
478 "ui_key", String(255), nullable=True, unique=None, default=None)
478 ui_value = Column(
479 ui_value = Column(
479 "ui_value", String(255), nullable=True, unique=None, default=None)
480 "ui_value", String(255), nullable=True, unique=None, default=None)
480 ui_active = Column(
481 ui_active = Column(
481 "ui_active", Boolean(), nullable=True, unique=None, default=True)
482 "ui_active", Boolean(), nullable=True, unique=None, default=True)
482
483
483 repository = relationship('Repository')
484 repository = relationship('Repository')
484
485
485 def __repr__(self):
486 def __repr__(self):
486 return '<%s[%s:%s]%s=>%s]>' % (
487 return '<%s[%s:%s]%s=>%s]>' % (
487 self.__class__.__name__, self.repository.repo_name,
488 self.__class__.__name__, self.repository.repo_name,
488 self.ui_section, self.ui_key, self.ui_value)
489 self.ui_section, self.ui_key, self.ui_value)
489
490
490
491
491 class User(Base, BaseModel):
492 class User(Base, BaseModel):
492 __tablename__ = 'users'
493 __tablename__ = 'users'
493 __table_args__ = (
494 __table_args__ = (
494 UniqueConstraint('username'), UniqueConstraint('email'),
495 UniqueConstraint('username'), UniqueConstraint('email'),
495 Index('u_username_idx', 'username'),
496 Index('u_username_idx', 'username'),
496 Index('u_email_idx', 'email'),
497 Index('u_email_idx', 'email'),
497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
499 )
500 )
500 DEFAULT_USER = 'default'
501 DEFAULT_USER = 'default'
501 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
502 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
502 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
503 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
503
504
504 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
505 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
505 username = Column("username", String(255), nullable=True, unique=None, default=None)
506 username = Column("username", String(255), nullable=True, unique=None, default=None)
506 password = Column("password", String(255), nullable=True, unique=None, default=None)
507 password = Column("password", String(255), nullable=True, unique=None, default=None)
507 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
508 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
508 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
509 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
509 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
510 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
510 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
511 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
511 _email = Column("email", String(255), nullable=True, unique=None, default=None)
512 _email = Column("email", String(255), nullable=True, unique=None, default=None)
512 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
513 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
513 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
514 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
514
515
515 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
516 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
516 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
517 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
517 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
518 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
518 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
519 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
521 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
521
522
522 user_log = relationship('UserLog')
523 user_log = relationship('UserLog')
523 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
524 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
524
525
525 repositories = relationship('Repository')
526 repositories = relationship('Repository')
526 repository_groups = relationship('RepoGroup')
527 repository_groups = relationship('RepoGroup')
527 user_groups = relationship('UserGroup')
528 user_groups = relationship('UserGroup')
528
529
529 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
530 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
530 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
531 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
531
532
532 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
533 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
533 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
534 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
534 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
535 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
535
536
536 group_member = relationship('UserGroupMember', cascade='all')
537 group_member = relationship('UserGroupMember', cascade='all')
537
538
538 notifications = relationship('UserNotification', cascade='all')
539 notifications = relationship('UserNotification', cascade='all')
539 # notifications assigned to this user
540 # notifications assigned to this user
540 user_created_notifications = relationship('Notification', cascade='all')
541 user_created_notifications = relationship('Notification', cascade='all')
541 # comments created by this user
542 # comments created by this user
542 user_comments = relationship('ChangesetComment', cascade='all')
543 user_comments = relationship('ChangesetComment', cascade='all')
543 # user profile extra info
544 # user profile extra info
544 user_emails = relationship('UserEmailMap', cascade='all')
545 user_emails = relationship('UserEmailMap', cascade='all')
545 user_ip_map = relationship('UserIpMap', cascade='all')
546 user_ip_map = relationship('UserIpMap', cascade='all')
546 user_auth_tokens = relationship('UserApiKeys', cascade='all')
547 user_auth_tokens = relationship('UserApiKeys', cascade='all')
547 # gists
548 # gists
548 user_gists = relationship('Gist', cascade='all')
549 user_gists = relationship('Gist', cascade='all')
549 # user pull requests
550 # user pull requests
550 user_pull_requests = relationship('PullRequest', cascade='all')
551 user_pull_requests = relationship('PullRequest', cascade='all')
551 # external identities
552 # external identities
552 extenal_identities = relationship(
553 extenal_identities = relationship(
553 'ExternalIdentity',
554 'ExternalIdentity',
554 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
555 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
555 cascade='all')
556 cascade='all')
556
557
557 def __unicode__(self):
558 def __unicode__(self):
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
559 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
559 self.user_id, self.username)
560 self.user_id, self.username)
560
561
561 @hybrid_property
562 @hybrid_property
562 def email(self):
563 def email(self):
563 return self._email
564 return self._email
564
565
565 @email.setter
566 @email.setter
566 def email(self, val):
567 def email(self, val):
567 self._email = val.lower() if val else None
568 self._email = val.lower() if val else None
568
569
569 @hybrid_property
570 @hybrid_property
570 def first_name(self):
571 def first_name(self):
571 from rhodecode.lib import helpers as h
572 from rhodecode.lib import helpers as h
572 if self.name:
573 if self.name:
573 return h.escape(self.name)
574 return h.escape(self.name)
574 return self.name
575 return self.name
575
576
576 @hybrid_property
577 @hybrid_property
577 def last_name(self):
578 def last_name(self):
578 from rhodecode.lib import helpers as h
579 from rhodecode.lib import helpers as h
579 if self.lastname:
580 if self.lastname:
580 return h.escape(self.lastname)
581 return h.escape(self.lastname)
581 return self.lastname
582 return self.lastname
582
583
583 @hybrid_property
584 @hybrid_property
584 def api_key(self):
585 def api_key(self):
585 """
586 """
586 Fetch if exist an auth-token with role ALL connected to this user
587 Fetch if exist an auth-token with role ALL connected to this user
587 """
588 """
588 user_auth_token = UserApiKeys.query()\
589 user_auth_token = UserApiKeys.query()\
589 .filter(UserApiKeys.user_id == self.user_id)\
590 .filter(UserApiKeys.user_id == self.user_id)\
590 .filter(or_(UserApiKeys.expires == -1,
591 .filter(or_(UserApiKeys.expires == -1,
591 UserApiKeys.expires >= time.time()))\
592 UserApiKeys.expires >= time.time()))\
592 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
593 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
593 if user_auth_token:
594 if user_auth_token:
594 user_auth_token = user_auth_token.api_key
595 user_auth_token = user_auth_token.api_key
595
596
596 return user_auth_token
597 return user_auth_token
597
598
598 @api_key.setter
599 @api_key.setter
599 def api_key(self, val):
600 def api_key(self, val):
600 # don't allow to set API key this is deprecated for now
601 # don't allow to set API key this is deprecated for now
601 self._api_key = None
602 self._api_key = None
602
603
603 @property
604 @property
604 def reviewer_pull_requests(self):
605 def reviewer_pull_requests(self):
605 return PullRequestReviewers.query() \
606 return PullRequestReviewers.query() \
606 .options(joinedload(PullRequestReviewers.pull_request)) \
607 .options(joinedload(PullRequestReviewers.pull_request)) \
607 .filter(PullRequestReviewers.user_id == self.user_id) \
608 .filter(PullRequestReviewers.user_id == self.user_id) \
608 .all()
609 .all()
609
610
610 @property
611 @property
611 def firstname(self):
612 def firstname(self):
612 # alias for future
613 # alias for future
613 return self.name
614 return self.name
614
615
615 @property
616 @property
616 def emails(self):
617 def emails(self):
617 other = UserEmailMap.query().filter(UserEmailMap.user == self).all()
618 other = UserEmailMap.query().filter(UserEmailMap.user == self).all()
618 return [self.email] + [x.email for x in other]
619 return [self.email] + [x.email for x in other]
619
620
620 @property
621 @property
621 def auth_tokens(self):
622 def auth_tokens(self):
622 auth_tokens = self.get_auth_tokens()
623 auth_tokens = self.get_auth_tokens()
623 return [x.api_key for x in auth_tokens]
624 return [x.api_key for x in auth_tokens]
624
625
625 def get_auth_tokens(self):
626 def get_auth_tokens(self):
626 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
627 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
627
628
628 @property
629 @property
629 def feed_token(self):
630 def feed_token(self):
630 return self.get_feed_token()
631 return self.get_feed_token()
631
632
632 def get_feed_token(self):
633 def get_feed_token(self):
633 feed_tokens = UserApiKeys.query()\
634 feed_tokens = UserApiKeys.query()\
634 .filter(UserApiKeys.user == self)\
635 .filter(UserApiKeys.user == self)\
635 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
636 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
636 .all()
637 .all()
637 if feed_tokens:
638 if feed_tokens:
638 return feed_tokens[0].api_key
639 return feed_tokens[0].api_key
639 return 'NO_FEED_TOKEN_AVAILABLE'
640 return 'NO_FEED_TOKEN_AVAILABLE'
640
641
641 @classmethod
642 @classmethod
642 def extra_valid_auth_tokens(cls, user, role=None):
643 def extra_valid_auth_tokens(cls, user, role=None):
643 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
644 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
644 .filter(or_(UserApiKeys.expires == -1,
645 .filter(or_(UserApiKeys.expires == -1,
645 UserApiKeys.expires >= time.time()))
646 UserApiKeys.expires >= time.time()))
646 if role:
647 if role:
647 tokens = tokens.filter(or_(UserApiKeys.role == role,
648 tokens = tokens.filter(or_(UserApiKeys.role == role,
648 UserApiKeys.role == UserApiKeys.ROLE_ALL))
649 UserApiKeys.role == UserApiKeys.ROLE_ALL))
649 return tokens.all()
650 return tokens.all()
650
651
651 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
652 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
652 from rhodecode.lib import auth
653 from rhodecode.lib import auth
653
654
654 log.debug('Trying to authenticate user: %s via auth-token, '
655 log.debug('Trying to authenticate user: %s via auth-token, '
655 'and roles: %s', self, roles)
656 'and roles: %s', self, roles)
656
657
657 if not auth_token:
658 if not auth_token:
658 return False
659 return False
659
660
660 crypto_backend = auth.crypto_backend()
661 crypto_backend = auth.crypto_backend()
661
662
662 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
663 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
663 tokens_q = UserApiKeys.query()\
664 tokens_q = UserApiKeys.query()\
664 .filter(UserApiKeys.user_id == self.user_id)\
665 .filter(UserApiKeys.user_id == self.user_id)\
665 .filter(or_(UserApiKeys.expires == -1,
666 .filter(or_(UserApiKeys.expires == -1,
666 UserApiKeys.expires >= time.time()))
667 UserApiKeys.expires >= time.time()))
667
668
668 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
669 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
669
670
670 plain_tokens = []
671 plain_tokens = []
671 hash_tokens = []
672 hash_tokens = []
672
673
673 for token in tokens_q.all():
674 for token in tokens_q.all():
674 # verify scope first
675 # verify scope first
675 if token.repo_id:
676 if token.repo_id:
676 # token has a scope, we need to verify it
677 # token has a scope, we need to verify it
677 if scope_repo_id != token.repo_id:
678 if scope_repo_id != token.repo_id:
678 log.debug(
679 log.debug(
679 'Scope mismatch: token has a set repo scope: %s, '
680 'Scope mismatch: token has a set repo scope: %s, '
680 'and calling scope is:%s, skipping further checks',
681 'and calling scope is:%s, skipping further checks',
681 token.repo, scope_repo_id)
682 token.repo, scope_repo_id)
682 # token has a scope, and it doesn't match, skip token
683 # token has a scope, and it doesn't match, skip token
683 continue
684 continue
684
685
685 if token.api_key.startswith(crypto_backend.ENC_PREF):
686 if token.api_key.startswith(crypto_backend.ENC_PREF):
686 hash_tokens.append(token.api_key)
687 hash_tokens.append(token.api_key)
687 else:
688 else:
688 plain_tokens.append(token.api_key)
689 plain_tokens.append(token.api_key)
689
690
690 is_plain_match = auth_token in plain_tokens
691 is_plain_match = auth_token in plain_tokens
691 if is_plain_match:
692 if is_plain_match:
692 return True
693 return True
693
694
694 for hashed in hash_tokens:
695 for hashed in hash_tokens:
695 # TODO(marcink): this is expensive to calculate, but most secure
696 # TODO(marcink): this is expensive to calculate, but most secure
696 match = crypto_backend.hash_check(auth_token, hashed)
697 match = crypto_backend.hash_check(auth_token, hashed)
697 if match:
698 if match:
698 return True
699 return True
699
700
700 return False
701 return False
701
702
702 @property
703 @property
703 def ip_addresses(self):
704 def ip_addresses(self):
704 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
705 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
705 return [x.ip_addr for x in ret]
706 return [x.ip_addr for x in ret]
706
707
707 @property
708 @property
708 def username_and_name(self):
709 def username_and_name(self):
709 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
710 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
710
711
711 @property
712 @property
712 def username_or_name_or_email(self):
713 def username_or_name_or_email(self):
713 full_name = self.full_name if self.full_name is not ' ' else None
714 full_name = self.full_name if self.full_name is not ' ' else None
714 return self.username or full_name or self.email
715 return self.username or full_name or self.email
715
716
716 @property
717 @property
717 def full_name(self):
718 def full_name(self):
718 return '%s %s' % (self.first_name, self.last_name)
719 return '%s %s' % (self.first_name, self.last_name)
719
720
720 @property
721 @property
721 def full_name_or_username(self):
722 def full_name_or_username(self):
722 return ('%s %s' % (self.first_name, self.last_name)
723 return ('%s %s' % (self.first_name, self.last_name)
723 if (self.first_name and self.last_name) else self.username)
724 if (self.first_name and self.last_name) else self.username)
724
725
725 @property
726 @property
726 def full_contact(self):
727 def full_contact(self):
727 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
728 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
728
729
729 @property
730 @property
730 def short_contact(self):
731 def short_contact(self):
731 return '%s %s' % (self.first_name, self.last_name)
732 return '%s %s' % (self.first_name, self.last_name)
732
733
733 @property
734 @property
734 def is_admin(self):
735 def is_admin(self):
735 return self.admin
736 return self.admin
736
737
737 @property
738 @property
738 def AuthUser(self):
739 def AuthUser(self):
739 """
740 """
740 Returns instance of AuthUser for this user
741 Returns instance of AuthUser for this user
741 """
742 """
742 from rhodecode.lib.auth import AuthUser
743 from rhodecode.lib.auth import AuthUser
743 return AuthUser(user_id=self.user_id, username=self.username)
744 return AuthUser(user_id=self.user_id, username=self.username)
744
745
745 @hybrid_property
746 @hybrid_property
746 def user_data(self):
747 def user_data(self):
747 if not self._user_data:
748 if not self._user_data:
748 return {}
749 return {}
749
750
750 try:
751 try:
751 return json.loads(self._user_data)
752 return json.loads(self._user_data)
752 except TypeError:
753 except TypeError:
753 return {}
754 return {}
754
755
755 @user_data.setter
756 @user_data.setter
756 def user_data(self, val):
757 def user_data(self, val):
757 if not isinstance(val, dict):
758 if not isinstance(val, dict):
758 raise Exception('user_data must be dict, got %s' % type(val))
759 raise Exception('user_data must be dict, got %s' % type(val))
759 try:
760 try:
760 self._user_data = json.dumps(val)
761 self._user_data = json.dumps(val)
761 except Exception:
762 except Exception:
762 log.error(traceback.format_exc())
763 log.error(traceback.format_exc())
763
764
764 @classmethod
765 @classmethod
765 def get_by_username(cls, username, case_insensitive=False,
766 def get_by_username(cls, username, case_insensitive=False,
766 cache=False, identity_cache=False):
767 cache=False, identity_cache=False):
767 session = Session()
768 session = Session()
768
769
769 if case_insensitive:
770 if case_insensitive:
770 q = cls.query().filter(
771 q = cls.query().filter(
771 func.lower(cls.username) == func.lower(username))
772 func.lower(cls.username) == func.lower(username))
772 else:
773 else:
773 q = cls.query().filter(cls.username == username)
774 q = cls.query().filter(cls.username == username)
774
775
775 if cache:
776 if cache:
776 if identity_cache:
777 if identity_cache:
777 val = cls.identity_cache(session, 'username', username)
778 val = cls.identity_cache(session, 'username', username)
778 if val:
779 if val:
779 return val
780 return val
780 else:
781 else:
781 cache_key = "get_user_by_name_%s" % _hash_key(username)
782 cache_key = "get_user_by_name_%s" % _hash_key(username)
782 q = q.options(
783 q = q.options(
783 FromCache("sql_cache_short", cache_key))
784 FromCache("sql_cache_short", cache_key))
784
785
785 return q.scalar()
786 return q.scalar()
786
787
787 @classmethod
788 @classmethod
788 def get_by_auth_token(cls, auth_token, cache=False):
789 def get_by_auth_token(cls, auth_token, cache=False):
789 q = UserApiKeys.query()\
790 q = UserApiKeys.query()\
790 .filter(UserApiKeys.api_key == auth_token)\
791 .filter(UserApiKeys.api_key == auth_token)\
791 .filter(or_(UserApiKeys.expires == -1,
792 .filter(or_(UserApiKeys.expires == -1,
792 UserApiKeys.expires >= time.time()))
793 UserApiKeys.expires >= time.time()))
793 if cache:
794 if cache:
794 q = q.options(
795 q = q.options(
795 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
796 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
796
797
797 match = q.first()
798 match = q.first()
798 if match:
799 if match:
799 return match.user
800 return match.user
800
801
801 @classmethod
802 @classmethod
802 def get_by_email(cls, email, case_insensitive=False, cache=False):
803 def get_by_email(cls, email, case_insensitive=False, cache=False):
803
804
804 if case_insensitive:
805 if case_insensitive:
805 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
806 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
806
807
807 else:
808 else:
808 q = cls.query().filter(cls.email == email)
809 q = cls.query().filter(cls.email == email)
809
810
810 email_key = _hash_key(email)
811 email_key = _hash_key(email)
811 if cache:
812 if cache:
812 q = q.options(
813 q = q.options(
813 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
814 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
814
815
815 ret = q.scalar()
816 ret = q.scalar()
816 if ret is None:
817 if ret is None:
817 q = UserEmailMap.query()
818 q = UserEmailMap.query()
818 # try fetching in alternate email map
819 # try fetching in alternate email map
819 if case_insensitive:
820 if case_insensitive:
820 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
821 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
821 else:
822 else:
822 q = q.filter(UserEmailMap.email == email)
823 q = q.filter(UserEmailMap.email == email)
823 q = q.options(joinedload(UserEmailMap.user))
824 q = q.options(joinedload(UserEmailMap.user))
824 if cache:
825 if cache:
825 q = q.options(
826 q = q.options(
826 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
827 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
827 ret = getattr(q.scalar(), 'user', None)
828 ret = getattr(q.scalar(), 'user', None)
828
829
829 return ret
830 return ret
830
831
831 @classmethod
832 @classmethod
832 def get_from_cs_author(cls, author):
833 def get_from_cs_author(cls, author):
833 """
834 """
834 Tries to get User objects out of commit author string
835 Tries to get User objects out of commit author string
835
836
836 :param author:
837 :param author:
837 """
838 """
838 from rhodecode.lib.helpers import email, author_name
839 from rhodecode.lib.helpers import email, author_name
839 # Valid email in the attribute passed, see if they're in the system
840 # Valid email in the attribute passed, see if they're in the system
840 _email = email(author)
841 _email = email(author)
841 if _email:
842 if _email:
842 user = cls.get_by_email(_email, case_insensitive=True)
843 user = cls.get_by_email(_email, case_insensitive=True)
843 if user:
844 if user:
844 return user
845 return user
845 # Maybe we can match by username?
846 # Maybe we can match by username?
846 _author = author_name(author)
847 _author = author_name(author)
847 user = cls.get_by_username(_author, case_insensitive=True)
848 user = cls.get_by_username(_author, case_insensitive=True)
848 if user:
849 if user:
849 return user
850 return user
850
851
851 def update_userdata(self, **kwargs):
852 def update_userdata(self, **kwargs):
852 usr = self
853 usr = self
853 old = usr.user_data
854 old = usr.user_data
854 old.update(**kwargs)
855 old.update(**kwargs)
855 usr.user_data = old
856 usr.user_data = old
856 Session().add(usr)
857 Session().add(usr)
857 log.debug('updated userdata with ', kwargs)
858 log.debug('updated userdata with ', kwargs)
858
859
859 def update_lastlogin(self):
860 def update_lastlogin(self):
860 """Update user lastlogin"""
861 """Update user lastlogin"""
861 self.last_login = datetime.datetime.now()
862 self.last_login = datetime.datetime.now()
862 Session().add(self)
863 Session().add(self)
863 log.debug('updated user %s lastlogin', self.username)
864 log.debug('updated user %s lastlogin', self.username)
864
865
865 def update_lastactivity(self):
866 def update_lastactivity(self):
866 """Update user lastactivity"""
867 """Update user lastactivity"""
867 self.last_activity = datetime.datetime.now()
868 self.last_activity = datetime.datetime.now()
868 Session().add(self)
869 Session().add(self)
869 log.debug('updated user %s lastactivity', self.username)
870 log.debug('updated user %s lastactivity', self.username)
870
871
871 def update_password(self, new_password):
872 def update_password(self, new_password):
872 from rhodecode.lib.auth import get_crypt_password
873 from rhodecode.lib.auth import get_crypt_password
873
874
874 self.password = get_crypt_password(new_password)
875 self.password = get_crypt_password(new_password)
875 Session().add(self)
876 Session().add(self)
876
877
877 @classmethod
878 @classmethod
878 def get_first_super_admin(cls):
879 def get_first_super_admin(cls):
879 user = User.query().filter(User.admin == true()).first()
880 user = User.query().filter(User.admin == true()).first()
880 if user is None:
881 if user is None:
881 raise Exception('FATAL: Missing administrative account!')
882 raise Exception('FATAL: Missing administrative account!')
882 return user
883 return user
883
884
884 @classmethod
885 @classmethod
885 def get_all_super_admins(cls):
886 def get_all_super_admins(cls):
886 """
887 """
887 Returns all admin accounts sorted by username
888 Returns all admin accounts sorted by username
888 """
889 """
889 return User.query().filter(User.admin == true())\
890 return User.query().filter(User.admin == true())\
890 .order_by(User.username.asc()).all()
891 .order_by(User.username.asc()).all()
891
892
892 @classmethod
893 @classmethod
893 def get_default_user(cls, cache=False, refresh=False):
894 def get_default_user(cls, cache=False, refresh=False):
894 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
895 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
895 if user is None:
896 if user is None:
896 raise Exception('FATAL: Missing default account!')
897 raise Exception('FATAL: Missing default account!')
897 if refresh:
898 if refresh:
898 # The default user might be based on outdated state which
899 # The default user might be based on outdated state which
899 # has been loaded from the cache.
900 # has been loaded from the cache.
900 # A call to refresh() ensures that the
901 # A call to refresh() ensures that the
901 # latest state from the database is used.
902 # latest state from the database is used.
902 Session().refresh(user)
903 Session().refresh(user)
903 return user
904 return user
904
905
905 def _get_default_perms(self, user, suffix=''):
906 def _get_default_perms(self, user, suffix=''):
906 from rhodecode.model.permission import PermissionModel
907 from rhodecode.model.permission import PermissionModel
907 return PermissionModel().get_default_perms(user.user_perms, suffix)
908 return PermissionModel().get_default_perms(user.user_perms, suffix)
908
909
909 def get_default_perms(self, suffix=''):
910 def get_default_perms(self, suffix=''):
910 return self._get_default_perms(self, suffix)
911 return self._get_default_perms(self, suffix)
911
912
912 def get_api_data(self, include_secrets=False, details='full'):
913 def get_api_data(self, include_secrets=False, details='full'):
913 """
914 """
914 Common function for generating user related data for API
915 Common function for generating user related data for API
915
916
916 :param include_secrets: By default secrets in the API data will be replaced
917 :param include_secrets: By default secrets in the API data will be replaced
917 by a placeholder value to prevent exposing this data by accident. In case
918 by a placeholder value to prevent exposing this data by accident. In case
918 this data shall be exposed, set this flag to ``True``.
919 this data shall be exposed, set this flag to ``True``.
919
920
920 :param details: details can be 'basic|full' basic gives only a subset of
921 :param details: details can be 'basic|full' basic gives only a subset of
921 the available user information that includes user_id, name and emails.
922 the available user information that includes user_id, name and emails.
922 """
923 """
923 user = self
924 user = self
924 user_data = self.user_data
925 user_data = self.user_data
925 data = {
926 data = {
926 'user_id': user.user_id,
927 'user_id': user.user_id,
927 'username': user.username,
928 'username': user.username,
928 'firstname': user.name,
929 'firstname': user.name,
929 'lastname': user.lastname,
930 'lastname': user.lastname,
930 'email': user.email,
931 'email': user.email,
931 'emails': user.emails,
932 'emails': user.emails,
932 }
933 }
933 if details == 'basic':
934 if details == 'basic':
934 return data
935 return data
935
936
936 auth_token_length = 40
937 auth_token_length = 40
937 auth_token_replacement = '*' * auth_token_length
938 auth_token_replacement = '*' * auth_token_length
938
939
939 extras = {
940 extras = {
940 'auth_tokens': [auth_token_replacement],
941 'auth_tokens': [auth_token_replacement],
941 'active': user.active,
942 'active': user.active,
942 'admin': user.admin,
943 'admin': user.admin,
943 'extern_type': user.extern_type,
944 'extern_type': user.extern_type,
944 'extern_name': user.extern_name,
945 'extern_name': user.extern_name,
945 'last_login': user.last_login,
946 'last_login': user.last_login,
946 'last_activity': user.last_activity,
947 'last_activity': user.last_activity,
947 'ip_addresses': user.ip_addresses,
948 'ip_addresses': user.ip_addresses,
948 'language': user_data.get('language')
949 'language': user_data.get('language')
949 }
950 }
950 data.update(extras)
951 data.update(extras)
951
952
952 if include_secrets:
953 if include_secrets:
953 data['auth_tokens'] = user.auth_tokens
954 data['auth_tokens'] = user.auth_tokens
954 return data
955 return data
955
956
956 def __json__(self):
957 def __json__(self):
957 data = {
958 data = {
958 'full_name': self.full_name,
959 'full_name': self.full_name,
959 'full_name_or_username': self.full_name_or_username,
960 'full_name_or_username': self.full_name_or_username,
960 'short_contact': self.short_contact,
961 'short_contact': self.short_contact,
961 'full_contact': self.full_contact,
962 'full_contact': self.full_contact,
962 }
963 }
963 data.update(self.get_api_data())
964 data.update(self.get_api_data())
964 return data
965 return data
965
966
966
967
967 class UserApiKeys(Base, BaseModel):
968 class UserApiKeys(Base, BaseModel):
968 __tablename__ = 'user_api_keys'
969 __tablename__ = 'user_api_keys'
969 __table_args__ = (
970 __table_args__ = (
970 Index('uak_api_key_idx', 'api_key', unique=True),
971 Index('uak_api_key_idx', 'api_key', unique=True),
971 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
972 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
972 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
974 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
974 )
975 )
975 __mapper_args__ = {}
976 __mapper_args__ = {}
976
977
977 # ApiKey role
978 # ApiKey role
978 ROLE_ALL = 'token_role_all'
979 ROLE_ALL = 'token_role_all'
979 ROLE_HTTP = 'token_role_http'
980 ROLE_HTTP = 'token_role_http'
980 ROLE_VCS = 'token_role_vcs'
981 ROLE_VCS = 'token_role_vcs'
981 ROLE_API = 'token_role_api'
982 ROLE_API = 'token_role_api'
982 ROLE_FEED = 'token_role_feed'
983 ROLE_FEED = 'token_role_feed'
983 ROLE_PASSWORD_RESET = 'token_password_reset'
984 ROLE_PASSWORD_RESET = 'token_password_reset'
984
985
985 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
986 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
986
987
987 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
989 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
989 api_key = Column("api_key", String(255), nullable=False, unique=True)
990 api_key = Column("api_key", String(255), nullable=False, unique=True)
990 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
991 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
991 expires = Column('expires', Float(53), nullable=False)
992 expires = Column('expires', Float(53), nullable=False)
992 role = Column('role', String(255), nullable=True)
993 role = Column('role', String(255), nullable=True)
993 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
994
995
995 # scope columns
996 # scope columns
996 repo_id = Column(
997 repo_id = Column(
997 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
998 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
998 nullable=True, unique=None, default=None)
999 nullable=True, unique=None, default=None)
999 repo = relationship('Repository', lazy='joined')
1000 repo = relationship('Repository', lazy='joined')
1000
1001
1001 repo_group_id = Column(
1002 repo_group_id = Column(
1002 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1003 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1003 nullable=True, unique=None, default=None)
1004 nullable=True, unique=None, default=None)
1004 repo_group = relationship('RepoGroup', lazy='joined')
1005 repo_group = relationship('RepoGroup', lazy='joined')
1005
1006
1006 user = relationship('User', lazy='joined')
1007 user = relationship('User', lazy='joined')
1007
1008
1008 def __unicode__(self):
1009 def __unicode__(self):
1009 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1010 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1010
1011
1011 def __json__(self):
1012 def __json__(self):
1012 data = {
1013 data = {
1013 'auth_token': self.api_key,
1014 'auth_token': self.api_key,
1014 'role': self.role,
1015 'role': self.role,
1015 'scope': self.scope_humanized,
1016 'scope': self.scope_humanized,
1016 'expired': self.expired
1017 'expired': self.expired
1017 }
1018 }
1018 return data
1019 return data
1019
1020
1020 def get_api_data(self, include_secrets=False):
1021 def get_api_data(self, include_secrets=False):
1021 data = self.__json__()
1022 data = self.__json__()
1022 if include_secrets:
1023 if include_secrets:
1023 return data
1024 return data
1024 else:
1025 else:
1025 data['auth_token'] = self.token_obfuscated
1026 data['auth_token'] = self.token_obfuscated
1026 return data
1027 return data
1027
1028
1028 @hybrid_property
1029 @hybrid_property
1029 def description_safe(self):
1030 def description_safe(self):
1030 from rhodecode.lib import helpers as h
1031 from rhodecode.lib import helpers as h
1031 return h.escape(self.description)
1032 return h.escape(self.description)
1032
1033
1033 @property
1034 @property
1034 def expired(self):
1035 def expired(self):
1035 if self.expires == -1:
1036 if self.expires == -1:
1036 return False
1037 return False
1037 return time.time() > self.expires
1038 return time.time() > self.expires
1038
1039
1039 @classmethod
1040 @classmethod
1040 def _get_role_name(cls, role):
1041 def _get_role_name(cls, role):
1041 return {
1042 return {
1042 cls.ROLE_ALL: _('all'),
1043 cls.ROLE_ALL: _('all'),
1043 cls.ROLE_HTTP: _('http/web interface'),
1044 cls.ROLE_HTTP: _('http/web interface'),
1044 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1045 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1045 cls.ROLE_API: _('api calls'),
1046 cls.ROLE_API: _('api calls'),
1046 cls.ROLE_FEED: _('feed access'),
1047 cls.ROLE_FEED: _('feed access'),
1047 }.get(role, role)
1048 }.get(role, role)
1048
1049
1049 @property
1050 @property
1050 def role_humanized(self):
1051 def role_humanized(self):
1051 return self._get_role_name(self.role)
1052 return self._get_role_name(self.role)
1052
1053
1053 def _get_scope(self):
1054 def _get_scope(self):
1054 if self.repo:
1055 if self.repo:
1055 return repr(self.repo)
1056 return repr(self.repo)
1056 if self.repo_group:
1057 if self.repo_group:
1057 return repr(self.repo_group) + ' (recursive)'
1058 return repr(self.repo_group) + ' (recursive)'
1058 return 'global'
1059 return 'global'
1059
1060
1060 @property
1061 @property
1061 def scope_humanized(self):
1062 def scope_humanized(self):
1062 return self._get_scope()
1063 return self._get_scope()
1063
1064
1064 @property
1065 @property
1065 def token_obfuscated(self):
1066 def token_obfuscated(self):
1066 if self.api_key:
1067 if self.api_key:
1067 return self.api_key[:4] + "****"
1068 return self.api_key[:4] + "****"
1068
1069
1069
1070
1070 class UserEmailMap(Base, BaseModel):
1071 class UserEmailMap(Base, BaseModel):
1071 __tablename__ = 'user_email_map'
1072 __tablename__ = 'user_email_map'
1072 __table_args__ = (
1073 __table_args__ = (
1073 Index('uem_email_idx', 'email'),
1074 Index('uem_email_idx', 'email'),
1074 UniqueConstraint('email'),
1075 UniqueConstraint('email'),
1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1076 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1077 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1077 )
1078 )
1078 __mapper_args__ = {}
1079 __mapper_args__ = {}
1079
1080
1080 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1083 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1083 user = relationship('User', lazy='joined')
1084 user = relationship('User', lazy='joined')
1084
1085
1085 @validates('_email')
1086 @validates('_email')
1086 def validate_email(self, key, email):
1087 def validate_email(self, key, email):
1087 # check if this email is not main one
1088 # check if this email is not main one
1088 main_email = Session().query(User).filter(User.email == email).scalar()
1089 main_email = Session().query(User).filter(User.email == email).scalar()
1089 if main_email is not None:
1090 if main_email is not None:
1090 raise AttributeError('email %s is present is user table' % email)
1091 raise AttributeError('email %s is present is user table' % email)
1091 return email
1092 return email
1092
1093
1093 @hybrid_property
1094 @hybrid_property
1094 def email(self):
1095 def email(self):
1095 return self._email
1096 return self._email
1096
1097
1097 @email.setter
1098 @email.setter
1098 def email(self, val):
1099 def email(self, val):
1099 self._email = val.lower() if val else None
1100 self._email = val.lower() if val else None
1100
1101
1101
1102
1102 class UserIpMap(Base, BaseModel):
1103 class UserIpMap(Base, BaseModel):
1103 __tablename__ = 'user_ip_map'
1104 __tablename__ = 'user_ip_map'
1104 __table_args__ = (
1105 __table_args__ = (
1105 UniqueConstraint('user_id', 'ip_addr'),
1106 UniqueConstraint('user_id', 'ip_addr'),
1106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1108 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1108 )
1109 )
1109 __mapper_args__ = {}
1110 __mapper_args__ = {}
1110
1111
1111 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1112 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1112 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1113 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1113 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1114 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1114 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1115 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1115 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1116 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1116 user = relationship('User', lazy='joined')
1117 user = relationship('User', lazy='joined')
1117
1118
1118 @hybrid_property
1119 @hybrid_property
1119 def description_safe(self):
1120 def description_safe(self):
1120 from rhodecode.lib import helpers as h
1121 from rhodecode.lib import helpers as h
1121 return h.escape(self.description)
1122 return h.escape(self.description)
1122
1123
1123 @classmethod
1124 @classmethod
1124 def _get_ip_range(cls, ip_addr):
1125 def _get_ip_range(cls, ip_addr):
1125 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1126 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1126 return [str(net.network_address), str(net.broadcast_address)]
1127 return [str(net.network_address), str(net.broadcast_address)]
1127
1128
1128 def __json__(self):
1129 def __json__(self):
1129 return {
1130 return {
1130 'ip_addr': self.ip_addr,
1131 'ip_addr': self.ip_addr,
1131 'ip_range': self._get_ip_range(self.ip_addr),
1132 'ip_range': self._get_ip_range(self.ip_addr),
1132 }
1133 }
1133
1134
1134 def __unicode__(self):
1135 def __unicode__(self):
1135 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1136 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1136 self.user_id, self.ip_addr)
1137 self.user_id, self.ip_addr)
1137
1138
1138
1139
1139 class UserLog(Base, BaseModel):
1140 class UserLog(Base, BaseModel):
1140 __tablename__ = 'user_logs'
1141 __tablename__ = 'user_logs'
1141 __table_args__ = (
1142 __table_args__ = (
1142 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1143 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1144 )
1145 )
1145 VERSION_1 = 'v1'
1146 VERSION_1 = 'v1'
1146 VERSION_2 = 'v2'
1147 VERSION_2 = 'v2'
1147 VERSIONS = [VERSION_1, VERSION_2]
1148 VERSIONS = [VERSION_1, VERSION_2]
1148
1149
1149 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1150 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1151 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1151 username = Column("username", String(255), nullable=True, unique=None, default=None)
1152 username = Column("username", String(255), nullable=True, unique=None, default=None)
1152 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1153 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1153 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1154 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1154 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1155 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1155 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1156 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1156 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1157 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1157
1158
1158 version = Column("version", String(255), nullable=True, default=VERSION_1)
1159 version = Column("version", String(255), nullable=True, default=VERSION_1)
1159 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1160 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1160 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1161 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1161
1162
1162 def __unicode__(self):
1163 def __unicode__(self):
1163 return u"<%s('id:%s:%s')>" % (
1164 return u"<%s('id:%s:%s')>" % (
1164 self.__class__.__name__, self.repository_name, self.action)
1165 self.__class__.__name__, self.repository_name, self.action)
1165
1166
1166 def __json__(self):
1167 def __json__(self):
1167 return {
1168 return {
1168 'user_id': self.user_id,
1169 'user_id': self.user_id,
1169 'username': self.username,
1170 'username': self.username,
1170 'repository_id': self.repository_id,
1171 'repository_id': self.repository_id,
1171 'repository_name': self.repository_name,
1172 'repository_name': self.repository_name,
1172 'user_ip': self.user_ip,
1173 'user_ip': self.user_ip,
1173 'action_date': self.action_date,
1174 'action_date': self.action_date,
1174 'action': self.action,
1175 'action': self.action,
1175 }
1176 }
1176
1177
1177 @property
1178 @property
1178 def action_as_day(self):
1179 def action_as_day(self):
1179 return datetime.date(*self.action_date.timetuple()[:3])
1180 return datetime.date(*self.action_date.timetuple()[:3])
1180
1181
1181 user = relationship('User')
1182 user = relationship('User')
1182 repository = relationship('Repository', cascade='')
1183 repository = relationship('Repository', cascade='')
1183
1184
1184
1185
1185 class UserGroup(Base, BaseModel):
1186 class UserGroup(Base, BaseModel):
1186 __tablename__ = 'users_groups'
1187 __tablename__ = 'users_groups'
1187 __table_args__ = (
1188 __table_args__ = (
1188 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1189 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1190 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1190 )
1191 )
1191
1192
1192 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1194 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1194 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1195 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1195 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1196 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1196 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1197 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1197 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1198 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1198 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1199 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1199 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1200 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1200
1201
1201 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1202 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1202 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1203 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1203 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1204 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1204 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1205 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1205 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1206 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1206 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1207 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1207
1208
1208 user = relationship('User')
1209 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1210
1211 @classmethod
1212 def _load_group_data(cls, column):
1213 if not column:
1214 return {}
1215
1216 try:
1217 return json.loads(column) or {}
1218 except TypeError:
1219 return {}
1209
1220
1210 @hybrid_property
1221 @hybrid_property
1211 def description_safe(self):
1222 def description_safe(self):
1212 from rhodecode.lib import helpers as h
1223 from rhodecode.lib import helpers as h
1213 return h.escape(self.description)
1224 return h.escape(self.description)
1214
1225
1215 @hybrid_property
1226 @hybrid_property
1216 def group_data(self):
1227 def group_data(self):
1217 if not self._group_data:
1228 return self._load_group_data(self._group_data)
1218 return {}
1229
1219
1230 @group_data.expression
1220 try:
1231 def group_data(self, **kwargs):
1221 return json.loads(self._group_data)
1232 return self._group_data
1222 except TypeError:
1223 return {}
1224
1233
1225 @group_data.setter
1234 @group_data.setter
1226 def group_data(self, val):
1235 def group_data(self, val):
1227 try:
1236 try:
1228 self._group_data = json.dumps(val)
1237 self._group_data = json.dumps(val)
1229 except Exception:
1238 except Exception:
1230 log.error(traceback.format_exc())
1239 log.error(traceback.format_exc())
1231
1240
1232 def __unicode__(self):
1241 def __unicode__(self):
1233 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1242 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1234 self.users_group_id,
1243 self.users_group_id,
1235 self.users_group_name)
1244 self.users_group_name)
1236
1245
1237 @classmethod
1246 @classmethod
1238 def get_by_group_name(cls, group_name, cache=False,
1247 def get_by_group_name(cls, group_name, cache=False,
1239 case_insensitive=False):
1248 case_insensitive=False):
1240 if case_insensitive:
1249 if case_insensitive:
1241 q = cls.query().filter(func.lower(cls.users_group_name) ==
1250 q = cls.query().filter(func.lower(cls.users_group_name) ==
1242 func.lower(group_name))
1251 func.lower(group_name))
1243
1252
1244 else:
1253 else:
1245 q = cls.query().filter(cls.users_group_name == group_name)
1254 q = cls.query().filter(cls.users_group_name == group_name)
1246 if cache:
1255 if cache:
1247 q = q.options(
1256 q = q.options(
1248 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1257 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1249 return q.scalar()
1258 return q.scalar()
1250
1259
1251 @classmethod
1260 @classmethod
1252 def get(cls, user_group_id, cache=False):
1261 def get(cls, user_group_id, cache=False):
1253 user_group = cls.query()
1262 user_group = cls.query()
1254 if cache:
1263 if cache:
1255 user_group = user_group.options(
1264 user_group = user_group.options(
1256 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1265 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1257 return user_group.get(user_group_id)
1266 return user_group.get(user_group_id)
1258
1267
1259 def permissions(self, with_admins=True, with_owner=True):
1268 def permissions(self, with_admins=True, with_owner=True):
1260 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1269 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1261 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1270 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1262 joinedload(UserUserGroupToPerm.user),
1271 joinedload(UserUserGroupToPerm.user),
1263 joinedload(UserUserGroupToPerm.permission),)
1272 joinedload(UserUserGroupToPerm.permission),)
1264
1273
1265 # get owners and admins and permissions. We do a trick of re-writing
1274 # get owners and admins and permissions. We do a trick of re-writing
1266 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1275 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1267 # has a global reference and changing one object propagates to all
1276 # has a global reference and changing one object propagates to all
1268 # others. This means if admin is also an owner admin_row that change
1277 # others. This means if admin is also an owner admin_row that change
1269 # would propagate to both objects
1278 # would propagate to both objects
1270 perm_rows = []
1279 perm_rows = []
1271 for _usr in q.all():
1280 for _usr in q.all():
1272 usr = AttributeDict(_usr.user.get_dict())
1281 usr = AttributeDict(_usr.user.get_dict())
1273 usr.permission = _usr.permission.permission_name
1282 usr.permission = _usr.permission.permission_name
1274 perm_rows.append(usr)
1283 perm_rows.append(usr)
1275
1284
1276 # filter the perm rows by 'default' first and then sort them by
1285 # filter the perm rows by 'default' first and then sort them by
1277 # admin,write,read,none permissions sorted again alphabetically in
1286 # admin,write,read,none permissions sorted again alphabetically in
1278 # each group
1287 # each group
1279 perm_rows = sorted(perm_rows, key=display_sort)
1288 perm_rows = sorted(perm_rows, key=display_sort)
1280
1289
1281 _admin_perm = 'usergroup.admin'
1290 _admin_perm = 'usergroup.admin'
1282 owner_row = []
1291 owner_row = []
1283 if with_owner:
1292 if with_owner:
1284 usr = AttributeDict(self.user.get_dict())
1293 usr = AttributeDict(self.user.get_dict())
1285 usr.owner_row = True
1294 usr.owner_row = True
1286 usr.permission = _admin_perm
1295 usr.permission = _admin_perm
1287 owner_row.append(usr)
1296 owner_row.append(usr)
1288
1297
1289 super_admin_rows = []
1298 super_admin_rows = []
1290 if with_admins:
1299 if with_admins:
1291 for usr in User.get_all_super_admins():
1300 for usr in User.get_all_super_admins():
1292 # if this admin is also owner, don't double the record
1301 # if this admin is also owner, don't double the record
1293 if usr.user_id == owner_row[0].user_id:
1302 if usr.user_id == owner_row[0].user_id:
1294 owner_row[0].admin_row = True
1303 owner_row[0].admin_row = True
1295 else:
1304 else:
1296 usr = AttributeDict(usr.get_dict())
1305 usr = AttributeDict(usr.get_dict())
1297 usr.admin_row = True
1306 usr.admin_row = True
1298 usr.permission = _admin_perm
1307 usr.permission = _admin_perm
1299 super_admin_rows.append(usr)
1308 super_admin_rows.append(usr)
1300
1309
1301 return super_admin_rows + owner_row + perm_rows
1310 return super_admin_rows + owner_row + perm_rows
1302
1311
1303 def permission_user_groups(self):
1312 def permission_user_groups(self):
1304 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1313 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1305 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1314 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1306 joinedload(UserGroupUserGroupToPerm.target_user_group),
1315 joinedload(UserGroupUserGroupToPerm.target_user_group),
1307 joinedload(UserGroupUserGroupToPerm.permission),)
1316 joinedload(UserGroupUserGroupToPerm.permission),)
1308
1317
1309 perm_rows = []
1318 perm_rows = []
1310 for _user_group in q.all():
1319 for _user_group in q.all():
1311 usr = AttributeDict(_user_group.user_group.get_dict())
1320 usr = AttributeDict(_user_group.user_group.get_dict())
1312 usr.permission = _user_group.permission.permission_name
1321 usr.permission = _user_group.permission.permission_name
1313 perm_rows.append(usr)
1322 perm_rows.append(usr)
1314
1323
1315 return perm_rows
1324 return perm_rows
1316
1325
1317 def _get_default_perms(self, user_group, suffix=''):
1326 def _get_default_perms(self, user_group, suffix=''):
1318 from rhodecode.model.permission import PermissionModel
1327 from rhodecode.model.permission import PermissionModel
1319 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1328 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1320
1329
1321 def get_default_perms(self, suffix=''):
1330 def get_default_perms(self, suffix=''):
1322 return self._get_default_perms(self, suffix)
1331 return self._get_default_perms(self, suffix)
1323
1332
1324 def get_api_data(self, with_group_members=True, include_secrets=False):
1333 def get_api_data(self, with_group_members=True, include_secrets=False):
1325 """
1334 """
1326 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1335 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1327 basically forwarded.
1336 basically forwarded.
1328
1337
1329 """
1338 """
1330 user_group = self
1339 user_group = self
1331 data = {
1340 data = {
1332 'users_group_id': user_group.users_group_id,
1341 'users_group_id': user_group.users_group_id,
1333 'group_name': user_group.users_group_name,
1342 'group_name': user_group.users_group_name,
1334 'group_description': user_group.user_group_description,
1343 'group_description': user_group.user_group_description,
1335 'active': user_group.users_group_active,
1344 'active': user_group.users_group_active,
1336 'owner': user_group.user.username,
1345 'owner': user_group.user.username,
1337 'owner_email': user_group.user.email,
1346 'owner_email': user_group.user.email,
1338 }
1347 }
1339
1348
1340 if with_group_members:
1349 if with_group_members:
1341 users = []
1350 users = []
1342 for user in user_group.members:
1351 for user in user_group.members:
1343 user = user.user
1352 user = user.user
1344 users.append(user.get_api_data(include_secrets=include_secrets))
1353 users.append(user.get_api_data(include_secrets=include_secrets))
1345 data['users'] = users
1354 data['users'] = users
1346
1355
1347 return data
1356 return data
1348
1357
1349
1358
1350 class UserGroupMember(Base, BaseModel):
1359 class UserGroupMember(Base, BaseModel):
1351 __tablename__ = 'users_groups_members'
1360 __tablename__ = 'users_groups_members'
1352 __table_args__ = (
1361 __table_args__ = (
1353 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1362 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1354 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1363 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1355 )
1364 )
1356
1365
1357 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1358 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1367 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1359 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1368 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1360
1369
1361 user = relationship('User', lazy='joined')
1370 user = relationship('User', lazy='joined')
1362 users_group = relationship('UserGroup')
1371 users_group = relationship('UserGroup')
1363
1372
1364 def __init__(self, gr_id='', u_id=''):
1373 def __init__(self, gr_id='', u_id=''):
1365 self.users_group_id = gr_id
1374 self.users_group_id = gr_id
1366 self.user_id = u_id
1375 self.user_id = u_id
1367
1376
1368
1377
1369 class RepositoryField(Base, BaseModel):
1378 class RepositoryField(Base, BaseModel):
1370 __tablename__ = 'repositories_fields'
1379 __tablename__ = 'repositories_fields'
1371 __table_args__ = (
1380 __table_args__ = (
1372 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1381 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1374 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1383 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1375 )
1384 )
1376 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1385 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1377
1386
1378 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1387 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1379 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1388 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1380 field_key = Column("field_key", String(250))
1389 field_key = Column("field_key", String(250))
1381 field_label = Column("field_label", String(1024), nullable=False)
1390 field_label = Column("field_label", String(1024), nullable=False)
1382 field_value = Column("field_value", String(10000), nullable=False)
1391 field_value = Column("field_value", String(10000), nullable=False)
1383 field_desc = Column("field_desc", String(1024), nullable=False)
1392 field_desc = Column("field_desc", String(1024), nullable=False)
1384 field_type = Column("field_type", String(255), nullable=False, unique=None)
1393 field_type = Column("field_type", String(255), nullable=False, unique=None)
1385 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1394 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1386
1395
1387 repository = relationship('Repository')
1396 repository = relationship('Repository')
1388
1397
1389 @property
1398 @property
1390 def field_key_prefixed(self):
1399 def field_key_prefixed(self):
1391 return 'ex_%s' % self.field_key
1400 return 'ex_%s' % self.field_key
1392
1401
1393 @classmethod
1402 @classmethod
1394 def un_prefix_key(cls, key):
1403 def un_prefix_key(cls, key):
1395 if key.startswith(cls.PREFIX):
1404 if key.startswith(cls.PREFIX):
1396 return key[len(cls.PREFIX):]
1405 return key[len(cls.PREFIX):]
1397 return key
1406 return key
1398
1407
1399 @classmethod
1408 @classmethod
1400 def get_by_key_name(cls, key, repo):
1409 def get_by_key_name(cls, key, repo):
1401 row = cls.query()\
1410 row = cls.query()\
1402 .filter(cls.repository == repo)\
1411 .filter(cls.repository == repo)\
1403 .filter(cls.field_key == key).scalar()
1412 .filter(cls.field_key == key).scalar()
1404 return row
1413 return row
1405
1414
1406
1415
1407 class Repository(Base, BaseModel):
1416 class Repository(Base, BaseModel):
1408 __tablename__ = 'repositories'
1417 __tablename__ = 'repositories'
1409 __table_args__ = (
1418 __table_args__ = (
1410 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1419 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1411 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1420 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1412 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1421 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1413 )
1422 )
1414 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1423 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1415 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1424 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1416
1425
1417 STATE_CREATED = 'repo_state_created'
1426 STATE_CREATED = 'repo_state_created'
1418 STATE_PENDING = 'repo_state_pending'
1427 STATE_PENDING = 'repo_state_pending'
1419 STATE_ERROR = 'repo_state_error'
1428 STATE_ERROR = 'repo_state_error'
1420
1429
1421 LOCK_AUTOMATIC = 'lock_auto'
1430 LOCK_AUTOMATIC = 'lock_auto'
1422 LOCK_API = 'lock_api'
1431 LOCK_API = 'lock_api'
1423 LOCK_WEB = 'lock_web'
1432 LOCK_WEB = 'lock_web'
1424 LOCK_PULL = 'lock_pull'
1433 LOCK_PULL = 'lock_pull'
1425
1434
1426 NAME_SEP = URL_SEP
1435 NAME_SEP = URL_SEP
1427
1436
1428 repo_id = Column(
1437 repo_id = Column(
1429 "repo_id", Integer(), nullable=False, unique=True, default=None,
1438 "repo_id", Integer(), nullable=False, unique=True, default=None,
1430 primary_key=True)
1439 primary_key=True)
1431 _repo_name = Column(
1440 _repo_name = Column(
1432 "repo_name", Text(), nullable=False, default=None)
1441 "repo_name", Text(), nullable=False, default=None)
1433 _repo_name_hash = Column(
1442 _repo_name_hash = Column(
1434 "repo_name_hash", String(255), nullable=False, unique=True)
1443 "repo_name_hash", String(255), nullable=False, unique=True)
1435 repo_state = Column("repo_state", String(255), nullable=True)
1444 repo_state = Column("repo_state", String(255), nullable=True)
1436
1445
1437 clone_uri = Column(
1446 clone_uri = Column(
1438 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1447 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1439 default=None)
1448 default=None)
1440 repo_type = Column(
1449 repo_type = Column(
1441 "repo_type", String(255), nullable=False, unique=False, default=None)
1450 "repo_type", String(255), nullable=False, unique=False, default=None)
1442 user_id = Column(
1451 user_id = Column(
1443 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1452 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1444 unique=False, default=None)
1453 unique=False, default=None)
1445 private = Column(
1454 private = Column(
1446 "private", Boolean(), nullable=True, unique=None, default=None)
1455 "private", Boolean(), nullable=True, unique=None, default=None)
1447 enable_statistics = Column(
1456 enable_statistics = Column(
1448 "statistics", Boolean(), nullable=True, unique=None, default=True)
1457 "statistics", Boolean(), nullable=True, unique=None, default=True)
1449 enable_downloads = Column(
1458 enable_downloads = Column(
1450 "downloads", Boolean(), nullable=True, unique=None, default=True)
1459 "downloads", Boolean(), nullable=True, unique=None, default=True)
1451 description = Column(
1460 description = Column(
1452 "description", String(10000), nullable=True, unique=None, default=None)
1461 "description", String(10000), nullable=True, unique=None, default=None)
1453 created_on = Column(
1462 created_on = Column(
1454 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1463 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1455 default=datetime.datetime.now)
1464 default=datetime.datetime.now)
1456 updated_on = Column(
1465 updated_on = Column(
1457 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1466 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1458 default=datetime.datetime.now)
1467 default=datetime.datetime.now)
1459 _landing_revision = Column(
1468 _landing_revision = Column(
1460 "landing_revision", String(255), nullable=False, unique=False,
1469 "landing_revision", String(255), nullable=False, unique=False,
1461 default=None)
1470 default=None)
1462 enable_locking = Column(
1471 enable_locking = Column(
1463 "enable_locking", Boolean(), nullable=False, unique=None,
1472 "enable_locking", Boolean(), nullable=False, unique=None,
1464 default=False)
1473 default=False)
1465 _locked = Column(
1474 _locked = Column(
1466 "locked", String(255), nullable=True, unique=False, default=None)
1475 "locked", String(255), nullable=True, unique=False, default=None)
1467 _changeset_cache = Column(
1476 _changeset_cache = Column(
1468 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1477 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1469
1478
1470 fork_id = Column(
1479 fork_id = Column(
1471 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1480 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1472 nullable=True, unique=False, default=None)
1481 nullable=True, unique=False, default=None)
1473 group_id = Column(
1482 group_id = Column(
1474 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1483 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1475 unique=False, default=None)
1484 unique=False, default=None)
1476
1485
1477 user = relationship('User', lazy='joined')
1486 user = relationship('User', lazy='joined')
1478 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1487 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1479 group = relationship('RepoGroup', lazy='joined')
1488 group = relationship('RepoGroup', lazy='joined')
1480 repo_to_perm = relationship(
1489 repo_to_perm = relationship(
1481 'UserRepoToPerm', cascade='all',
1490 'UserRepoToPerm', cascade='all',
1482 order_by='UserRepoToPerm.repo_to_perm_id')
1491 order_by='UserRepoToPerm.repo_to_perm_id')
1483 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1492 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1484 stats = relationship('Statistics', cascade='all', uselist=False)
1493 stats = relationship('Statistics', cascade='all', uselist=False)
1485
1494
1486 followers = relationship(
1495 followers = relationship(
1487 'UserFollowing',
1496 'UserFollowing',
1488 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1497 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1489 cascade='all')
1498 cascade='all')
1490 extra_fields = relationship(
1499 extra_fields = relationship(
1491 'RepositoryField', cascade="all, delete, delete-orphan")
1500 'RepositoryField', cascade="all, delete, delete-orphan")
1492 logs = relationship('UserLog')
1501 logs = relationship('UserLog')
1493 comments = relationship(
1502 comments = relationship(
1494 'ChangesetComment', cascade="all, delete, delete-orphan")
1503 'ChangesetComment', cascade="all, delete, delete-orphan")
1495 pull_requests_source = relationship(
1504 pull_requests_source = relationship(
1496 'PullRequest',
1505 'PullRequest',
1497 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1506 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1498 cascade="all, delete, delete-orphan")
1507 cascade="all, delete, delete-orphan")
1499 pull_requests_target = relationship(
1508 pull_requests_target = relationship(
1500 'PullRequest',
1509 'PullRequest',
1501 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1510 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1502 cascade="all, delete, delete-orphan")
1511 cascade="all, delete, delete-orphan")
1503 ui = relationship('RepoRhodeCodeUi', cascade="all")
1512 ui = relationship('RepoRhodeCodeUi', cascade="all")
1504 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1513 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1505 integrations = relationship('Integration',
1514 integrations = relationship('Integration',
1506 cascade="all, delete, delete-orphan")
1515 cascade="all, delete, delete-orphan")
1507
1516
1508 def __unicode__(self):
1517 def __unicode__(self):
1509 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1518 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1510 safe_unicode(self.repo_name))
1519 safe_unicode(self.repo_name))
1511
1520
1512 @hybrid_property
1521 @hybrid_property
1513 def description_safe(self):
1522 def description_safe(self):
1514 from rhodecode.lib import helpers as h
1523 from rhodecode.lib import helpers as h
1515 return h.escape(self.description)
1524 return h.escape(self.description)
1516
1525
1517 @hybrid_property
1526 @hybrid_property
1518 def landing_rev(self):
1527 def landing_rev(self):
1519 # always should return [rev_type, rev]
1528 # always should return [rev_type, rev]
1520 if self._landing_revision:
1529 if self._landing_revision:
1521 _rev_info = self._landing_revision.split(':')
1530 _rev_info = self._landing_revision.split(':')
1522 if len(_rev_info) < 2:
1531 if len(_rev_info) < 2:
1523 _rev_info.insert(0, 'rev')
1532 _rev_info.insert(0, 'rev')
1524 return [_rev_info[0], _rev_info[1]]
1533 return [_rev_info[0], _rev_info[1]]
1525 return [None, None]
1534 return [None, None]
1526
1535
1527 @landing_rev.setter
1536 @landing_rev.setter
1528 def landing_rev(self, val):
1537 def landing_rev(self, val):
1529 if ':' not in val:
1538 if ':' not in val:
1530 raise ValueError('value must be delimited with `:` and consist '
1539 raise ValueError('value must be delimited with `:` and consist '
1531 'of <rev_type>:<rev>, got %s instead' % val)
1540 'of <rev_type>:<rev>, got %s instead' % val)
1532 self._landing_revision = val
1541 self._landing_revision = val
1533
1542
1534 @hybrid_property
1543 @hybrid_property
1535 def locked(self):
1544 def locked(self):
1536 if self._locked:
1545 if self._locked:
1537 user_id, timelocked, reason = self._locked.split(':')
1546 user_id, timelocked, reason = self._locked.split(':')
1538 lock_values = int(user_id), timelocked, reason
1547 lock_values = int(user_id), timelocked, reason
1539 else:
1548 else:
1540 lock_values = [None, None, None]
1549 lock_values = [None, None, None]
1541 return lock_values
1550 return lock_values
1542
1551
1543 @locked.setter
1552 @locked.setter
1544 def locked(self, val):
1553 def locked(self, val):
1545 if val and isinstance(val, (list, tuple)):
1554 if val and isinstance(val, (list, tuple)):
1546 self._locked = ':'.join(map(str, val))
1555 self._locked = ':'.join(map(str, val))
1547 else:
1556 else:
1548 self._locked = None
1557 self._locked = None
1549
1558
1550 @hybrid_property
1559 @hybrid_property
1551 def changeset_cache(self):
1560 def changeset_cache(self):
1552 from rhodecode.lib.vcs.backends.base import EmptyCommit
1561 from rhodecode.lib.vcs.backends.base import EmptyCommit
1553 dummy = EmptyCommit().__json__()
1562 dummy = EmptyCommit().__json__()
1554 if not self._changeset_cache:
1563 if not self._changeset_cache:
1555 return dummy
1564 return dummy
1556 try:
1565 try:
1557 return json.loads(self._changeset_cache)
1566 return json.loads(self._changeset_cache)
1558 except TypeError:
1567 except TypeError:
1559 return dummy
1568 return dummy
1560 except Exception:
1569 except Exception:
1561 log.error(traceback.format_exc())
1570 log.error(traceback.format_exc())
1562 return dummy
1571 return dummy
1563
1572
1564 @changeset_cache.setter
1573 @changeset_cache.setter
1565 def changeset_cache(self, val):
1574 def changeset_cache(self, val):
1566 try:
1575 try:
1567 self._changeset_cache = json.dumps(val)
1576 self._changeset_cache = json.dumps(val)
1568 except Exception:
1577 except Exception:
1569 log.error(traceback.format_exc())
1578 log.error(traceback.format_exc())
1570
1579
1571 @hybrid_property
1580 @hybrid_property
1572 def repo_name(self):
1581 def repo_name(self):
1573 return self._repo_name
1582 return self._repo_name
1574
1583
1575 @repo_name.setter
1584 @repo_name.setter
1576 def repo_name(self, value):
1585 def repo_name(self, value):
1577 self._repo_name = value
1586 self._repo_name = value
1578 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1587 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1579
1588
1580 @classmethod
1589 @classmethod
1581 def normalize_repo_name(cls, repo_name):
1590 def normalize_repo_name(cls, repo_name):
1582 """
1591 """
1583 Normalizes os specific repo_name to the format internally stored inside
1592 Normalizes os specific repo_name to the format internally stored inside
1584 database using URL_SEP
1593 database using URL_SEP
1585
1594
1586 :param cls:
1595 :param cls:
1587 :param repo_name:
1596 :param repo_name:
1588 """
1597 """
1589 return cls.NAME_SEP.join(repo_name.split(os.sep))
1598 return cls.NAME_SEP.join(repo_name.split(os.sep))
1590
1599
1591 @classmethod
1600 @classmethod
1592 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1601 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1593 session = Session()
1602 session = Session()
1594 q = session.query(cls).filter(cls.repo_name == repo_name)
1603 q = session.query(cls).filter(cls.repo_name == repo_name)
1595
1604
1596 if cache:
1605 if cache:
1597 if identity_cache:
1606 if identity_cache:
1598 val = cls.identity_cache(session, 'repo_name', repo_name)
1607 val = cls.identity_cache(session, 'repo_name', repo_name)
1599 if val:
1608 if val:
1600 return val
1609 return val
1601 else:
1610 else:
1602 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1611 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1603 q = q.options(
1612 q = q.options(
1604 FromCache("sql_cache_short", cache_key))
1613 FromCache("sql_cache_short", cache_key))
1605
1614
1606 return q.scalar()
1615 return q.scalar()
1607
1616
1608 @classmethod
1617 @classmethod
1609 def get_by_full_path(cls, repo_full_path):
1618 def get_by_full_path(cls, repo_full_path):
1610 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1619 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1611 repo_name = cls.normalize_repo_name(repo_name)
1620 repo_name = cls.normalize_repo_name(repo_name)
1612 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1621 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1613
1622
1614 @classmethod
1623 @classmethod
1615 def get_repo_forks(cls, repo_id):
1624 def get_repo_forks(cls, repo_id):
1616 return cls.query().filter(Repository.fork_id == repo_id)
1625 return cls.query().filter(Repository.fork_id == repo_id)
1617
1626
1618 @classmethod
1627 @classmethod
1619 def base_path(cls):
1628 def base_path(cls):
1620 """
1629 """
1621 Returns base path when all repos are stored
1630 Returns base path when all repos are stored
1622
1631
1623 :param cls:
1632 :param cls:
1624 """
1633 """
1625 q = Session().query(RhodeCodeUi)\
1634 q = Session().query(RhodeCodeUi)\
1626 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1635 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1627 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1636 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1628 return q.one().ui_value
1637 return q.one().ui_value
1629
1638
1630 @classmethod
1639 @classmethod
1631 def is_valid(cls, repo_name):
1640 def is_valid(cls, repo_name):
1632 """
1641 """
1633 returns True if given repo name is a valid filesystem repository
1642 returns True if given repo name is a valid filesystem repository
1634
1643
1635 :param cls:
1644 :param cls:
1636 :param repo_name:
1645 :param repo_name:
1637 """
1646 """
1638 from rhodecode.lib.utils import is_valid_repo
1647 from rhodecode.lib.utils import is_valid_repo
1639
1648
1640 return is_valid_repo(repo_name, cls.base_path())
1649 return is_valid_repo(repo_name, cls.base_path())
1641
1650
1642 @classmethod
1651 @classmethod
1643 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1652 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1644 case_insensitive=True):
1653 case_insensitive=True):
1645 q = Repository.query()
1654 q = Repository.query()
1646
1655
1647 if not isinstance(user_id, Optional):
1656 if not isinstance(user_id, Optional):
1648 q = q.filter(Repository.user_id == user_id)
1657 q = q.filter(Repository.user_id == user_id)
1649
1658
1650 if not isinstance(group_id, Optional):
1659 if not isinstance(group_id, Optional):
1651 q = q.filter(Repository.group_id == group_id)
1660 q = q.filter(Repository.group_id == group_id)
1652
1661
1653 if case_insensitive:
1662 if case_insensitive:
1654 q = q.order_by(func.lower(Repository.repo_name))
1663 q = q.order_by(func.lower(Repository.repo_name))
1655 else:
1664 else:
1656 q = q.order_by(Repository.repo_name)
1665 q = q.order_by(Repository.repo_name)
1657 return q.all()
1666 return q.all()
1658
1667
1659 @property
1668 @property
1660 def forks(self):
1669 def forks(self):
1661 """
1670 """
1662 Return forks of this repo
1671 Return forks of this repo
1663 """
1672 """
1664 return Repository.get_repo_forks(self.repo_id)
1673 return Repository.get_repo_forks(self.repo_id)
1665
1674
1666 @property
1675 @property
1667 def parent(self):
1676 def parent(self):
1668 """
1677 """
1669 Returns fork parent
1678 Returns fork parent
1670 """
1679 """
1671 return self.fork
1680 return self.fork
1672
1681
1673 @property
1682 @property
1674 def just_name(self):
1683 def just_name(self):
1675 return self.repo_name.split(self.NAME_SEP)[-1]
1684 return self.repo_name.split(self.NAME_SEP)[-1]
1676
1685
1677 @property
1686 @property
1678 def groups_with_parents(self):
1687 def groups_with_parents(self):
1679 groups = []
1688 groups = []
1680 if self.group is None:
1689 if self.group is None:
1681 return groups
1690 return groups
1682
1691
1683 cur_gr = self.group
1692 cur_gr = self.group
1684 groups.insert(0, cur_gr)
1693 groups.insert(0, cur_gr)
1685 while 1:
1694 while 1:
1686 gr = getattr(cur_gr, 'parent_group', None)
1695 gr = getattr(cur_gr, 'parent_group', None)
1687 cur_gr = cur_gr.parent_group
1696 cur_gr = cur_gr.parent_group
1688 if gr is None:
1697 if gr is None:
1689 break
1698 break
1690 groups.insert(0, gr)
1699 groups.insert(0, gr)
1691
1700
1692 return groups
1701 return groups
1693
1702
1694 @property
1703 @property
1695 def groups_and_repo(self):
1704 def groups_and_repo(self):
1696 return self.groups_with_parents, self
1705 return self.groups_with_parents, self
1697
1706
1698 @LazyProperty
1707 @LazyProperty
1699 def repo_path(self):
1708 def repo_path(self):
1700 """
1709 """
1701 Returns base full path for that repository means where it actually
1710 Returns base full path for that repository means where it actually
1702 exists on a filesystem
1711 exists on a filesystem
1703 """
1712 """
1704 q = Session().query(RhodeCodeUi).filter(
1713 q = Session().query(RhodeCodeUi).filter(
1705 RhodeCodeUi.ui_key == self.NAME_SEP)
1714 RhodeCodeUi.ui_key == self.NAME_SEP)
1706 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1715 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1707 return q.one().ui_value
1716 return q.one().ui_value
1708
1717
1709 @property
1718 @property
1710 def repo_full_path(self):
1719 def repo_full_path(self):
1711 p = [self.repo_path]
1720 p = [self.repo_path]
1712 # we need to split the name by / since this is how we store the
1721 # we need to split the name by / since this is how we store the
1713 # names in the database, but that eventually needs to be converted
1722 # names in the database, but that eventually needs to be converted
1714 # into a valid system path
1723 # into a valid system path
1715 p += self.repo_name.split(self.NAME_SEP)
1724 p += self.repo_name.split(self.NAME_SEP)
1716 return os.path.join(*map(safe_unicode, p))
1725 return os.path.join(*map(safe_unicode, p))
1717
1726
1718 @property
1727 @property
1719 def cache_keys(self):
1728 def cache_keys(self):
1720 """
1729 """
1721 Returns associated cache keys for that repo
1730 Returns associated cache keys for that repo
1722 """
1731 """
1723 return CacheKey.query()\
1732 return CacheKey.query()\
1724 .filter(CacheKey.cache_args == self.repo_name)\
1733 .filter(CacheKey.cache_args == self.repo_name)\
1725 .order_by(CacheKey.cache_key)\
1734 .order_by(CacheKey.cache_key)\
1726 .all()
1735 .all()
1727
1736
1728 def get_new_name(self, repo_name):
1737 def get_new_name(self, repo_name):
1729 """
1738 """
1730 returns new full repository name based on assigned group and new new
1739 returns new full repository name based on assigned group and new new
1731
1740
1732 :param group_name:
1741 :param group_name:
1733 """
1742 """
1734 path_prefix = self.group.full_path_splitted if self.group else []
1743 path_prefix = self.group.full_path_splitted if self.group else []
1735 return self.NAME_SEP.join(path_prefix + [repo_name])
1744 return self.NAME_SEP.join(path_prefix + [repo_name])
1736
1745
1737 @property
1746 @property
1738 def _config(self):
1747 def _config(self):
1739 """
1748 """
1740 Returns db based config object.
1749 Returns db based config object.
1741 """
1750 """
1742 from rhodecode.lib.utils import make_db_config
1751 from rhodecode.lib.utils import make_db_config
1743 return make_db_config(clear_session=False, repo=self)
1752 return make_db_config(clear_session=False, repo=self)
1744
1753
1745 def permissions(self, with_admins=True, with_owner=True):
1754 def permissions(self, with_admins=True, with_owner=True):
1746 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1755 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1747 q = q.options(joinedload(UserRepoToPerm.repository),
1756 q = q.options(joinedload(UserRepoToPerm.repository),
1748 joinedload(UserRepoToPerm.user),
1757 joinedload(UserRepoToPerm.user),
1749 joinedload(UserRepoToPerm.permission),)
1758 joinedload(UserRepoToPerm.permission),)
1750
1759
1751 # get owners and admins and permissions. We do a trick of re-writing
1760 # get owners and admins and permissions. We do a trick of re-writing
1752 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1761 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1753 # has a global reference and changing one object propagates to all
1762 # has a global reference and changing one object propagates to all
1754 # others. This means if admin is also an owner admin_row that change
1763 # others. This means if admin is also an owner admin_row that change
1755 # would propagate to both objects
1764 # would propagate to both objects
1756 perm_rows = []
1765 perm_rows = []
1757 for _usr in q.all():
1766 for _usr in q.all():
1758 usr = AttributeDict(_usr.user.get_dict())
1767 usr = AttributeDict(_usr.user.get_dict())
1759 usr.permission = _usr.permission.permission_name
1768 usr.permission = _usr.permission.permission_name
1760 perm_rows.append(usr)
1769 perm_rows.append(usr)
1761
1770
1762 # filter the perm rows by 'default' first and then sort them by
1771 # filter the perm rows by 'default' first and then sort them by
1763 # admin,write,read,none permissions sorted again alphabetically in
1772 # admin,write,read,none permissions sorted again alphabetically in
1764 # each group
1773 # each group
1765 perm_rows = sorted(perm_rows, key=display_sort)
1774 perm_rows = sorted(perm_rows, key=display_sort)
1766
1775
1767 _admin_perm = 'repository.admin'
1776 _admin_perm = 'repository.admin'
1768 owner_row = []
1777 owner_row = []
1769 if with_owner:
1778 if with_owner:
1770 usr = AttributeDict(self.user.get_dict())
1779 usr = AttributeDict(self.user.get_dict())
1771 usr.owner_row = True
1780 usr.owner_row = True
1772 usr.permission = _admin_perm
1781 usr.permission = _admin_perm
1773 owner_row.append(usr)
1782 owner_row.append(usr)
1774
1783
1775 super_admin_rows = []
1784 super_admin_rows = []
1776 if with_admins:
1785 if with_admins:
1777 for usr in User.get_all_super_admins():
1786 for usr in User.get_all_super_admins():
1778 # if this admin is also owner, don't double the record
1787 # if this admin is also owner, don't double the record
1779 if usr.user_id == owner_row[0].user_id:
1788 if usr.user_id == owner_row[0].user_id:
1780 owner_row[0].admin_row = True
1789 owner_row[0].admin_row = True
1781 else:
1790 else:
1782 usr = AttributeDict(usr.get_dict())
1791 usr = AttributeDict(usr.get_dict())
1783 usr.admin_row = True
1792 usr.admin_row = True
1784 usr.permission = _admin_perm
1793 usr.permission = _admin_perm
1785 super_admin_rows.append(usr)
1794 super_admin_rows.append(usr)
1786
1795
1787 return super_admin_rows + owner_row + perm_rows
1796 return super_admin_rows + owner_row + perm_rows
1788
1797
1789 def permission_user_groups(self):
1798 def permission_user_groups(self):
1790 q = UserGroupRepoToPerm.query().filter(
1799 q = UserGroupRepoToPerm.query().filter(
1791 UserGroupRepoToPerm.repository == self)
1800 UserGroupRepoToPerm.repository == self)
1792 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1801 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1793 joinedload(UserGroupRepoToPerm.users_group),
1802 joinedload(UserGroupRepoToPerm.users_group),
1794 joinedload(UserGroupRepoToPerm.permission),)
1803 joinedload(UserGroupRepoToPerm.permission),)
1795
1804
1796 perm_rows = []
1805 perm_rows = []
1797 for _user_group in q.all():
1806 for _user_group in q.all():
1798 usr = AttributeDict(_user_group.users_group.get_dict())
1807 usr = AttributeDict(_user_group.users_group.get_dict())
1799 usr.permission = _user_group.permission.permission_name
1808 usr.permission = _user_group.permission.permission_name
1800 perm_rows.append(usr)
1809 perm_rows.append(usr)
1801
1810
1802 return perm_rows
1811 return perm_rows
1803
1812
1804 def get_api_data(self, include_secrets=False):
1813 def get_api_data(self, include_secrets=False):
1805 """
1814 """
1806 Common function for generating repo api data
1815 Common function for generating repo api data
1807
1816
1808 :param include_secrets: See :meth:`User.get_api_data`.
1817 :param include_secrets: See :meth:`User.get_api_data`.
1809
1818
1810 """
1819 """
1811 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1820 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1812 # move this methods on models level.
1821 # move this methods on models level.
1813 from rhodecode.model.settings import SettingsModel
1822 from rhodecode.model.settings import SettingsModel
1814 from rhodecode.model.repo import RepoModel
1823 from rhodecode.model.repo import RepoModel
1815
1824
1816 repo = self
1825 repo = self
1817 _user_id, _time, _reason = self.locked
1826 _user_id, _time, _reason = self.locked
1818
1827
1819 data = {
1828 data = {
1820 'repo_id': repo.repo_id,
1829 'repo_id': repo.repo_id,
1821 'repo_name': repo.repo_name,
1830 'repo_name': repo.repo_name,
1822 'repo_type': repo.repo_type,
1831 'repo_type': repo.repo_type,
1823 'clone_uri': repo.clone_uri or '',
1832 'clone_uri': repo.clone_uri or '',
1824 'url': RepoModel().get_url(self),
1833 'url': RepoModel().get_url(self),
1825 'private': repo.private,
1834 'private': repo.private,
1826 'created_on': repo.created_on,
1835 'created_on': repo.created_on,
1827 'description': repo.description_safe,
1836 'description': repo.description_safe,
1828 'landing_rev': repo.landing_rev,
1837 'landing_rev': repo.landing_rev,
1829 'owner': repo.user.username,
1838 'owner': repo.user.username,
1830 'fork_of': repo.fork.repo_name if repo.fork else None,
1839 'fork_of': repo.fork.repo_name if repo.fork else None,
1831 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1840 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1832 'enable_statistics': repo.enable_statistics,
1841 'enable_statistics': repo.enable_statistics,
1833 'enable_locking': repo.enable_locking,
1842 'enable_locking': repo.enable_locking,
1834 'enable_downloads': repo.enable_downloads,
1843 'enable_downloads': repo.enable_downloads,
1835 'last_changeset': repo.changeset_cache,
1844 'last_changeset': repo.changeset_cache,
1836 'locked_by': User.get(_user_id).get_api_data(
1845 'locked_by': User.get(_user_id).get_api_data(
1837 include_secrets=include_secrets) if _user_id else None,
1846 include_secrets=include_secrets) if _user_id else None,
1838 'locked_date': time_to_datetime(_time) if _time else None,
1847 'locked_date': time_to_datetime(_time) if _time else None,
1839 'lock_reason': _reason if _reason else None,
1848 'lock_reason': _reason if _reason else None,
1840 }
1849 }
1841
1850
1842 # TODO: mikhail: should be per-repo settings here
1851 # TODO: mikhail: should be per-repo settings here
1843 rc_config = SettingsModel().get_all_settings()
1852 rc_config = SettingsModel().get_all_settings()
1844 repository_fields = str2bool(
1853 repository_fields = str2bool(
1845 rc_config.get('rhodecode_repository_fields'))
1854 rc_config.get('rhodecode_repository_fields'))
1846 if repository_fields:
1855 if repository_fields:
1847 for f in self.extra_fields:
1856 for f in self.extra_fields:
1848 data[f.field_key_prefixed] = f.field_value
1857 data[f.field_key_prefixed] = f.field_value
1849
1858
1850 return data
1859 return data
1851
1860
1852 @classmethod
1861 @classmethod
1853 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1862 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1854 if not lock_time:
1863 if not lock_time:
1855 lock_time = time.time()
1864 lock_time = time.time()
1856 if not lock_reason:
1865 if not lock_reason:
1857 lock_reason = cls.LOCK_AUTOMATIC
1866 lock_reason = cls.LOCK_AUTOMATIC
1858 repo.locked = [user_id, lock_time, lock_reason]
1867 repo.locked = [user_id, lock_time, lock_reason]
1859 Session().add(repo)
1868 Session().add(repo)
1860 Session().commit()
1869 Session().commit()
1861
1870
1862 @classmethod
1871 @classmethod
1863 def unlock(cls, repo):
1872 def unlock(cls, repo):
1864 repo.locked = None
1873 repo.locked = None
1865 Session().add(repo)
1874 Session().add(repo)
1866 Session().commit()
1875 Session().commit()
1867
1876
1868 @classmethod
1877 @classmethod
1869 def getlock(cls, repo):
1878 def getlock(cls, repo):
1870 return repo.locked
1879 return repo.locked
1871
1880
1872 def is_user_lock(self, user_id):
1881 def is_user_lock(self, user_id):
1873 if self.lock[0]:
1882 if self.lock[0]:
1874 lock_user_id = safe_int(self.lock[0])
1883 lock_user_id = safe_int(self.lock[0])
1875 user_id = safe_int(user_id)
1884 user_id = safe_int(user_id)
1876 # both are ints, and they are equal
1885 # both are ints, and they are equal
1877 return all([lock_user_id, user_id]) and lock_user_id == user_id
1886 return all([lock_user_id, user_id]) and lock_user_id == user_id
1878
1887
1879 return False
1888 return False
1880
1889
1881 def get_locking_state(self, action, user_id, only_when_enabled=True):
1890 def get_locking_state(self, action, user_id, only_when_enabled=True):
1882 """
1891 """
1883 Checks locking on this repository, if locking is enabled and lock is
1892 Checks locking on this repository, if locking is enabled and lock is
1884 present returns a tuple of make_lock, locked, locked_by.
1893 present returns a tuple of make_lock, locked, locked_by.
1885 make_lock can have 3 states None (do nothing) True, make lock
1894 make_lock can have 3 states None (do nothing) True, make lock
1886 False release lock, This value is later propagated to hooks, which
1895 False release lock, This value is later propagated to hooks, which
1887 do the locking. Think about this as signals passed to hooks what to do.
1896 do the locking. Think about this as signals passed to hooks what to do.
1888
1897
1889 """
1898 """
1890 # TODO: johbo: This is part of the business logic and should be moved
1899 # TODO: johbo: This is part of the business logic and should be moved
1891 # into the RepositoryModel.
1900 # into the RepositoryModel.
1892
1901
1893 if action not in ('push', 'pull'):
1902 if action not in ('push', 'pull'):
1894 raise ValueError("Invalid action value: %s" % repr(action))
1903 raise ValueError("Invalid action value: %s" % repr(action))
1895
1904
1896 # defines if locked error should be thrown to user
1905 # defines if locked error should be thrown to user
1897 currently_locked = False
1906 currently_locked = False
1898 # defines if new lock should be made, tri-state
1907 # defines if new lock should be made, tri-state
1899 make_lock = None
1908 make_lock = None
1900 repo = self
1909 repo = self
1901 user = User.get(user_id)
1910 user = User.get(user_id)
1902
1911
1903 lock_info = repo.locked
1912 lock_info = repo.locked
1904
1913
1905 if repo and (repo.enable_locking or not only_when_enabled):
1914 if repo and (repo.enable_locking or not only_when_enabled):
1906 if action == 'push':
1915 if action == 'push':
1907 # check if it's already locked !, if it is compare users
1916 # check if it's already locked !, if it is compare users
1908 locked_by_user_id = lock_info[0]
1917 locked_by_user_id = lock_info[0]
1909 if user.user_id == locked_by_user_id:
1918 if user.user_id == locked_by_user_id:
1910 log.debug(
1919 log.debug(
1911 'Got `push` action from user %s, now unlocking', user)
1920 'Got `push` action from user %s, now unlocking', user)
1912 # unlock if we have push from user who locked
1921 # unlock if we have push from user who locked
1913 make_lock = False
1922 make_lock = False
1914 else:
1923 else:
1915 # we're not the same user who locked, ban with
1924 # we're not the same user who locked, ban with
1916 # code defined in settings (default is 423 HTTP Locked) !
1925 # code defined in settings (default is 423 HTTP Locked) !
1917 log.debug('Repo %s is currently locked by %s', repo, user)
1926 log.debug('Repo %s is currently locked by %s', repo, user)
1918 currently_locked = True
1927 currently_locked = True
1919 elif action == 'pull':
1928 elif action == 'pull':
1920 # [0] user [1] date
1929 # [0] user [1] date
1921 if lock_info[0] and lock_info[1]:
1930 if lock_info[0] and lock_info[1]:
1922 log.debug('Repo %s is currently locked by %s', repo, user)
1931 log.debug('Repo %s is currently locked by %s', repo, user)
1923 currently_locked = True
1932 currently_locked = True
1924 else:
1933 else:
1925 log.debug('Setting lock on repo %s by %s', repo, user)
1934 log.debug('Setting lock on repo %s by %s', repo, user)
1926 make_lock = True
1935 make_lock = True
1927
1936
1928 else:
1937 else:
1929 log.debug('Repository %s do not have locking enabled', repo)
1938 log.debug('Repository %s do not have locking enabled', repo)
1930
1939
1931 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1940 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1932 make_lock, currently_locked, lock_info)
1941 make_lock, currently_locked, lock_info)
1933
1942
1934 from rhodecode.lib.auth import HasRepoPermissionAny
1943 from rhodecode.lib.auth import HasRepoPermissionAny
1935 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1944 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1936 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1945 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1937 # if we don't have at least write permission we cannot make a lock
1946 # if we don't have at least write permission we cannot make a lock
1938 log.debug('lock state reset back to FALSE due to lack '
1947 log.debug('lock state reset back to FALSE due to lack '
1939 'of at least read permission')
1948 'of at least read permission')
1940 make_lock = False
1949 make_lock = False
1941
1950
1942 return make_lock, currently_locked, lock_info
1951 return make_lock, currently_locked, lock_info
1943
1952
1944 @property
1953 @property
1945 def last_db_change(self):
1954 def last_db_change(self):
1946 return self.updated_on
1955 return self.updated_on
1947
1956
1948 @property
1957 @property
1949 def clone_uri_hidden(self):
1958 def clone_uri_hidden(self):
1950 clone_uri = self.clone_uri
1959 clone_uri = self.clone_uri
1951 if clone_uri:
1960 if clone_uri:
1952 import urlobject
1961 import urlobject
1953 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1962 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1954 if url_obj.password:
1963 if url_obj.password:
1955 clone_uri = url_obj.with_password('*****')
1964 clone_uri = url_obj.with_password('*****')
1956 return clone_uri
1965 return clone_uri
1957
1966
1958 def clone_url(self, **override):
1967 def clone_url(self, **override):
1959 from rhodecode.model.settings import SettingsModel
1968 from rhodecode.model.settings import SettingsModel
1960
1969
1961 uri_tmpl = None
1970 uri_tmpl = None
1962 if 'with_id' in override:
1971 if 'with_id' in override:
1963 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1972 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1964 del override['with_id']
1973 del override['with_id']
1965
1974
1966 if 'uri_tmpl' in override:
1975 if 'uri_tmpl' in override:
1967 uri_tmpl = override['uri_tmpl']
1976 uri_tmpl = override['uri_tmpl']
1968 del override['uri_tmpl']
1977 del override['uri_tmpl']
1969
1978
1970 # we didn't override our tmpl from **overrides
1979 # we didn't override our tmpl from **overrides
1971 if not uri_tmpl:
1980 if not uri_tmpl:
1972 rc_config = SettingsModel().get_all_settings(cache=True)
1981 rc_config = SettingsModel().get_all_settings(cache=True)
1973 uri_tmpl = rc_config.get(
1982 uri_tmpl = rc_config.get(
1974 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
1983 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
1975
1984
1976 request = get_current_request()
1985 request = get_current_request()
1977 return get_clone_url(request=request,
1986 return get_clone_url(request=request,
1978 uri_tmpl=uri_tmpl,
1987 uri_tmpl=uri_tmpl,
1979 repo_name=self.repo_name,
1988 repo_name=self.repo_name,
1980 repo_id=self.repo_id, **override)
1989 repo_id=self.repo_id, **override)
1981
1990
1982 def set_state(self, state):
1991 def set_state(self, state):
1983 self.repo_state = state
1992 self.repo_state = state
1984 Session().add(self)
1993 Session().add(self)
1985 #==========================================================================
1994 #==========================================================================
1986 # SCM PROPERTIES
1995 # SCM PROPERTIES
1987 #==========================================================================
1996 #==========================================================================
1988
1997
1989 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1998 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1990 return get_commit_safe(
1999 return get_commit_safe(
1991 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2000 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1992
2001
1993 def get_changeset(self, rev=None, pre_load=None):
2002 def get_changeset(self, rev=None, pre_load=None):
1994 warnings.warn("Use get_commit", DeprecationWarning)
2003 warnings.warn("Use get_commit", DeprecationWarning)
1995 commit_id = None
2004 commit_id = None
1996 commit_idx = None
2005 commit_idx = None
1997 if isinstance(rev, basestring):
2006 if isinstance(rev, basestring):
1998 commit_id = rev
2007 commit_id = rev
1999 else:
2008 else:
2000 commit_idx = rev
2009 commit_idx = rev
2001 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2010 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2002 pre_load=pre_load)
2011 pre_load=pre_load)
2003
2012
2004 def get_landing_commit(self):
2013 def get_landing_commit(self):
2005 """
2014 """
2006 Returns landing commit, or if that doesn't exist returns the tip
2015 Returns landing commit, or if that doesn't exist returns the tip
2007 """
2016 """
2008 _rev_type, _rev = self.landing_rev
2017 _rev_type, _rev = self.landing_rev
2009 commit = self.get_commit(_rev)
2018 commit = self.get_commit(_rev)
2010 if isinstance(commit, EmptyCommit):
2019 if isinstance(commit, EmptyCommit):
2011 return self.get_commit()
2020 return self.get_commit()
2012 return commit
2021 return commit
2013
2022
2014 def update_commit_cache(self, cs_cache=None, config=None):
2023 def update_commit_cache(self, cs_cache=None, config=None):
2015 """
2024 """
2016 Update cache of last changeset for repository, keys should be::
2025 Update cache of last changeset for repository, keys should be::
2017
2026
2018 short_id
2027 short_id
2019 raw_id
2028 raw_id
2020 revision
2029 revision
2021 parents
2030 parents
2022 message
2031 message
2023 date
2032 date
2024 author
2033 author
2025
2034
2026 :param cs_cache:
2035 :param cs_cache:
2027 """
2036 """
2028 from rhodecode.lib.vcs.backends.base import BaseChangeset
2037 from rhodecode.lib.vcs.backends.base import BaseChangeset
2029 if cs_cache is None:
2038 if cs_cache is None:
2030 # use no-cache version here
2039 # use no-cache version here
2031 scm_repo = self.scm_instance(cache=False, config=config)
2040 scm_repo = self.scm_instance(cache=False, config=config)
2032 if scm_repo:
2041 if scm_repo:
2033 cs_cache = scm_repo.get_commit(
2042 cs_cache = scm_repo.get_commit(
2034 pre_load=["author", "date", "message", "parents"])
2043 pre_load=["author", "date", "message", "parents"])
2035 else:
2044 else:
2036 cs_cache = EmptyCommit()
2045 cs_cache = EmptyCommit()
2037
2046
2038 if isinstance(cs_cache, BaseChangeset):
2047 if isinstance(cs_cache, BaseChangeset):
2039 cs_cache = cs_cache.__json__()
2048 cs_cache = cs_cache.__json__()
2040
2049
2041 def is_outdated(new_cs_cache):
2050 def is_outdated(new_cs_cache):
2042 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2051 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2043 new_cs_cache['revision'] != self.changeset_cache['revision']):
2052 new_cs_cache['revision'] != self.changeset_cache['revision']):
2044 return True
2053 return True
2045 return False
2054 return False
2046
2055
2047 # check if we have maybe already latest cached revision
2056 # check if we have maybe already latest cached revision
2048 if is_outdated(cs_cache) or not self.changeset_cache:
2057 if is_outdated(cs_cache) or not self.changeset_cache:
2049 _default = datetime.datetime.fromtimestamp(0)
2058 _default = datetime.datetime.fromtimestamp(0)
2050 last_change = cs_cache.get('date') or _default
2059 last_change = cs_cache.get('date') or _default
2051 log.debug('updated repo %s with new cs cache %s',
2060 log.debug('updated repo %s with new cs cache %s',
2052 self.repo_name, cs_cache)
2061 self.repo_name, cs_cache)
2053 self.updated_on = last_change
2062 self.updated_on = last_change
2054 self.changeset_cache = cs_cache
2063 self.changeset_cache = cs_cache
2055 Session().add(self)
2064 Session().add(self)
2056 Session().commit()
2065 Session().commit()
2057 else:
2066 else:
2058 log.debug('Skipping update_commit_cache for repo:`%s` '
2067 log.debug('Skipping update_commit_cache for repo:`%s` '
2059 'commit already with latest changes', self.repo_name)
2068 'commit already with latest changes', self.repo_name)
2060
2069
2061 @property
2070 @property
2062 def tip(self):
2071 def tip(self):
2063 return self.get_commit('tip')
2072 return self.get_commit('tip')
2064
2073
2065 @property
2074 @property
2066 def author(self):
2075 def author(self):
2067 return self.tip.author
2076 return self.tip.author
2068
2077
2069 @property
2078 @property
2070 def last_change(self):
2079 def last_change(self):
2071 return self.scm_instance().last_change
2080 return self.scm_instance().last_change
2072
2081
2073 def get_comments(self, revisions=None):
2082 def get_comments(self, revisions=None):
2074 """
2083 """
2075 Returns comments for this repository grouped by revisions
2084 Returns comments for this repository grouped by revisions
2076
2085
2077 :param revisions: filter query by revisions only
2086 :param revisions: filter query by revisions only
2078 """
2087 """
2079 cmts = ChangesetComment.query()\
2088 cmts = ChangesetComment.query()\
2080 .filter(ChangesetComment.repo == self)
2089 .filter(ChangesetComment.repo == self)
2081 if revisions:
2090 if revisions:
2082 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2091 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2083 grouped = collections.defaultdict(list)
2092 grouped = collections.defaultdict(list)
2084 for cmt in cmts.all():
2093 for cmt in cmts.all():
2085 grouped[cmt.revision].append(cmt)
2094 grouped[cmt.revision].append(cmt)
2086 return grouped
2095 return grouped
2087
2096
2088 def statuses(self, revisions=None):
2097 def statuses(self, revisions=None):
2089 """
2098 """
2090 Returns statuses for this repository
2099 Returns statuses for this repository
2091
2100
2092 :param revisions: list of revisions to get statuses for
2101 :param revisions: list of revisions to get statuses for
2093 """
2102 """
2094 statuses = ChangesetStatus.query()\
2103 statuses = ChangesetStatus.query()\
2095 .filter(ChangesetStatus.repo == self)\
2104 .filter(ChangesetStatus.repo == self)\
2096 .filter(ChangesetStatus.version == 0)
2105 .filter(ChangesetStatus.version == 0)
2097
2106
2098 if revisions:
2107 if revisions:
2099 # Try doing the filtering in chunks to avoid hitting limits
2108 # Try doing the filtering in chunks to avoid hitting limits
2100 size = 500
2109 size = 500
2101 status_results = []
2110 status_results = []
2102 for chunk in xrange(0, len(revisions), size):
2111 for chunk in xrange(0, len(revisions), size):
2103 status_results += statuses.filter(
2112 status_results += statuses.filter(
2104 ChangesetStatus.revision.in_(
2113 ChangesetStatus.revision.in_(
2105 revisions[chunk: chunk+size])
2114 revisions[chunk: chunk+size])
2106 ).all()
2115 ).all()
2107 else:
2116 else:
2108 status_results = statuses.all()
2117 status_results = statuses.all()
2109
2118
2110 grouped = {}
2119 grouped = {}
2111
2120
2112 # maybe we have open new pullrequest without a status?
2121 # maybe we have open new pullrequest without a status?
2113 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2122 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2114 status_lbl = ChangesetStatus.get_status_lbl(stat)
2123 status_lbl = ChangesetStatus.get_status_lbl(stat)
2115 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2124 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2116 for rev in pr.revisions:
2125 for rev in pr.revisions:
2117 pr_id = pr.pull_request_id
2126 pr_id = pr.pull_request_id
2118 pr_repo = pr.target_repo.repo_name
2127 pr_repo = pr.target_repo.repo_name
2119 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2128 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2120
2129
2121 for stat in status_results:
2130 for stat in status_results:
2122 pr_id = pr_repo = None
2131 pr_id = pr_repo = None
2123 if stat.pull_request:
2132 if stat.pull_request:
2124 pr_id = stat.pull_request.pull_request_id
2133 pr_id = stat.pull_request.pull_request_id
2125 pr_repo = stat.pull_request.target_repo.repo_name
2134 pr_repo = stat.pull_request.target_repo.repo_name
2126 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2135 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2127 pr_id, pr_repo]
2136 pr_id, pr_repo]
2128 return grouped
2137 return grouped
2129
2138
2130 # ==========================================================================
2139 # ==========================================================================
2131 # SCM CACHE INSTANCE
2140 # SCM CACHE INSTANCE
2132 # ==========================================================================
2141 # ==========================================================================
2133
2142
2134 def scm_instance(self, **kwargs):
2143 def scm_instance(self, **kwargs):
2135 import rhodecode
2144 import rhodecode
2136
2145
2137 # Passing a config will not hit the cache currently only used
2146 # Passing a config will not hit the cache currently only used
2138 # for repo2dbmapper
2147 # for repo2dbmapper
2139 config = kwargs.pop('config', None)
2148 config = kwargs.pop('config', None)
2140 cache = kwargs.pop('cache', None)
2149 cache = kwargs.pop('cache', None)
2141 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2150 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2142 # if cache is NOT defined use default global, else we have a full
2151 # if cache is NOT defined use default global, else we have a full
2143 # control over cache behaviour
2152 # control over cache behaviour
2144 if cache is None and full_cache and not config:
2153 if cache is None and full_cache and not config:
2145 return self._get_instance_cached()
2154 return self._get_instance_cached()
2146 return self._get_instance(cache=bool(cache), config=config)
2155 return self._get_instance(cache=bool(cache), config=config)
2147
2156
2148 def _get_instance_cached(self):
2157 def _get_instance_cached(self):
2149 @cache_region('long_term')
2158 @cache_region('long_term')
2150 def _get_repo(cache_key):
2159 def _get_repo(cache_key):
2151 return self._get_instance()
2160 return self._get_instance()
2152
2161
2153 invalidator_context = CacheKey.repo_context_cache(
2162 invalidator_context = CacheKey.repo_context_cache(
2154 _get_repo, self.repo_name, None, thread_scoped=True)
2163 _get_repo, self.repo_name, None, thread_scoped=True)
2155
2164
2156 with invalidator_context as context:
2165 with invalidator_context as context:
2157 context.invalidate()
2166 context.invalidate()
2158 repo = context.compute()
2167 repo = context.compute()
2159
2168
2160 return repo
2169 return repo
2161
2170
2162 def _get_instance(self, cache=True, config=None):
2171 def _get_instance(self, cache=True, config=None):
2163 config = config or self._config
2172 config = config or self._config
2164 custom_wire = {
2173 custom_wire = {
2165 'cache': cache # controls the vcs.remote cache
2174 'cache': cache # controls the vcs.remote cache
2166 }
2175 }
2167 repo = get_vcs_instance(
2176 repo = get_vcs_instance(
2168 repo_path=safe_str(self.repo_full_path),
2177 repo_path=safe_str(self.repo_full_path),
2169 config=config,
2178 config=config,
2170 with_wire=custom_wire,
2179 with_wire=custom_wire,
2171 create=False,
2180 create=False,
2172 _vcs_alias=self.repo_type)
2181 _vcs_alias=self.repo_type)
2173
2182
2174 return repo
2183 return repo
2175
2184
2176 def __json__(self):
2185 def __json__(self):
2177 return {'landing_rev': self.landing_rev}
2186 return {'landing_rev': self.landing_rev}
2178
2187
2179 def get_dict(self):
2188 def get_dict(self):
2180
2189
2181 # Since we transformed `repo_name` to a hybrid property, we need to
2190 # Since we transformed `repo_name` to a hybrid property, we need to
2182 # keep compatibility with the code which uses `repo_name` field.
2191 # keep compatibility with the code which uses `repo_name` field.
2183
2192
2184 result = super(Repository, self).get_dict()
2193 result = super(Repository, self).get_dict()
2185 result['repo_name'] = result.pop('_repo_name', None)
2194 result['repo_name'] = result.pop('_repo_name', None)
2186 return result
2195 return result
2187
2196
2188
2197
2189 class RepoGroup(Base, BaseModel):
2198 class RepoGroup(Base, BaseModel):
2190 __tablename__ = 'groups'
2199 __tablename__ = 'groups'
2191 __table_args__ = (
2200 __table_args__ = (
2192 UniqueConstraint('group_name', 'group_parent_id'),
2201 UniqueConstraint('group_name', 'group_parent_id'),
2193 CheckConstraint('group_id != group_parent_id'),
2202 CheckConstraint('group_id != group_parent_id'),
2194 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2195 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2196 )
2205 )
2197 __mapper_args__ = {'order_by': 'group_name'}
2206 __mapper_args__ = {'order_by': 'group_name'}
2198
2207
2199 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2208 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2200
2209
2201 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2210 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2202 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2211 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2203 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2212 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2204 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2213 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2205 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2214 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2206 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2215 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2207 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2216 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2208 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2217 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2209 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2218 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2210
2219
2211 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2220 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2212 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2221 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2213 parent_group = relationship('RepoGroup', remote_side=group_id)
2222 parent_group = relationship('RepoGroup', remote_side=group_id)
2214 user = relationship('User')
2223 user = relationship('User')
2215 integrations = relationship('Integration',
2224 integrations = relationship('Integration',
2216 cascade="all, delete, delete-orphan")
2225 cascade="all, delete, delete-orphan")
2217
2226
2218 def __init__(self, group_name='', parent_group=None):
2227 def __init__(self, group_name='', parent_group=None):
2219 self.group_name = group_name
2228 self.group_name = group_name
2220 self.parent_group = parent_group
2229 self.parent_group = parent_group
2221
2230
2222 def __unicode__(self):
2231 def __unicode__(self):
2223 return u"<%s('id:%s:%s')>" % (
2232 return u"<%s('id:%s:%s')>" % (
2224 self.__class__.__name__, self.group_id, self.group_name)
2233 self.__class__.__name__, self.group_id, self.group_name)
2225
2234
2226 @hybrid_property
2235 @hybrid_property
2227 def description_safe(self):
2236 def description_safe(self):
2228 from rhodecode.lib import helpers as h
2237 from rhodecode.lib import helpers as h
2229 return h.escape(self.group_description)
2238 return h.escape(self.group_description)
2230
2239
2231 @classmethod
2240 @classmethod
2232 def _generate_choice(cls, repo_group):
2241 def _generate_choice(cls, repo_group):
2233 from webhelpers.html import literal as _literal
2242 from webhelpers.html import literal as _literal
2234 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2243 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2235 return repo_group.group_id, _name(repo_group.full_path_splitted)
2244 return repo_group.group_id, _name(repo_group.full_path_splitted)
2236
2245
2237 @classmethod
2246 @classmethod
2238 def groups_choices(cls, groups=None, show_empty_group=True):
2247 def groups_choices(cls, groups=None, show_empty_group=True):
2239 if not groups:
2248 if not groups:
2240 groups = cls.query().all()
2249 groups = cls.query().all()
2241
2250
2242 repo_groups = []
2251 repo_groups = []
2243 if show_empty_group:
2252 if show_empty_group:
2244 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2253 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2245
2254
2246 repo_groups.extend([cls._generate_choice(x) for x in groups])
2255 repo_groups.extend([cls._generate_choice(x) for x in groups])
2247
2256
2248 repo_groups = sorted(
2257 repo_groups = sorted(
2249 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2258 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2250 return repo_groups
2259 return repo_groups
2251
2260
2252 @classmethod
2261 @classmethod
2253 def url_sep(cls):
2262 def url_sep(cls):
2254 return URL_SEP
2263 return URL_SEP
2255
2264
2256 @classmethod
2265 @classmethod
2257 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2266 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2258 if case_insensitive:
2267 if case_insensitive:
2259 gr = cls.query().filter(func.lower(cls.group_name)
2268 gr = cls.query().filter(func.lower(cls.group_name)
2260 == func.lower(group_name))
2269 == func.lower(group_name))
2261 else:
2270 else:
2262 gr = cls.query().filter(cls.group_name == group_name)
2271 gr = cls.query().filter(cls.group_name == group_name)
2263 if cache:
2272 if cache:
2264 name_key = _hash_key(group_name)
2273 name_key = _hash_key(group_name)
2265 gr = gr.options(
2274 gr = gr.options(
2266 FromCache("sql_cache_short", "get_group_%s" % name_key))
2275 FromCache("sql_cache_short", "get_group_%s" % name_key))
2267 return gr.scalar()
2276 return gr.scalar()
2268
2277
2269 @classmethod
2278 @classmethod
2270 def get_user_personal_repo_group(cls, user_id):
2279 def get_user_personal_repo_group(cls, user_id):
2271 user = User.get(user_id)
2280 user = User.get(user_id)
2272 if user.username == User.DEFAULT_USER:
2281 if user.username == User.DEFAULT_USER:
2273 return None
2282 return None
2274
2283
2275 return cls.query()\
2284 return cls.query()\
2276 .filter(cls.personal == true()) \
2285 .filter(cls.personal == true()) \
2277 .filter(cls.user == user).scalar()
2286 .filter(cls.user == user).scalar()
2278
2287
2279 @classmethod
2288 @classmethod
2280 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2289 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2281 case_insensitive=True):
2290 case_insensitive=True):
2282 q = RepoGroup.query()
2291 q = RepoGroup.query()
2283
2292
2284 if not isinstance(user_id, Optional):
2293 if not isinstance(user_id, Optional):
2285 q = q.filter(RepoGroup.user_id == user_id)
2294 q = q.filter(RepoGroup.user_id == user_id)
2286
2295
2287 if not isinstance(group_id, Optional):
2296 if not isinstance(group_id, Optional):
2288 q = q.filter(RepoGroup.group_parent_id == group_id)
2297 q = q.filter(RepoGroup.group_parent_id == group_id)
2289
2298
2290 if case_insensitive:
2299 if case_insensitive:
2291 q = q.order_by(func.lower(RepoGroup.group_name))
2300 q = q.order_by(func.lower(RepoGroup.group_name))
2292 else:
2301 else:
2293 q = q.order_by(RepoGroup.group_name)
2302 q = q.order_by(RepoGroup.group_name)
2294 return q.all()
2303 return q.all()
2295
2304
2296 @property
2305 @property
2297 def parents(self):
2306 def parents(self):
2298 parents_recursion_limit = 10
2307 parents_recursion_limit = 10
2299 groups = []
2308 groups = []
2300 if self.parent_group is None:
2309 if self.parent_group is None:
2301 return groups
2310 return groups
2302 cur_gr = self.parent_group
2311 cur_gr = self.parent_group
2303 groups.insert(0, cur_gr)
2312 groups.insert(0, cur_gr)
2304 cnt = 0
2313 cnt = 0
2305 while 1:
2314 while 1:
2306 cnt += 1
2315 cnt += 1
2307 gr = getattr(cur_gr, 'parent_group', None)
2316 gr = getattr(cur_gr, 'parent_group', None)
2308 cur_gr = cur_gr.parent_group
2317 cur_gr = cur_gr.parent_group
2309 if gr is None:
2318 if gr is None:
2310 break
2319 break
2311 if cnt == parents_recursion_limit:
2320 if cnt == parents_recursion_limit:
2312 # this will prevent accidental infinit loops
2321 # this will prevent accidental infinit loops
2313 log.error(('more than %s parents found for group %s, stopping '
2322 log.error(('more than %s parents found for group %s, stopping '
2314 'recursive parent fetching' % (parents_recursion_limit, self)))
2323 'recursive parent fetching' % (parents_recursion_limit, self)))
2315 break
2324 break
2316
2325
2317 groups.insert(0, gr)
2326 groups.insert(0, gr)
2318 return groups
2327 return groups
2319
2328
2320 @property
2329 @property
2321 def last_db_change(self):
2330 def last_db_change(self):
2322 return self.updated_on
2331 return self.updated_on
2323
2332
2324 @property
2333 @property
2325 def children(self):
2334 def children(self):
2326 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2335 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2327
2336
2328 @property
2337 @property
2329 def name(self):
2338 def name(self):
2330 return self.group_name.split(RepoGroup.url_sep())[-1]
2339 return self.group_name.split(RepoGroup.url_sep())[-1]
2331
2340
2332 @property
2341 @property
2333 def full_path(self):
2342 def full_path(self):
2334 return self.group_name
2343 return self.group_name
2335
2344
2336 @property
2345 @property
2337 def full_path_splitted(self):
2346 def full_path_splitted(self):
2338 return self.group_name.split(RepoGroup.url_sep())
2347 return self.group_name.split(RepoGroup.url_sep())
2339
2348
2340 @property
2349 @property
2341 def repositories(self):
2350 def repositories(self):
2342 return Repository.query()\
2351 return Repository.query()\
2343 .filter(Repository.group == self)\
2352 .filter(Repository.group == self)\
2344 .order_by(Repository.repo_name)
2353 .order_by(Repository.repo_name)
2345
2354
2346 @property
2355 @property
2347 def repositories_recursive_count(self):
2356 def repositories_recursive_count(self):
2348 cnt = self.repositories.count()
2357 cnt = self.repositories.count()
2349
2358
2350 def children_count(group):
2359 def children_count(group):
2351 cnt = 0
2360 cnt = 0
2352 for child in group.children:
2361 for child in group.children:
2353 cnt += child.repositories.count()
2362 cnt += child.repositories.count()
2354 cnt += children_count(child)
2363 cnt += children_count(child)
2355 return cnt
2364 return cnt
2356
2365
2357 return cnt + children_count(self)
2366 return cnt + children_count(self)
2358
2367
2359 def _recursive_objects(self, include_repos=True):
2368 def _recursive_objects(self, include_repos=True):
2360 all_ = []
2369 all_ = []
2361
2370
2362 def _get_members(root_gr):
2371 def _get_members(root_gr):
2363 if include_repos:
2372 if include_repos:
2364 for r in root_gr.repositories:
2373 for r in root_gr.repositories:
2365 all_.append(r)
2374 all_.append(r)
2366 childs = root_gr.children.all()
2375 childs = root_gr.children.all()
2367 if childs:
2376 if childs:
2368 for gr in childs:
2377 for gr in childs:
2369 all_.append(gr)
2378 all_.append(gr)
2370 _get_members(gr)
2379 _get_members(gr)
2371
2380
2372 _get_members(self)
2381 _get_members(self)
2373 return [self] + all_
2382 return [self] + all_
2374
2383
2375 def recursive_groups_and_repos(self):
2384 def recursive_groups_and_repos(self):
2376 """
2385 """
2377 Recursive return all groups, with repositories in those groups
2386 Recursive return all groups, with repositories in those groups
2378 """
2387 """
2379 return self._recursive_objects()
2388 return self._recursive_objects()
2380
2389
2381 def recursive_groups(self):
2390 def recursive_groups(self):
2382 """
2391 """
2383 Returns all children groups for this group including children of children
2392 Returns all children groups for this group including children of children
2384 """
2393 """
2385 return self._recursive_objects(include_repos=False)
2394 return self._recursive_objects(include_repos=False)
2386
2395
2387 def get_new_name(self, group_name):
2396 def get_new_name(self, group_name):
2388 """
2397 """
2389 returns new full group name based on parent and new name
2398 returns new full group name based on parent and new name
2390
2399
2391 :param group_name:
2400 :param group_name:
2392 """
2401 """
2393 path_prefix = (self.parent_group.full_path_splitted if
2402 path_prefix = (self.parent_group.full_path_splitted if
2394 self.parent_group else [])
2403 self.parent_group else [])
2395 return RepoGroup.url_sep().join(path_prefix + [group_name])
2404 return RepoGroup.url_sep().join(path_prefix + [group_name])
2396
2405
2397 def permissions(self, with_admins=True, with_owner=True):
2406 def permissions(self, with_admins=True, with_owner=True):
2398 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2407 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2399 q = q.options(joinedload(UserRepoGroupToPerm.group),
2408 q = q.options(joinedload(UserRepoGroupToPerm.group),
2400 joinedload(UserRepoGroupToPerm.user),
2409 joinedload(UserRepoGroupToPerm.user),
2401 joinedload(UserRepoGroupToPerm.permission),)
2410 joinedload(UserRepoGroupToPerm.permission),)
2402
2411
2403 # get owners and admins and permissions. We do a trick of re-writing
2412 # get owners and admins and permissions. We do a trick of re-writing
2404 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2413 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2405 # has a global reference and changing one object propagates to all
2414 # has a global reference and changing one object propagates to all
2406 # others. This means if admin is also an owner admin_row that change
2415 # others. This means if admin is also an owner admin_row that change
2407 # would propagate to both objects
2416 # would propagate to both objects
2408 perm_rows = []
2417 perm_rows = []
2409 for _usr in q.all():
2418 for _usr in q.all():
2410 usr = AttributeDict(_usr.user.get_dict())
2419 usr = AttributeDict(_usr.user.get_dict())
2411 usr.permission = _usr.permission.permission_name
2420 usr.permission = _usr.permission.permission_name
2412 perm_rows.append(usr)
2421 perm_rows.append(usr)
2413
2422
2414 # filter the perm rows by 'default' first and then sort them by
2423 # filter the perm rows by 'default' first and then sort them by
2415 # admin,write,read,none permissions sorted again alphabetically in
2424 # admin,write,read,none permissions sorted again alphabetically in
2416 # each group
2425 # each group
2417 perm_rows = sorted(perm_rows, key=display_sort)
2426 perm_rows = sorted(perm_rows, key=display_sort)
2418
2427
2419 _admin_perm = 'group.admin'
2428 _admin_perm = 'group.admin'
2420 owner_row = []
2429 owner_row = []
2421 if with_owner:
2430 if with_owner:
2422 usr = AttributeDict(self.user.get_dict())
2431 usr = AttributeDict(self.user.get_dict())
2423 usr.owner_row = True
2432 usr.owner_row = True
2424 usr.permission = _admin_perm
2433 usr.permission = _admin_perm
2425 owner_row.append(usr)
2434 owner_row.append(usr)
2426
2435
2427 super_admin_rows = []
2436 super_admin_rows = []
2428 if with_admins:
2437 if with_admins:
2429 for usr in User.get_all_super_admins():
2438 for usr in User.get_all_super_admins():
2430 # if this admin is also owner, don't double the record
2439 # if this admin is also owner, don't double the record
2431 if usr.user_id == owner_row[0].user_id:
2440 if usr.user_id == owner_row[0].user_id:
2432 owner_row[0].admin_row = True
2441 owner_row[0].admin_row = True
2433 else:
2442 else:
2434 usr = AttributeDict(usr.get_dict())
2443 usr = AttributeDict(usr.get_dict())
2435 usr.admin_row = True
2444 usr.admin_row = True
2436 usr.permission = _admin_perm
2445 usr.permission = _admin_perm
2437 super_admin_rows.append(usr)
2446 super_admin_rows.append(usr)
2438
2447
2439 return super_admin_rows + owner_row + perm_rows
2448 return super_admin_rows + owner_row + perm_rows
2440
2449
2441 def permission_user_groups(self):
2450 def permission_user_groups(self):
2442 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2451 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2443 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2452 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2444 joinedload(UserGroupRepoGroupToPerm.users_group),
2453 joinedload(UserGroupRepoGroupToPerm.users_group),
2445 joinedload(UserGroupRepoGroupToPerm.permission),)
2454 joinedload(UserGroupRepoGroupToPerm.permission),)
2446
2455
2447 perm_rows = []
2456 perm_rows = []
2448 for _user_group in q.all():
2457 for _user_group in q.all():
2449 usr = AttributeDict(_user_group.users_group.get_dict())
2458 usr = AttributeDict(_user_group.users_group.get_dict())
2450 usr.permission = _user_group.permission.permission_name
2459 usr.permission = _user_group.permission.permission_name
2451 perm_rows.append(usr)
2460 perm_rows.append(usr)
2452
2461
2453 return perm_rows
2462 return perm_rows
2454
2463
2455 def get_api_data(self):
2464 def get_api_data(self):
2456 """
2465 """
2457 Common function for generating api data
2466 Common function for generating api data
2458
2467
2459 """
2468 """
2460 group = self
2469 group = self
2461 data = {
2470 data = {
2462 'group_id': group.group_id,
2471 'group_id': group.group_id,
2463 'group_name': group.group_name,
2472 'group_name': group.group_name,
2464 'group_description': group.description_safe,
2473 'group_description': group.description_safe,
2465 'parent_group': group.parent_group.group_name if group.parent_group else None,
2474 'parent_group': group.parent_group.group_name if group.parent_group else None,
2466 'repositories': [x.repo_name for x in group.repositories],
2475 'repositories': [x.repo_name for x in group.repositories],
2467 'owner': group.user.username,
2476 'owner': group.user.username,
2468 }
2477 }
2469 return data
2478 return data
2470
2479
2471
2480
2472 class Permission(Base, BaseModel):
2481 class Permission(Base, BaseModel):
2473 __tablename__ = 'permissions'
2482 __tablename__ = 'permissions'
2474 __table_args__ = (
2483 __table_args__ = (
2475 Index('p_perm_name_idx', 'permission_name'),
2484 Index('p_perm_name_idx', 'permission_name'),
2476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2477 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2486 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2478 )
2487 )
2479 PERMS = [
2488 PERMS = [
2480 ('hg.admin', _('RhodeCode Super Administrator')),
2489 ('hg.admin', _('RhodeCode Super Administrator')),
2481
2490
2482 ('repository.none', _('Repository no access')),
2491 ('repository.none', _('Repository no access')),
2483 ('repository.read', _('Repository read access')),
2492 ('repository.read', _('Repository read access')),
2484 ('repository.write', _('Repository write access')),
2493 ('repository.write', _('Repository write access')),
2485 ('repository.admin', _('Repository admin access')),
2494 ('repository.admin', _('Repository admin access')),
2486
2495
2487 ('group.none', _('Repository group no access')),
2496 ('group.none', _('Repository group no access')),
2488 ('group.read', _('Repository group read access')),
2497 ('group.read', _('Repository group read access')),
2489 ('group.write', _('Repository group write access')),
2498 ('group.write', _('Repository group write access')),
2490 ('group.admin', _('Repository group admin access')),
2499 ('group.admin', _('Repository group admin access')),
2491
2500
2492 ('usergroup.none', _('User group no access')),
2501 ('usergroup.none', _('User group no access')),
2493 ('usergroup.read', _('User group read access')),
2502 ('usergroup.read', _('User group read access')),
2494 ('usergroup.write', _('User group write access')),
2503 ('usergroup.write', _('User group write access')),
2495 ('usergroup.admin', _('User group admin access')),
2504 ('usergroup.admin', _('User group admin access')),
2496
2505
2497 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2506 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2498 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2507 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2499
2508
2500 ('hg.usergroup.create.false', _('User Group creation disabled')),
2509 ('hg.usergroup.create.false', _('User Group creation disabled')),
2501 ('hg.usergroup.create.true', _('User Group creation enabled')),
2510 ('hg.usergroup.create.true', _('User Group creation enabled')),
2502
2511
2503 ('hg.create.none', _('Repository creation disabled')),
2512 ('hg.create.none', _('Repository creation disabled')),
2504 ('hg.create.repository', _('Repository creation enabled')),
2513 ('hg.create.repository', _('Repository creation enabled')),
2505 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2514 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2506 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2515 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2507
2516
2508 ('hg.fork.none', _('Repository forking disabled')),
2517 ('hg.fork.none', _('Repository forking disabled')),
2509 ('hg.fork.repository', _('Repository forking enabled')),
2518 ('hg.fork.repository', _('Repository forking enabled')),
2510
2519
2511 ('hg.register.none', _('Registration disabled')),
2520 ('hg.register.none', _('Registration disabled')),
2512 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2521 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2513 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2522 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2514
2523
2515 ('hg.password_reset.enabled', _('Password reset enabled')),
2524 ('hg.password_reset.enabled', _('Password reset enabled')),
2516 ('hg.password_reset.hidden', _('Password reset hidden')),
2525 ('hg.password_reset.hidden', _('Password reset hidden')),
2517 ('hg.password_reset.disabled', _('Password reset disabled')),
2526 ('hg.password_reset.disabled', _('Password reset disabled')),
2518
2527
2519 ('hg.extern_activate.manual', _('Manual activation of external account')),
2528 ('hg.extern_activate.manual', _('Manual activation of external account')),
2520 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2529 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2521
2530
2522 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2531 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2523 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2532 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2524 ]
2533 ]
2525
2534
2526 # definition of system default permissions for DEFAULT user
2535 # definition of system default permissions for DEFAULT user
2527 DEFAULT_USER_PERMISSIONS = [
2536 DEFAULT_USER_PERMISSIONS = [
2528 'repository.read',
2537 'repository.read',
2529 'group.read',
2538 'group.read',
2530 'usergroup.read',
2539 'usergroup.read',
2531 'hg.create.repository',
2540 'hg.create.repository',
2532 'hg.repogroup.create.false',
2541 'hg.repogroup.create.false',
2533 'hg.usergroup.create.false',
2542 'hg.usergroup.create.false',
2534 'hg.create.write_on_repogroup.true',
2543 'hg.create.write_on_repogroup.true',
2535 'hg.fork.repository',
2544 'hg.fork.repository',
2536 'hg.register.manual_activate',
2545 'hg.register.manual_activate',
2537 'hg.password_reset.enabled',
2546 'hg.password_reset.enabled',
2538 'hg.extern_activate.auto',
2547 'hg.extern_activate.auto',
2539 'hg.inherit_default_perms.true',
2548 'hg.inherit_default_perms.true',
2540 ]
2549 ]
2541
2550
2542 # defines which permissions are more important higher the more important
2551 # defines which permissions are more important higher the more important
2543 # Weight defines which permissions are more important.
2552 # Weight defines which permissions are more important.
2544 # The higher number the more important.
2553 # The higher number the more important.
2545 PERM_WEIGHTS = {
2554 PERM_WEIGHTS = {
2546 'repository.none': 0,
2555 'repository.none': 0,
2547 'repository.read': 1,
2556 'repository.read': 1,
2548 'repository.write': 3,
2557 'repository.write': 3,
2549 'repository.admin': 4,
2558 'repository.admin': 4,
2550
2559
2551 'group.none': 0,
2560 'group.none': 0,
2552 'group.read': 1,
2561 'group.read': 1,
2553 'group.write': 3,
2562 'group.write': 3,
2554 'group.admin': 4,
2563 'group.admin': 4,
2555
2564
2556 'usergroup.none': 0,
2565 'usergroup.none': 0,
2557 'usergroup.read': 1,
2566 'usergroup.read': 1,
2558 'usergroup.write': 3,
2567 'usergroup.write': 3,
2559 'usergroup.admin': 4,
2568 'usergroup.admin': 4,
2560
2569
2561 'hg.repogroup.create.false': 0,
2570 'hg.repogroup.create.false': 0,
2562 'hg.repogroup.create.true': 1,
2571 'hg.repogroup.create.true': 1,
2563
2572
2564 'hg.usergroup.create.false': 0,
2573 'hg.usergroup.create.false': 0,
2565 'hg.usergroup.create.true': 1,
2574 'hg.usergroup.create.true': 1,
2566
2575
2567 'hg.fork.none': 0,
2576 'hg.fork.none': 0,
2568 'hg.fork.repository': 1,
2577 'hg.fork.repository': 1,
2569 'hg.create.none': 0,
2578 'hg.create.none': 0,
2570 'hg.create.repository': 1
2579 'hg.create.repository': 1
2571 }
2580 }
2572
2581
2573 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2582 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2574 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2583 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2575 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2584 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2576
2585
2577 def __unicode__(self):
2586 def __unicode__(self):
2578 return u"<%s('%s:%s')>" % (
2587 return u"<%s('%s:%s')>" % (
2579 self.__class__.__name__, self.permission_id, self.permission_name
2588 self.__class__.__name__, self.permission_id, self.permission_name
2580 )
2589 )
2581
2590
2582 @classmethod
2591 @classmethod
2583 def get_by_key(cls, key):
2592 def get_by_key(cls, key):
2584 return cls.query().filter(cls.permission_name == key).scalar()
2593 return cls.query().filter(cls.permission_name == key).scalar()
2585
2594
2586 @classmethod
2595 @classmethod
2587 def get_default_repo_perms(cls, user_id, repo_id=None):
2596 def get_default_repo_perms(cls, user_id, repo_id=None):
2588 q = Session().query(UserRepoToPerm, Repository, Permission)\
2597 q = Session().query(UserRepoToPerm, Repository, Permission)\
2589 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2598 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2590 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2599 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2591 .filter(UserRepoToPerm.user_id == user_id)
2600 .filter(UserRepoToPerm.user_id == user_id)
2592 if repo_id:
2601 if repo_id:
2593 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2602 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2594 return q.all()
2603 return q.all()
2595
2604
2596 @classmethod
2605 @classmethod
2597 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2606 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2598 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2607 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2599 .join(
2608 .join(
2600 Permission,
2609 Permission,
2601 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2610 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2602 .join(
2611 .join(
2603 Repository,
2612 Repository,
2604 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2613 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2605 .join(
2614 .join(
2606 UserGroup,
2615 UserGroup,
2607 UserGroupRepoToPerm.users_group_id ==
2616 UserGroupRepoToPerm.users_group_id ==
2608 UserGroup.users_group_id)\
2617 UserGroup.users_group_id)\
2609 .join(
2618 .join(
2610 UserGroupMember,
2619 UserGroupMember,
2611 UserGroupRepoToPerm.users_group_id ==
2620 UserGroupRepoToPerm.users_group_id ==
2612 UserGroupMember.users_group_id)\
2621 UserGroupMember.users_group_id)\
2613 .filter(
2622 .filter(
2614 UserGroupMember.user_id == user_id,
2623 UserGroupMember.user_id == user_id,
2615 UserGroup.users_group_active == true())
2624 UserGroup.users_group_active == true())
2616 if repo_id:
2625 if repo_id:
2617 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2626 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2618 return q.all()
2627 return q.all()
2619
2628
2620 @classmethod
2629 @classmethod
2621 def get_default_group_perms(cls, user_id, repo_group_id=None):
2630 def get_default_group_perms(cls, user_id, repo_group_id=None):
2622 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2631 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2623 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2632 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2624 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2633 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2625 .filter(UserRepoGroupToPerm.user_id == user_id)
2634 .filter(UserRepoGroupToPerm.user_id == user_id)
2626 if repo_group_id:
2635 if repo_group_id:
2627 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2636 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2628 return q.all()
2637 return q.all()
2629
2638
2630 @classmethod
2639 @classmethod
2631 def get_default_group_perms_from_user_group(
2640 def get_default_group_perms_from_user_group(
2632 cls, user_id, repo_group_id=None):
2641 cls, user_id, repo_group_id=None):
2633 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2642 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2634 .join(
2643 .join(
2635 Permission,
2644 Permission,
2636 UserGroupRepoGroupToPerm.permission_id ==
2645 UserGroupRepoGroupToPerm.permission_id ==
2637 Permission.permission_id)\
2646 Permission.permission_id)\
2638 .join(
2647 .join(
2639 RepoGroup,
2648 RepoGroup,
2640 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2649 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2641 .join(
2650 .join(
2642 UserGroup,
2651 UserGroup,
2643 UserGroupRepoGroupToPerm.users_group_id ==
2652 UserGroupRepoGroupToPerm.users_group_id ==
2644 UserGroup.users_group_id)\
2653 UserGroup.users_group_id)\
2645 .join(
2654 .join(
2646 UserGroupMember,
2655 UserGroupMember,
2647 UserGroupRepoGroupToPerm.users_group_id ==
2656 UserGroupRepoGroupToPerm.users_group_id ==
2648 UserGroupMember.users_group_id)\
2657 UserGroupMember.users_group_id)\
2649 .filter(
2658 .filter(
2650 UserGroupMember.user_id == user_id,
2659 UserGroupMember.user_id == user_id,
2651 UserGroup.users_group_active == true())
2660 UserGroup.users_group_active == true())
2652 if repo_group_id:
2661 if repo_group_id:
2653 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2662 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2654 return q.all()
2663 return q.all()
2655
2664
2656 @classmethod
2665 @classmethod
2657 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2666 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2658 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2667 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2659 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2668 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2660 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2669 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2661 .filter(UserUserGroupToPerm.user_id == user_id)
2670 .filter(UserUserGroupToPerm.user_id == user_id)
2662 if user_group_id:
2671 if user_group_id:
2663 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2672 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2664 return q.all()
2673 return q.all()
2665
2674
2666 @classmethod
2675 @classmethod
2667 def get_default_user_group_perms_from_user_group(
2676 def get_default_user_group_perms_from_user_group(
2668 cls, user_id, user_group_id=None):
2677 cls, user_id, user_group_id=None):
2669 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2678 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2670 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2679 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2671 .join(
2680 .join(
2672 Permission,
2681 Permission,
2673 UserGroupUserGroupToPerm.permission_id ==
2682 UserGroupUserGroupToPerm.permission_id ==
2674 Permission.permission_id)\
2683 Permission.permission_id)\
2675 .join(
2684 .join(
2676 TargetUserGroup,
2685 TargetUserGroup,
2677 UserGroupUserGroupToPerm.target_user_group_id ==
2686 UserGroupUserGroupToPerm.target_user_group_id ==
2678 TargetUserGroup.users_group_id)\
2687 TargetUserGroup.users_group_id)\
2679 .join(
2688 .join(
2680 UserGroup,
2689 UserGroup,
2681 UserGroupUserGroupToPerm.user_group_id ==
2690 UserGroupUserGroupToPerm.user_group_id ==
2682 UserGroup.users_group_id)\
2691 UserGroup.users_group_id)\
2683 .join(
2692 .join(
2684 UserGroupMember,
2693 UserGroupMember,
2685 UserGroupUserGroupToPerm.user_group_id ==
2694 UserGroupUserGroupToPerm.user_group_id ==
2686 UserGroupMember.users_group_id)\
2695 UserGroupMember.users_group_id)\
2687 .filter(
2696 .filter(
2688 UserGroupMember.user_id == user_id,
2697 UserGroupMember.user_id == user_id,
2689 UserGroup.users_group_active == true())
2698 UserGroup.users_group_active == true())
2690 if user_group_id:
2699 if user_group_id:
2691 q = q.filter(
2700 q = q.filter(
2692 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2701 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2693
2702
2694 return q.all()
2703 return q.all()
2695
2704
2696
2705
2697 class UserRepoToPerm(Base, BaseModel):
2706 class UserRepoToPerm(Base, BaseModel):
2698 __tablename__ = 'repo_to_perm'
2707 __tablename__ = 'repo_to_perm'
2699 __table_args__ = (
2708 __table_args__ = (
2700 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2709 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2701 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2702 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2703 )
2712 )
2704 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2713 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2705 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2714 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2706 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2715 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2707 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2708
2717
2709 user = relationship('User')
2718 user = relationship('User')
2710 repository = relationship('Repository')
2719 repository = relationship('Repository')
2711 permission = relationship('Permission')
2720 permission = relationship('Permission')
2712
2721
2713 @classmethod
2722 @classmethod
2714 def create(cls, user, repository, permission):
2723 def create(cls, user, repository, permission):
2715 n = cls()
2724 n = cls()
2716 n.user = user
2725 n.user = user
2717 n.repository = repository
2726 n.repository = repository
2718 n.permission = permission
2727 n.permission = permission
2719 Session().add(n)
2728 Session().add(n)
2720 return n
2729 return n
2721
2730
2722 def __unicode__(self):
2731 def __unicode__(self):
2723 return u'<%s => %s >' % (self.user, self.repository)
2732 return u'<%s => %s >' % (self.user, self.repository)
2724
2733
2725
2734
2726 class UserUserGroupToPerm(Base, BaseModel):
2735 class UserUserGroupToPerm(Base, BaseModel):
2727 __tablename__ = 'user_user_group_to_perm'
2736 __tablename__ = 'user_user_group_to_perm'
2728 __table_args__ = (
2737 __table_args__ = (
2729 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2738 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2740 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2732 )
2741 )
2733 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2742 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2743 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2735 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2744 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2736 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2745 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2737
2746
2738 user = relationship('User')
2747 user = relationship('User')
2739 user_group = relationship('UserGroup')
2748 user_group = relationship('UserGroup')
2740 permission = relationship('Permission')
2749 permission = relationship('Permission')
2741
2750
2742 @classmethod
2751 @classmethod
2743 def create(cls, user, user_group, permission):
2752 def create(cls, user, user_group, permission):
2744 n = cls()
2753 n = cls()
2745 n.user = user
2754 n.user = user
2746 n.user_group = user_group
2755 n.user_group = user_group
2747 n.permission = permission
2756 n.permission = permission
2748 Session().add(n)
2757 Session().add(n)
2749 return n
2758 return n
2750
2759
2751 def __unicode__(self):
2760 def __unicode__(self):
2752 return u'<%s => %s >' % (self.user, self.user_group)
2761 return u'<%s => %s >' % (self.user, self.user_group)
2753
2762
2754
2763
2755 class UserToPerm(Base, BaseModel):
2764 class UserToPerm(Base, BaseModel):
2756 __tablename__ = 'user_to_perm'
2765 __tablename__ = 'user_to_perm'
2757 __table_args__ = (
2766 __table_args__ = (
2758 UniqueConstraint('user_id', 'permission_id'),
2767 UniqueConstraint('user_id', 'permission_id'),
2759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2760 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2761 )
2770 )
2762 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2771 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2763 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2764 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2773 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2765
2774
2766 user = relationship('User')
2775 user = relationship('User')
2767 permission = relationship('Permission', lazy='joined')
2776 permission = relationship('Permission', lazy='joined')
2768
2777
2769 def __unicode__(self):
2778 def __unicode__(self):
2770 return u'<%s => %s >' % (self.user, self.permission)
2779 return u'<%s => %s >' % (self.user, self.permission)
2771
2780
2772
2781
2773 class UserGroupRepoToPerm(Base, BaseModel):
2782 class UserGroupRepoToPerm(Base, BaseModel):
2774 __tablename__ = 'users_group_repo_to_perm'
2783 __tablename__ = 'users_group_repo_to_perm'
2775 __table_args__ = (
2784 __table_args__ = (
2776 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2785 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2777 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2786 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2778 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2787 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2779 )
2788 )
2780 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2789 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2781 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2790 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2782 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2791 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2783 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2792 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2784
2793
2785 users_group = relationship('UserGroup')
2794 users_group = relationship('UserGroup')
2786 permission = relationship('Permission')
2795 permission = relationship('Permission')
2787 repository = relationship('Repository')
2796 repository = relationship('Repository')
2788
2797
2789 @classmethod
2798 @classmethod
2790 def create(cls, users_group, repository, permission):
2799 def create(cls, users_group, repository, permission):
2791 n = cls()
2800 n = cls()
2792 n.users_group = users_group
2801 n.users_group = users_group
2793 n.repository = repository
2802 n.repository = repository
2794 n.permission = permission
2803 n.permission = permission
2795 Session().add(n)
2804 Session().add(n)
2796 return n
2805 return n
2797
2806
2798 def __unicode__(self):
2807 def __unicode__(self):
2799 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2808 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2800
2809
2801
2810
2802 class UserGroupUserGroupToPerm(Base, BaseModel):
2811 class UserGroupUserGroupToPerm(Base, BaseModel):
2803 __tablename__ = 'user_group_user_group_to_perm'
2812 __tablename__ = 'user_group_user_group_to_perm'
2804 __table_args__ = (
2813 __table_args__ = (
2805 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2814 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2806 CheckConstraint('target_user_group_id != user_group_id'),
2815 CheckConstraint('target_user_group_id != user_group_id'),
2807 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2816 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2808 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2817 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2809 )
2818 )
2810 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2819 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2811 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2820 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2812 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2821 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2813 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2822 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2814
2823
2815 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2824 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2816 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2825 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2817 permission = relationship('Permission')
2826 permission = relationship('Permission')
2818
2827
2819 @classmethod
2828 @classmethod
2820 def create(cls, target_user_group, user_group, permission):
2829 def create(cls, target_user_group, user_group, permission):
2821 n = cls()
2830 n = cls()
2822 n.target_user_group = target_user_group
2831 n.target_user_group = target_user_group
2823 n.user_group = user_group
2832 n.user_group = user_group
2824 n.permission = permission
2833 n.permission = permission
2825 Session().add(n)
2834 Session().add(n)
2826 return n
2835 return n
2827
2836
2828 def __unicode__(self):
2837 def __unicode__(self):
2829 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2838 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2830
2839
2831
2840
2832 class UserGroupToPerm(Base, BaseModel):
2841 class UserGroupToPerm(Base, BaseModel):
2833 __tablename__ = 'users_group_to_perm'
2842 __tablename__ = 'users_group_to_perm'
2834 __table_args__ = (
2843 __table_args__ = (
2835 UniqueConstraint('users_group_id', 'permission_id',),
2844 UniqueConstraint('users_group_id', 'permission_id',),
2836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2837 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2846 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2838 )
2847 )
2839 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2848 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2840 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2849 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2850 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2842
2851
2843 users_group = relationship('UserGroup')
2852 users_group = relationship('UserGroup')
2844 permission = relationship('Permission')
2853 permission = relationship('Permission')
2845
2854
2846
2855
2847 class UserRepoGroupToPerm(Base, BaseModel):
2856 class UserRepoGroupToPerm(Base, BaseModel):
2848 __tablename__ = 'user_repo_group_to_perm'
2857 __tablename__ = 'user_repo_group_to_perm'
2849 __table_args__ = (
2858 __table_args__ = (
2850 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2859 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2852 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2853 )
2862 )
2854
2863
2855 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2864 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2856 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2865 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2857 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2866 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2858 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2867 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2859
2868
2860 user = relationship('User')
2869 user = relationship('User')
2861 group = relationship('RepoGroup')
2870 group = relationship('RepoGroup')
2862 permission = relationship('Permission')
2871 permission = relationship('Permission')
2863
2872
2864 @classmethod
2873 @classmethod
2865 def create(cls, user, repository_group, permission):
2874 def create(cls, user, repository_group, permission):
2866 n = cls()
2875 n = cls()
2867 n.user = user
2876 n.user = user
2868 n.group = repository_group
2877 n.group = repository_group
2869 n.permission = permission
2878 n.permission = permission
2870 Session().add(n)
2879 Session().add(n)
2871 return n
2880 return n
2872
2881
2873
2882
2874 class UserGroupRepoGroupToPerm(Base, BaseModel):
2883 class UserGroupRepoGroupToPerm(Base, BaseModel):
2875 __tablename__ = 'users_group_repo_group_to_perm'
2884 __tablename__ = 'users_group_repo_group_to_perm'
2876 __table_args__ = (
2885 __table_args__ = (
2877 UniqueConstraint('users_group_id', 'group_id'),
2886 UniqueConstraint('users_group_id', 'group_id'),
2878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2880 )
2889 )
2881
2890
2882 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2883 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2892 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2884 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2893 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2885 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2886
2895
2887 users_group = relationship('UserGroup')
2896 users_group = relationship('UserGroup')
2888 permission = relationship('Permission')
2897 permission = relationship('Permission')
2889 group = relationship('RepoGroup')
2898 group = relationship('RepoGroup')
2890
2899
2891 @classmethod
2900 @classmethod
2892 def create(cls, user_group, repository_group, permission):
2901 def create(cls, user_group, repository_group, permission):
2893 n = cls()
2902 n = cls()
2894 n.users_group = user_group
2903 n.users_group = user_group
2895 n.group = repository_group
2904 n.group = repository_group
2896 n.permission = permission
2905 n.permission = permission
2897 Session().add(n)
2906 Session().add(n)
2898 return n
2907 return n
2899
2908
2900 def __unicode__(self):
2909 def __unicode__(self):
2901 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2910 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2902
2911
2903
2912
2904 class Statistics(Base, BaseModel):
2913 class Statistics(Base, BaseModel):
2905 __tablename__ = 'statistics'
2914 __tablename__ = 'statistics'
2906 __table_args__ = (
2915 __table_args__ = (
2907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2916 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2917 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2909 )
2918 )
2910 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2919 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2911 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2920 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2912 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2921 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2913 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2922 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2914 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2923 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2915 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2924 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2916
2925
2917 repository = relationship('Repository', single_parent=True)
2926 repository = relationship('Repository', single_parent=True)
2918
2927
2919
2928
2920 class UserFollowing(Base, BaseModel):
2929 class UserFollowing(Base, BaseModel):
2921 __tablename__ = 'user_followings'
2930 __tablename__ = 'user_followings'
2922 __table_args__ = (
2931 __table_args__ = (
2923 UniqueConstraint('user_id', 'follows_repository_id'),
2932 UniqueConstraint('user_id', 'follows_repository_id'),
2924 UniqueConstraint('user_id', 'follows_user_id'),
2933 UniqueConstraint('user_id', 'follows_user_id'),
2925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2934 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2935 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2927 )
2936 )
2928
2937
2929 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2938 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2930 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2939 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2931 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2940 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2932 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2941 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2933 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2942 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2934
2943
2935 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2944 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2936
2945
2937 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2946 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2938 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2947 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2939
2948
2940 @classmethod
2949 @classmethod
2941 def get_repo_followers(cls, repo_id):
2950 def get_repo_followers(cls, repo_id):
2942 return cls.query().filter(cls.follows_repo_id == repo_id)
2951 return cls.query().filter(cls.follows_repo_id == repo_id)
2943
2952
2944
2953
2945 class CacheKey(Base, BaseModel):
2954 class CacheKey(Base, BaseModel):
2946 __tablename__ = 'cache_invalidation'
2955 __tablename__ = 'cache_invalidation'
2947 __table_args__ = (
2956 __table_args__ = (
2948 UniqueConstraint('cache_key'),
2957 UniqueConstraint('cache_key'),
2949 Index('key_idx', 'cache_key'),
2958 Index('key_idx', 'cache_key'),
2950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2951 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2952 )
2961 )
2953 CACHE_TYPE_ATOM = 'ATOM'
2962 CACHE_TYPE_ATOM = 'ATOM'
2954 CACHE_TYPE_RSS = 'RSS'
2963 CACHE_TYPE_RSS = 'RSS'
2955 CACHE_TYPE_README = 'README'
2964 CACHE_TYPE_README = 'README'
2956
2965
2957 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2966 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2958 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2967 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2959 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2968 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2960 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2969 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2961
2970
2962 def __init__(self, cache_key, cache_args=''):
2971 def __init__(self, cache_key, cache_args=''):
2963 self.cache_key = cache_key
2972 self.cache_key = cache_key
2964 self.cache_args = cache_args
2973 self.cache_args = cache_args
2965 self.cache_active = False
2974 self.cache_active = False
2966
2975
2967 def __unicode__(self):
2976 def __unicode__(self):
2968 return u"<%s('%s:%s[%s]')>" % (
2977 return u"<%s('%s:%s[%s]')>" % (
2969 self.__class__.__name__,
2978 self.__class__.__name__,
2970 self.cache_id, self.cache_key, self.cache_active)
2979 self.cache_id, self.cache_key, self.cache_active)
2971
2980
2972 def _cache_key_partition(self):
2981 def _cache_key_partition(self):
2973 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2982 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2974 return prefix, repo_name, suffix
2983 return prefix, repo_name, suffix
2975
2984
2976 def get_prefix(self):
2985 def get_prefix(self):
2977 """
2986 """
2978 Try to extract prefix from existing cache key. The key could consist
2987 Try to extract prefix from existing cache key. The key could consist
2979 of prefix, repo_name, suffix
2988 of prefix, repo_name, suffix
2980 """
2989 """
2981 # this returns prefix, repo_name, suffix
2990 # this returns prefix, repo_name, suffix
2982 return self._cache_key_partition()[0]
2991 return self._cache_key_partition()[0]
2983
2992
2984 def get_suffix(self):
2993 def get_suffix(self):
2985 """
2994 """
2986 get suffix that might have been used in _get_cache_key to
2995 get suffix that might have been used in _get_cache_key to
2987 generate self.cache_key. Only used for informational purposes
2996 generate self.cache_key. Only used for informational purposes
2988 in repo_edit.mako.
2997 in repo_edit.mako.
2989 """
2998 """
2990 # prefix, repo_name, suffix
2999 # prefix, repo_name, suffix
2991 return self._cache_key_partition()[2]
3000 return self._cache_key_partition()[2]
2992
3001
2993 @classmethod
3002 @classmethod
2994 def delete_all_cache(cls):
3003 def delete_all_cache(cls):
2995 """
3004 """
2996 Delete all cache keys from database.
3005 Delete all cache keys from database.
2997 Should only be run when all instances are down and all entries
3006 Should only be run when all instances are down and all entries
2998 thus stale.
3007 thus stale.
2999 """
3008 """
3000 cls.query().delete()
3009 cls.query().delete()
3001 Session().commit()
3010 Session().commit()
3002
3011
3003 @classmethod
3012 @classmethod
3004 def get_cache_key(cls, repo_name, cache_type):
3013 def get_cache_key(cls, repo_name, cache_type):
3005 """
3014 """
3006
3015
3007 Generate a cache key for this process of RhodeCode instance.
3016 Generate a cache key for this process of RhodeCode instance.
3008 Prefix most likely will be process id or maybe explicitly set
3017 Prefix most likely will be process id or maybe explicitly set
3009 instance_id from .ini file.
3018 instance_id from .ini file.
3010 """
3019 """
3011 import rhodecode
3020 import rhodecode
3012 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3021 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3013
3022
3014 repo_as_unicode = safe_unicode(repo_name)
3023 repo_as_unicode = safe_unicode(repo_name)
3015 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3024 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3016 if cache_type else repo_as_unicode
3025 if cache_type else repo_as_unicode
3017
3026
3018 return u'{}{}'.format(prefix, key)
3027 return u'{}{}'.format(prefix, key)
3019
3028
3020 @classmethod
3029 @classmethod
3021 def set_invalidate(cls, repo_name, delete=False):
3030 def set_invalidate(cls, repo_name, delete=False):
3022 """
3031 """
3023 Mark all caches of a repo as invalid in the database.
3032 Mark all caches of a repo as invalid in the database.
3024 """
3033 """
3025
3034
3026 try:
3035 try:
3027 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3036 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3028 if delete:
3037 if delete:
3029 log.debug('cache objects deleted for repo %s',
3038 log.debug('cache objects deleted for repo %s',
3030 safe_str(repo_name))
3039 safe_str(repo_name))
3031 qry.delete()
3040 qry.delete()
3032 else:
3041 else:
3033 log.debug('cache objects marked as invalid for repo %s',
3042 log.debug('cache objects marked as invalid for repo %s',
3034 safe_str(repo_name))
3043 safe_str(repo_name))
3035 qry.update({"cache_active": False})
3044 qry.update({"cache_active": False})
3036
3045
3037 Session().commit()
3046 Session().commit()
3038 except Exception:
3047 except Exception:
3039 log.exception(
3048 log.exception(
3040 'Cache key invalidation failed for repository %s',
3049 'Cache key invalidation failed for repository %s',
3041 safe_str(repo_name))
3050 safe_str(repo_name))
3042 Session().rollback()
3051 Session().rollback()
3043
3052
3044 @classmethod
3053 @classmethod
3045 def get_active_cache(cls, cache_key):
3054 def get_active_cache(cls, cache_key):
3046 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3055 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3047 if inv_obj:
3056 if inv_obj:
3048 return inv_obj
3057 return inv_obj
3049 return None
3058 return None
3050
3059
3051 @classmethod
3060 @classmethod
3052 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3061 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3053 thread_scoped=False):
3062 thread_scoped=False):
3054 """
3063 """
3055 @cache_region('long_term')
3064 @cache_region('long_term')
3056 def _heavy_calculation(cache_key):
3065 def _heavy_calculation(cache_key):
3057 return 'result'
3066 return 'result'
3058
3067
3059 cache_context = CacheKey.repo_context_cache(
3068 cache_context = CacheKey.repo_context_cache(
3060 _heavy_calculation, repo_name, cache_type)
3069 _heavy_calculation, repo_name, cache_type)
3061
3070
3062 with cache_context as context:
3071 with cache_context as context:
3063 context.invalidate()
3072 context.invalidate()
3064 computed = context.compute()
3073 computed = context.compute()
3065
3074
3066 assert computed == 'result'
3075 assert computed == 'result'
3067 """
3076 """
3068 from rhodecode.lib import caches
3077 from rhodecode.lib import caches
3069 return caches.InvalidationContext(
3078 return caches.InvalidationContext(
3070 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3079 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3071
3080
3072
3081
3073 class ChangesetComment(Base, BaseModel):
3082 class ChangesetComment(Base, BaseModel):
3074 __tablename__ = 'changeset_comments'
3083 __tablename__ = 'changeset_comments'
3075 __table_args__ = (
3084 __table_args__ = (
3076 Index('cc_revision_idx', 'revision'),
3085 Index('cc_revision_idx', 'revision'),
3077 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3086 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3078 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3087 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3079 )
3088 )
3080
3089
3081 COMMENT_OUTDATED = u'comment_outdated'
3090 COMMENT_OUTDATED = u'comment_outdated'
3082 COMMENT_TYPE_NOTE = u'note'
3091 COMMENT_TYPE_NOTE = u'note'
3083 COMMENT_TYPE_TODO = u'todo'
3092 COMMENT_TYPE_TODO = u'todo'
3084 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3093 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3085
3094
3086 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3095 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3087 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3096 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3088 revision = Column('revision', String(40), nullable=True)
3097 revision = Column('revision', String(40), nullable=True)
3089 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3098 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3090 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3099 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3091 line_no = Column('line_no', Unicode(10), nullable=True)
3100 line_no = Column('line_no', Unicode(10), nullable=True)
3092 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3101 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3093 f_path = Column('f_path', Unicode(1000), nullable=True)
3102 f_path = Column('f_path', Unicode(1000), nullable=True)
3094 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3103 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3095 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3104 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3096 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3105 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3097 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3106 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3098 renderer = Column('renderer', Unicode(64), nullable=True)
3107 renderer = Column('renderer', Unicode(64), nullable=True)
3099 display_state = Column('display_state', Unicode(128), nullable=True)
3108 display_state = Column('display_state', Unicode(128), nullable=True)
3100
3109
3101 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3110 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3102 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3111 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3103 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3112 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3104 author = relationship('User', lazy='joined')
3113 author = relationship('User', lazy='joined')
3105 repo = relationship('Repository')
3114 repo = relationship('Repository')
3106 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3115 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3107 pull_request = relationship('PullRequest', lazy='joined')
3116 pull_request = relationship('PullRequest', lazy='joined')
3108 pull_request_version = relationship('PullRequestVersion')
3117 pull_request_version = relationship('PullRequestVersion')
3109
3118
3110 @classmethod
3119 @classmethod
3111 def get_users(cls, revision=None, pull_request_id=None):
3120 def get_users(cls, revision=None, pull_request_id=None):
3112 """
3121 """
3113 Returns user associated with this ChangesetComment. ie those
3122 Returns user associated with this ChangesetComment. ie those
3114 who actually commented
3123 who actually commented
3115
3124
3116 :param cls:
3125 :param cls:
3117 :param revision:
3126 :param revision:
3118 """
3127 """
3119 q = Session().query(User)\
3128 q = Session().query(User)\
3120 .join(ChangesetComment.author)
3129 .join(ChangesetComment.author)
3121 if revision:
3130 if revision:
3122 q = q.filter(cls.revision == revision)
3131 q = q.filter(cls.revision == revision)
3123 elif pull_request_id:
3132 elif pull_request_id:
3124 q = q.filter(cls.pull_request_id == pull_request_id)
3133 q = q.filter(cls.pull_request_id == pull_request_id)
3125 return q.all()
3134 return q.all()
3126
3135
3127 @classmethod
3136 @classmethod
3128 def get_index_from_version(cls, pr_version, versions):
3137 def get_index_from_version(cls, pr_version, versions):
3129 num_versions = [x.pull_request_version_id for x in versions]
3138 num_versions = [x.pull_request_version_id for x in versions]
3130 try:
3139 try:
3131 return num_versions.index(pr_version) +1
3140 return num_versions.index(pr_version) +1
3132 except (IndexError, ValueError):
3141 except (IndexError, ValueError):
3133 return
3142 return
3134
3143
3135 @property
3144 @property
3136 def outdated(self):
3145 def outdated(self):
3137 return self.display_state == self.COMMENT_OUTDATED
3146 return self.display_state == self.COMMENT_OUTDATED
3138
3147
3139 def outdated_at_version(self, version):
3148 def outdated_at_version(self, version):
3140 """
3149 """
3141 Checks if comment is outdated for given pull request version
3150 Checks if comment is outdated for given pull request version
3142 """
3151 """
3143 return self.outdated and self.pull_request_version_id != version
3152 return self.outdated and self.pull_request_version_id != version
3144
3153
3145 def older_than_version(self, version):
3154 def older_than_version(self, version):
3146 """
3155 """
3147 Checks if comment is made from previous version than given
3156 Checks if comment is made from previous version than given
3148 """
3157 """
3149 if version is None:
3158 if version is None:
3150 return self.pull_request_version_id is not None
3159 return self.pull_request_version_id is not None
3151
3160
3152 return self.pull_request_version_id < version
3161 return self.pull_request_version_id < version
3153
3162
3154 @property
3163 @property
3155 def resolved(self):
3164 def resolved(self):
3156 return self.resolved_by[0] if self.resolved_by else None
3165 return self.resolved_by[0] if self.resolved_by else None
3157
3166
3158 @property
3167 @property
3159 def is_todo(self):
3168 def is_todo(self):
3160 return self.comment_type == self.COMMENT_TYPE_TODO
3169 return self.comment_type == self.COMMENT_TYPE_TODO
3161
3170
3162 @property
3171 @property
3163 def is_inline(self):
3172 def is_inline(self):
3164 return self.line_no and self.f_path
3173 return self.line_no and self.f_path
3165
3174
3166 def get_index_version(self, versions):
3175 def get_index_version(self, versions):
3167 return self.get_index_from_version(
3176 return self.get_index_from_version(
3168 self.pull_request_version_id, versions)
3177 self.pull_request_version_id, versions)
3169
3178
3170 def __repr__(self):
3179 def __repr__(self):
3171 if self.comment_id:
3180 if self.comment_id:
3172 return '<DB:Comment #%s>' % self.comment_id
3181 return '<DB:Comment #%s>' % self.comment_id
3173 else:
3182 else:
3174 return '<DB:Comment at %#x>' % id(self)
3183 return '<DB:Comment at %#x>' % id(self)
3175
3184
3176 def get_api_data(self):
3185 def get_api_data(self):
3177 comment = self
3186 comment = self
3178 data = {
3187 data = {
3179 'comment_id': comment.comment_id,
3188 'comment_id': comment.comment_id,
3180 'comment_type': comment.comment_type,
3189 'comment_type': comment.comment_type,
3181 'comment_text': comment.text,
3190 'comment_text': comment.text,
3182 'comment_status': comment.status_change,
3191 'comment_status': comment.status_change,
3183 'comment_f_path': comment.f_path,
3192 'comment_f_path': comment.f_path,
3184 'comment_lineno': comment.line_no,
3193 'comment_lineno': comment.line_no,
3185 'comment_author': comment.author,
3194 'comment_author': comment.author,
3186 'comment_created_on': comment.created_on
3195 'comment_created_on': comment.created_on
3187 }
3196 }
3188 return data
3197 return data
3189
3198
3190 def __json__(self):
3199 def __json__(self):
3191 data = dict()
3200 data = dict()
3192 data.update(self.get_api_data())
3201 data.update(self.get_api_data())
3193 return data
3202 return data
3194
3203
3195
3204
3196 class ChangesetStatus(Base, BaseModel):
3205 class ChangesetStatus(Base, BaseModel):
3197 __tablename__ = 'changeset_statuses'
3206 __tablename__ = 'changeset_statuses'
3198 __table_args__ = (
3207 __table_args__ = (
3199 Index('cs_revision_idx', 'revision'),
3208 Index('cs_revision_idx', 'revision'),
3200 Index('cs_version_idx', 'version'),
3209 Index('cs_version_idx', 'version'),
3201 UniqueConstraint('repo_id', 'revision', 'version'),
3210 UniqueConstraint('repo_id', 'revision', 'version'),
3202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3204 )
3213 )
3205 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3214 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3206 STATUS_APPROVED = 'approved'
3215 STATUS_APPROVED = 'approved'
3207 STATUS_REJECTED = 'rejected'
3216 STATUS_REJECTED = 'rejected'
3208 STATUS_UNDER_REVIEW = 'under_review'
3217 STATUS_UNDER_REVIEW = 'under_review'
3209
3218
3210 STATUSES = [
3219 STATUSES = [
3211 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3220 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3212 (STATUS_APPROVED, _("Approved")),
3221 (STATUS_APPROVED, _("Approved")),
3213 (STATUS_REJECTED, _("Rejected")),
3222 (STATUS_REJECTED, _("Rejected")),
3214 (STATUS_UNDER_REVIEW, _("Under Review")),
3223 (STATUS_UNDER_REVIEW, _("Under Review")),
3215 ]
3224 ]
3216
3225
3217 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3226 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3218 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3227 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3219 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3220 revision = Column('revision', String(40), nullable=False)
3229 revision = Column('revision', String(40), nullable=False)
3221 status = Column('status', String(128), nullable=False, default=DEFAULT)
3230 status = Column('status', String(128), nullable=False, default=DEFAULT)
3222 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3231 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3223 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3232 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3224 version = Column('version', Integer(), nullable=False, default=0)
3233 version = Column('version', Integer(), nullable=False, default=0)
3225 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3234 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3226
3235
3227 author = relationship('User', lazy='joined')
3236 author = relationship('User', lazy='joined')
3228 repo = relationship('Repository')
3237 repo = relationship('Repository')
3229 comment = relationship('ChangesetComment', lazy='joined')
3238 comment = relationship('ChangesetComment', lazy='joined')
3230 pull_request = relationship('PullRequest', lazy='joined')
3239 pull_request = relationship('PullRequest', lazy='joined')
3231
3240
3232 def __unicode__(self):
3241 def __unicode__(self):
3233 return u"<%s('%s[v%s]:%s')>" % (
3242 return u"<%s('%s[v%s]:%s')>" % (
3234 self.__class__.__name__,
3243 self.__class__.__name__,
3235 self.status, self.version, self.author
3244 self.status, self.version, self.author
3236 )
3245 )
3237
3246
3238 @classmethod
3247 @classmethod
3239 def get_status_lbl(cls, value):
3248 def get_status_lbl(cls, value):
3240 return dict(cls.STATUSES).get(value)
3249 return dict(cls.STATUSES).get(value)
3241
3250
3242 @property
3251 @property
3243 def status_lbl(self):
3252 def status_lbl(self):
3244 return ChangesetStatus.get_status_lbl(self.status)
3253 return ChangesetStatus.get_status_lbl(self.status)
3245
3254
3246 def get_api_data(self):
3255 def get_api_data(self):
3247 status = self
3256 status = self
3248 data = {
3257 data = {
3249 'status_id': status.changeset_status_id,
3258 'status_id': status.changeset_status_id,
3250 'status': status.status,
3259 'status': status.status,
3251 }
3260 }
3252 return data
3261 return data
3253
3262
3254 def __json__(self):
3263 def __json__(self):
3255 data = dict()
3264 data = dict()
3256 data.update(self.get_api_data())
3265 data.update(self.get_api_data())
3257 return data
3266 return data
3258
3267
3259
3268
3260 class _PullRequestBase(BaseModel):
3269 class _PullRequestBase(BaseModel):
3261 """
3270 """
3262 Common attributes of pull request and version entries.
3271 Common attributes of pull request and version entries.
3263 """
3272 """
3264
3273
3265 # .status values
3274 # .status values
3266 STATUS_NEW = u'new'
3275 STATUS_NEW = u'new'
3267 STATUS_OPEN = u'open'
3276 STATUS_OPEN = u'open'
3268 STATUS_CLOSED = u'closed'
3277 STATUS_CLOSED = u'closed'
3269
3278
3270 title = Column('title', Unicode(255), nullable=True)
3279 title = Column('title', Unicode(255), nullable=True)
3271 description = Column(
3280 description = Column(
3272 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3281 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3273 nullable=True)
3282 nullable=True)
3274 # new/open/closed status of pull request (not approve/reject/etc)
3283 # new/open/closed status of pull request (not approve/reject/etc)
3275 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3284 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3276 created_on = Column(
3285 created_on = Column(
3277 'created_on', DateTime(timezone=False), nullable=False,
3286 'created_on', DateTime(timezone=False), nullable=False,
3278 default=datetime.datetime.now)
3287 default=datetime.datetime.now)
3279 updated_on = Column(
3288 updated_on = Column(
3280 'updated_on', DateTime(timezone=False), nullable=False,
3289 'updated_on', DateTime(timezone=False), nullable=False,
3281 default=datetime.datetime.now)
3290 default=datetime.datetime.now)
3282
3291
3283 @declared_attr
3292 @declared_attr
3284 def user_id(cls):
3293 def user_id(cls):
3285 return Column(
3294 return Column(
3286 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3295 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3287 unique=None)
3296 unique=None)
3288
3297
3289 # 500 revisions max
3298 # 500 revisions max
3290 _revisions = Column(
3299 _revisions = Column(
3291 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3300 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3292
3301
3293 @declared_attr
3302 @declared_attr
3294 def source_repo_id(cls):
3303 def source_repo_id(cls):
3295 # TODO: dan: rename column to source_repo_id
3304 # TODO: dan: rename column to source_repo_id
3296 return Column(
3305 return Column(
3297 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3306 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3298 nullable=False)
3307 nullable=False)
3299
3308
3300 source_ref = Column('org_ref', Unicode(255), nullable=False)
3309 source_ref = Column('org_ref', Unicode(255), nullable=False)
3301
3310
3302 @declared_attr
3311 @declared_attr
3303 def target_repo_id(cls):
3312 def target_repo_id(cls):
3304 # TODO: dan: rename column to target_repo_id
3313 # TODO: dan: rename column to target_repo_id
3305 return Column(
3314 return Column(
3306 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3315 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3307 nullable=False)
3316 nullable=False)
3308
3317
3309 target_ref = Column('other_ref', Unicode(255), nullable=False)
3318 target_ref = Column('other_ref', Unicode(255), nullable=False)
3310 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3319 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3311
3320
3312 # TODO: dan: rename column to last_merge_source_rev
3321 # TODO: dan: rename column to last_merge_source_rev
3313 _last_merge_source_rev = Column(
3322 _last_merge_source_rev = Column(
3314 'last_merge_org_rev', String(40), nullable=True)
3323 'last_merge_org_rev', String(40), nullable=True)
3315 # TODO: dan: rename column to last_merge_target_rev
3324 # TODO: dan: rename column to last_merge_target_rev
3316 _last_merge_target_rev = Column(
3325 _last_merge_target_rev = Column(
3317 'last_merge_other_rev', String(40), nullable=True)
3326 'last_merge_other_rev', String(40), nullable=True)
3318 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3327 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3319 merge_rev = Column('merge_rev', String(40), nullable=True)
3328 merge_rev = Column('merge_rev', String(40), nullable=True)
3320
3329
3321 reviewer_data = Column(
3330 reviewer_data = Column(
3322 'reviewer_data_json', MutationObj.as_mutable(
3331 'reviewer_data_json', MutationObj.as_mutable(
3323 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3332 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3324
3333
3325 @property
3334 @property
3326 def reviewer_data_json(self):
3335 def reviewer_data_json(self):
3327 return json.dumps(self.reviewer_data)
3336 return json.dumps(self.reviewer_data)
3328
3337
3329 @hybrid_property
3338 @hybrid_property
3330 def description_safe(self):
3339 def description_safe(self):
3331 from rhodecode.lib import helpers as h
3340 from rhodecode.lib import helpers as h
3332 return h.escape(self.description)
3341 return h.escape(self.description)
3333
3342
3334 @hybrid_property
3343 @hybrid_property
3335 def revisions(self):
3344 def revisions(self):
3336 return self._revisions.split(':') if self._revisions else []
3345 return self._revisions.split(':') if self._revisions else []
3337
3346
3338 @revisions.setter
3347 @revisions.setter
3339 def revisions(self, val):
3348 def revisions(self, val):
3340 self._revisions = ':'.join(val)
3349 self._revisions = ':'.join(val)
3341
3350
3342 @hybrid_property
3351 @hybrid_property
3343 def last_merge_status(self):
3352 def last_merge_status(self):
3344 return safe_int(self._last_merge_status)
3353 return safe_int(self._last_merge_status)
3345
3354
3346 @last_merge_status.setter
3355 @last_merge_status.setter
3347 def last_merge_status(self, val):
3356 def last_merge_status(self, val):
3348 self._last_merge_status = val
3357 self._last_merge_status = val
3349
3358
3350 @declared_attr
3359 @declared_attr
3351 def author(cls):
3360 def author(cls):
3352 return relationship('User', lazy='joined')
3361 return relationship('User', lazy='joined')
3353
3362
3354 @declared_attr
3363 @declared_attr
3355 def source_repo(cls):
3364 def source_repo(cls):
3356 return relationship(
3365 return relationship(
3357 'Repository',
3366 'Repository',
3358 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3367 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3359
3368
3360 @property
3369 @property
3361 def source_ref_parts(self):
3370 def source_ref_parts(self):
3362 return self.unicode_to_reference(self.source_ref)
3371 return self.unicode_to_reference(self.source_ref)
3363
3372
3364 @declared_attr
3373 @declared_attr
3365 def target_repo(cls):
3374 def target_repo(cls):
3366 return relationship(
3375 return relationship(
3367 'Repository',
3376 'Repository',
3368 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3377 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3369
3378
3370 @property
3379 @property
3371 def target_ref_parts(self):
3380 def target_ref_parts(self):
3372 return self.unicode_to_reference(self.target_ref)
3381 return self.unicode_to_reference(self.target_ref)
3373
3382
3374 @property
3383 @property
3375 def shadow_merge_ref(self):
3384 def shadow_merge_ref(self):
3376 return self.unicode_to_reference(self._shadow_merge_ref)
3385 return self.unicode_to_reference(self._shadow_merge_ref)
3377
3386
3378 @shadow_merge_ref.setter
3387 @shadow_merge_ref.setter
3379 def shadow_merge_ref(self, ref):
3388 def shadow_merge_ref(self, ref):
3380 self._shadow_merge_ref = self.reference_to_unicode(ref)
3389 self._shadow_merge_ref = self.reference_to_unicode(ref)
3381
3390
3382 def unicode_to_reference(self, raw):
3391 def unicode_to_reference(self, raw):
3383 """
3392 """
3384 Convert a unicode (or string) to a reference object.
3393 Convert a unicode (or string) to a reference object.
3385 If unicode evaluates to False it returns None.
3394 If unicode evaluates to False it returns None.
3386 """
3395 """
3387 if raw:
3396 if raw:
3388 refs = raw.split(':')
3397 refs = raw.split(':')
3389 return Reference(*refs)
3398 return Reference(*refs)
3390 else:
3399 else:
3391 return None
3400 return None
3392
3401
3393 def reference_to_unicode(self, ref):
3402 def reference_to_unicode(self, ref):
3394 """
3403 """
3395 Convert a reference object to unicode.
3404 Convert a reference object to unicode.
3396 If reference is None it returns None.
3405 If reference is None it returns None.
3397 """
3406 """
3398 if ref:
3407 if ref:
3399 return u':'.join(ref)
3408 return u':'.join(ref)
3400 else:
3409 else:
3401 return None
3410 return None
3402
3411
3403 def get_api_data(self, with_merge_state=True):
3412 def get_api_data(self, with_merge_state=True):
3404 from rhodecode.model.pull_request import PullRequestModel
3413 from rhodecode.model.pull_request import PullRequestModel
3405
3414
3406 pull_request = self
3415 pull_request = self
3407 if with_merge_state:
3416 if with_merge_state:
3408 merge_status = PullRequestModel().merge_status(pull_request)
3417 merge_status = PullRequestModel().merge_status(pull_request)
3409 merge_state = {
3418 merge_state = {
3410 'status': merge_status[0],
3419 'status': merge_status[0],
3411 'message': safe_unicode(merge_status[1]),
3420 'message': safe_unicode(merge_status[1]),
3412 }
3421 }
3413 else:
3422 else:
3414 merge_state = {'status': 'not_available',
3423 merge_state = {'status': 'not_available',
3415 'message': 'not_available'}
3424 'message': 'not_available'}
3416
3425
3417 merge_data = {
3426 merge_data = {
3418 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3427 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3419 'reference': (
3428 'reference': (
3420 pull_request.shadow_merge_ref._asdict()
3429 pull_request.shadow_merge_ref._asdict()
3421 if pull_request.shadow_merge_ref else None),
3430 if pull_request.shadow_merge_ref else None),
3422 }
3431 }
3423
3432
3424 data = {
3433 data = {
3425 'pull_request_id': pull_request.pull_request_id,
3434 'pull_request_id': pull_request.pull_request_id,
3426 'url': PullRequestModel().get_url(pull_request),
3435 'url': PullRequestModel().get_url(pull_request),
3427 'title': pull_request.title,
3436 'title': pull_request.title,
3428 'description': pull_request.description,
3437 'description': pull_request.description,
3429 'status': pull_request.status,
3438 'status': pull_request.status,
3430 'created_on': pull_request.created_on,
3439 'created_on': pull_request.created_on,
3431 'updated_on': pull_request.updated_on,
3440 'updated_on': pull_request.updated_on,
3432 'commit_ids': pull_request.revisions,
3441 'commit_ids': pull_request.revisions,
3433 'review_status': pull_request.calculated_review_status(),
3442 'review_status': pull_request.calculated_review_status(),
3434 'mergeable': merge_state,
3443 'mergeable': merge_state,
3435 'source': {
3444 'source': {
3436 'clone_url': pull_request.source_repo.clone_url(),
3445 'clone_url': pull_request.source_repo.clone_url(),
3437 'repository': pull_request.source_repo.repo_name,
3446 'repository': pull_request.source_repo.repo_name,
3438 'reference': {
3447 'reference': {
3439 'name': pull_request.source_ref_parts.name,
3448 'name': pull_request.source_ref_parts.name,
3440 'type': pull_request.source_ref_parts.type,
3449 'type': pull_request.source_ref_parts.type,
3441 'commit_id': pull_request.source_ref_parts.commit_id,
3450 'commit_id': pull_request.source_ref_parts.commit_id,
3442 },
3451 },
3443 },
3452 },
3444 'target': {
3453 'target': {
3445 'clone_url': pull_request.target_repo.clone_url(),
3454 'clone_url': pull_request.target_repo.clone_url(),
3446 'repository': pull_request.target_repo.repo_name,
3455 'repository': pull_request.target_repo.repo_name,
3447 'reference': {
3456 'reference': {
3448 'name': pull_request.target_ref_parts.name,
3457 'name': pull_request.target_ref_parts.name,
3449 'type': pull_request.target_ref_parts.type,
3458 'type': pull_request.target_ref_parts.type,
3450 'commit_id': pull_request.target_ref_parts.commit_id,
3459 'commit_id': pull_request.target_ref_parts.commit_id,
3451 },
3460 },
3452 },
3461 },
3453 'merge': merge_data,
3462 'merge': merge_data,
3454 'author': pull_request.author.get_api_data(include_secrets=False,
3463 'author': pull_request.author.get_api_data(include_secrets=False,
3455 details='basic'),
3464 details='basic'),
3456 'reviewers': [
3465 'reviewers': [
3457 {
3466 {
3458 'user': reviewer.get_api_data(include_secrets=False,
3467 'user': reviewer.get_api_data(include_secrets=False,
3459 details='basic'),
3468 details='basic'),
3460 'reasons': reasons,
3469 'reasons': reasons,
3461 'review_status': st[0][1].status if st else 'not_reviewed',
3470 'review_status': st[0][1].status if st else 'not_reviewed',
3462 }
3471 }
3463 for reviewer, reasons, mandatory, st in
3472 for reviewer, reasons, mandatory, st in
3464 pull_request.reviewers_statuses()
3473 pull_request.reviewers_statuses()
3465 ]
3474 ]
3466 }
3475 }
3467
3476
3468 return data
3477 return data
3469
3478
3470
3479
3471 class PullRequest(Base, _PullRequestBase):
3480 class PullRequest(Base, _PullRequestBase):
3472 __tablename__ = 'pull_requests'
3481 __tablename__ = 'pull_requests'
3473 __table_args__ = (
3482 __table_args__ = (
3474 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3475 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3476 )
3485 )
3477
3486
3478 pull_request_id = Column(
3487 pull_request_id = Column(
3479 'pull_request_id', Integer(), nullable=False, primary_key=True)
3488 'pull_request_id', Integer(), nullable=False, primary_key=True)
3480
3489
3481 def __repr__(self):
3490 def __repr__(self):
3482 if self.pull_request_id:
3491 if self.pull_request_id:
3483 return '<DB:PullRequest #%s>' % self.pull_request_id
3492 return '<DB:PullRequest #%s>' % self.pull_request_id
3484 else:
3493 else:
3485 return '<DB:PullRequest at %#x>' % id(self)
3494 return '<DB:PullRequest at %#x>' % id(self)
3486
3495
3487 reviewers = relationship('PullRequestReviewers',
3496 reviewers = relationship('PullRequestReviewers',
3488 cascade="all, delete, delete-orphan")
3497 cascade="all, delete, delete-orphan")
3489 statuses = relationship('ChangesetStatus',
3498 statuses = relationship('ChangesetStatus',
3490 cascade="all, delete, delete-orphan")
3499 cascade="all, delete, delete-orphan")
3491 comments = relationship('ChangesetComment',
3500 comments = relationship('ChangesetComment',
3492 cascade="all, delete, delete-orphan")
3501 cascade="all, delete, delete-orphan")
3493 versions = relationship('PullRequestVersion',
3502 versions = relationship('PullRequestVersion',
3494 cascade="all, delete, delete-orphan",
3503 cascade="all, delete, delete-orphan",
3495 lazy='dynamic')
3504 lazy='dynamic')
3496
3505
3497 @classmethod
3506 @classmethod
3498 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3507 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3499 internal_methods=None):
3508 internal_methods=None):
3500
3509
3501 class PullRequestDisplay(object):
3510 class PullRequestDisplay(object):
3502 """
3511 """
3503 Special object wrapper for showing PullRequest data via Versions
3512 Special object wrapper for showing PullRequest data via Versions
3504 It mimics PR object as close as possible. This is read only object
3513 It mimics PR object as close as possible. This is read only object
3505 just for display
3514 just for display
3506 """
3515 """
3507
3516
3508 def __init__(self, attrs, internal=None):
3517 def __init__(self, attrs, internal=None):
3509 self.attrs = attrs
3518 self.attrs = attrs
3510 # internal have priority over the given ones via attrs
3519 # internal have priority over the given ones via attrs
3511 self.internal = internal or ['versions']
3520 self.internal = internal or ['versions']
3512
3521
3513 def __getattr__(self, item):
3522 def __getattr__(self, item):
3514 if item in self.internal:
3523 if item in self.internal:
3515 return getattr(self, item)
3524 return getattr(self, item)
3516 try:
3525 try:
3517 return self.attrs[item]
3526 return self.attrs[item]
3518 except KeyError:
3527 except KeyError:
3519 raise AttributeError(
3528 raise AttributeError(
3520 '%s object has no attribute %s' % (self, item))
3529 '%s object has no attribute %s' % (self, item))
3521
3530
3522 def __repr__(self):
3531 def __repr__(self):
3523 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3532 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3524
3533
3525 def versions(self):
3534 def versions(self):
3526 return pull_request_obj.versions.order_by(
3535 return pull_request_obj.versions.order_by(
3527 PullRequestVersion.pull_request_version_id).all()
3536 PullRequestVersion.pull_request_version_id).all()
3528
3537
3529 def is_closed(self):
3538 def is_closed(self):
3530 return pull_request_obj.is_closed()
3539 return pull_request_obj.is_closed()
3531
3540
3532 @property
3541 @property
3533 def pull_request_version_id(self):
3542 def pull_request_version_id(self):
3534 return getattr(pull_request_obj, 'pull_request_version_id', None)
3543 return getattr(pull_request_obj, 'pull_request_version_id', None)
3535
3544
3536 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3545 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3537
3546
3538 attrs.author = StrictAttributeDict(
3547 attrs.author = StrictAttributeDict(
3539 pull_request_obj.author.get_api_data())
3548 pull_request_obj.author.get_api_data())
3540 if pull_request_obj.target_repo:
3549 if pull_request_obj.target_repo:
3541 attrs.target_repo = StrictAttributeDict(
3550 attrs.target_repo = StrictAttributeDict(
3542 pull_request_obj.target_repo.get_api_data())
3551 pull_request_obj.target_repo.get_api_data())
3543 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3552 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3544
3553
3545 if pull_request_obj.source_repo:
3554 if pull_request_obj.source_repo:
3546 attrs.source_repo = StrictAttributeDict(
3555 attrs.source_repo = StrictAttributeDict(
3547 pull_request_obj.source_repo.get_api_data())
3556 pull_request_obj.source_repo.get_api_data())
3548 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3557 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3549
3558
3550 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3559 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3551 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3560 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3552 attrs.revisions = pull_request_obj.revisions
3561 attrs.revisions = pull_request_obj.revisions
3553
3562
3554 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3563 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3555 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3564 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3556 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3565 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3557
3566
3558 return PullRequestDisplay(attrs, internal=internal_methods)
3567 return PullRequestDisplay(attrs, internal=internal_methods)
3559
3568
3560 def is_closed(self):
3569 def is_closed(self):
3561 return self.status == self.STATUS_CLOSED
3570 return self.status == self.STATUS_CLOSED
3562
3571
3563 def __json__(self):
3572 def __json__(self):
3564 return {
3573 return {
3565 'revisions': self.revisions,
3574 'revisions': self.revisions,
3566 }
3575 }
3567
3576
3568 def calculated_review_status(self):
3577 def calculated_review_status(self):
3569 from rhodecode.model.changeset_status import ChangesetStatusModel
3578 from rhodecode.model.changeset_status import ChangesetStatusModel
3570 return ChangesetStatusModel().calculated_review_status(self)
3579 return ChangesetStatusModel().calculated_review_status(self)
3571
3580
3572 def reviewers_statuses(self):
3581 def reviewers_statuses(self):
3573 from rhodecode.model.changeset_status import ChangesetStatusModel
3582 from rhodecode.model.changeset_status import ChangesetStatusModel
3574 return ChangesetStatusModel().reviewers_statuses(self)
3583 return ChangesetStatusModel().reviewers_statuses(self)
3575
3584
3576 @property
3585 @property
3577 def workspace_id(self):
3586 def workspace_id(self):
3578 from rhodecode.model.pull_request import PullRequestModel
3587 from rhodecode.model.pull_request import PullRequestModel
3579 return PullRequestModel()._workspace_id(self)
3588 return PullRequestModel()._workspace_id(self)
3580
3589
3581 def get_shadow_repo(self):
3590 def get_shadow_repo(self):
3582 workspace_id = self.workspace_id
3591 workspace_id = self.workspace_id
3583 vcs_obj = self.target_repo.scm_instance()
3592 vcs_obj = self.target_repo.scm_instance()
3584 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3593 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3585 workspace_id)
3594 workspace_id)
3586 return vcs_obj._get_shadow_instance(shadow_repository_path)
3595 return vcs_obj._get_shadow_instance(shadow_repository_path)
3587
3596
3588
3597
3589 class PullRequestVersion(Base, _PullRequestBase):
3598 class PullRequestVersion(Base, _PullRequestBase):
3590 __tablename__ = 'pull_request_versions'
3599 __tablename__ = 'pull_request_versions'
3591 __table_args__ = (
3600 __table_args__ = (
3592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3593 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3602 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3594 )
3603 )
3595
3604
3596 pull_request_version_id = Column(
3605 pull_request_version_id = Column(
3597 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3606 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3598 pull_request_id = Column(
3607 pull_request_id = Column(
3599 'pull_request_id', Integer(),
3608 'pull_request_id', Integer(),
3600 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3609 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3601 pull_request = relationship('PullRequest')
3610 pull_request = relationship('PullRequest')
3602
3611
3603 def __repr__(self):
3612 def __repr__(self):
3604 if self.pull_request_version_id:
3613 if self.pull_request_version_id:
3605 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3614 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3606 else:
3615 else:
3607 return '<DB:PullRequestVersion at %#x>' % id(self)
3616 return '<DB:PullRequestVersion at %#x>' % id(self)
3608
3617
3609 @property
3618 @property
3610 def reviewers(self):
3619 def reviewers(self):
3611 return self.pull_request.reviewers
3620 return self.pull_request.reviewers
3612
3621
3613 @property
3622 @property
3614 def versions(self):
3623 def versions(self):
3615 return self.pull_request.versions
3624 return self.pull_request.versions
3616
3625
3617 def is_closed(self):
3626 def is_closed(self):
3618 # calculate from original
3627 # calculate from original
3619 return self.pull_request.status == self.STATUS_CLOSED
3628 return self.pull_request.status == self.STATUS_CLOSED
3620
3629
3621 def calculated_review_status(self):
3630 def calculated_review_status(self):
3622 return self.pull_request.calculated_review_status()
3631 return self.pull_request.calculated_review_status()
3623
3632
3624 def reviewers_statuses(self):
3633 def reviewers_statuses(self):
3625 return self.pull_request.reviewers_statuses()
3634 return self.pull_request.reviewers_statuses()
3626
3635
3627
3636
3628 class PullRequestReviewers(Base, BaseModel):
3637 class PullRequestReviewers(Base, BaseModel):
3629 __tablename__ = 'pull_request_reviewers'
3638 __tablename__ = 'pull_request_reviewers'
3630 __table_args__ = (
3639 __table_args__ = (
3631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3632 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3633 )
3642 )
3634
3643
3635 @hybrid_property
3644 @hybrid_property
3636 def reasons(self):
3645 def reasons(self):
3637 if not self._reasons:
3646 if not self._reasons:
3638 return []
3647 return []
3639 return self._reasons
3648 return self._reasons
3640
3649
3641 @reasons.setter
3650 @reasons.setter
3642 def reasons(self, val):
3651 def reasons(self, val):
3643 val = val or []
3652 val = val or []
3644 if any(not isinstance(x, basestring) for x in val):
3653 if any(not isinstance(x, basestring) for x in val):
3645 raise Exception('invalid reasons type, must be list of strings')
3654 raise Exception('invalid reasons type, must be list of strings')
3646 self._reasons = val
3655 self._reasons = val
3647
3656
3648 pull_requests_reviewers_id = Column(
3657 pull_requests_reviewers_id = Column(
3649 'pull_requests_reviewers_id', Integer(), nullable=False,
3658 'pull_requests_reviewers_id', Integer(), nullable=False,
3650 primary_key=True)
3659 primary_key=True)
3651 pull_request_id = Column(
3660 pull_request_id = Column(
3652 "pull_request_id", Integer(),
3661 "pull_request_id", Integer(),
3653 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3662 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3654 user_id = Column(
3663 user_id = Column(
3655 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3664 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3656 _reasons = Column(
3665 _reasons = Column(
3657 'reason', MutationList.as_mutable(
3666 'reason', MutationList.as_mutable(
3658 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3667 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3659 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3668 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3660 user = relationship('User')
3669 user = relationship('User')
3661 pull_request = relationship('PullRequest')
3670 pull_request = relationship('PullRequest')
3662
3671
3663
3672
3664 class Notification(Base, BaseModel):
3673 class Notification(Base, BaseModel):
3665 __tablename__ = 'notifications'
3674 __tablename__ = 'notifications'
3666 __table_args__ = (
3675 __table_args__ = (
3667 Index('notification_type_idx', 'type'),
3676 Index('notification_type_idx', 'type'),
3668 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3669 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3670 )
3679 )
3671
3680
3672 TYPE_CHANGESET_COMMENT = u'cs_comment'
3681 TYPE_CHANGESET_COMMENT = u'cs_comment'
3673 TYPE_MESSAGE = u'message'
3682 TYPE_MESSAGE = u'message'
3674 TYPE_MENTION = u'mention'
3683 TYPE_MENTION = u'mention'
3675 TYPE_REGISTRATION = u'registration'
3684 TYPE_REGISTRATION = u'registration'
3676 TYPE_PULL_REQUEST = u'pull_request'
3685 TYPE_PULL_REQUEST = u'pull_request'
3677 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3686 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3678
3687
3679 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3688 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3680 subject = Column('subject', Unicode(512), nullable=True)
3689 subject = Column('subject', Unicode(512), nullable=True)
3681 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3690 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3682 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3691 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3683 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3692 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3684 type_ = Column('type', Unicode(255))
3693 type_ = Column('type', Unicode(255))
3685
3694
3686 created_by_user = relationship('User')
3695 created_by_user = relationship('User')
3687 notifications_to_users = relationship('UserNotification', lazy='joined',
3696 notifications_to_users = relationship('UserNotification', lazy='joined',
3688 cascade="all, delete, delete-orphan")
3697 cascade="all, delete, delete-orphan")
3689
3698
3690 @property
3699 @property
3691 def recipients(self):
3700 def recipients(self):
3692 return [x.user for x in UserNotification.query()\
3701 return [x.user for x in UserNotification.query()\
3693 .filter(UserNotification.notification == self)\
3702 .filter(UserNotification.notification == self)\
3694 .order_by(UserNotification.user_id.asc()).all()]
3703 .order_by(UserNotification.user_id.asc()).all()]
3695
3704
3696 @classmethod
3705 @classmethod
3697 def create(cls, created_by, subject, body, recipients, type_=None):
3706 def create(cls, created_by, subject, body, recipients, type_=None):
3698 if type_ is None:
3707 if type_ is None:
3699 type_ = Notification.TYPE_MESSAGE
3708 type_ = Notification.TYPE_MESSAGE
3700
3709
3701 notification = cls()
3710 notification = cls()
3702 notification.created_by_user = created_by
3711 notification.created_by_user = created_by
3703 notification.subject = subject
3712 notification.subject = subject
3704 notification.body = body
3713 notification.body = body
3705 notification.type_ = type_
3714 notification.type_ = type_
3706 notification.created_on = datetime.datetime.now()
3715 notification.created_on = datetime.datetime.now()
3707
3716
3708 for u in recipients:
3717 for u in recipients:
3709 assoc = UserNotification()
3718 assoc = UserNotification()
3710 assoc.notification = notification
3719 assoc.notification = notification
3711
3720
3712 # if created_by is inside recipients mark his notification
3721 # if created_by is inside recipients mark his notification
3713 # as read
3722 # as read
3714 if u.user_id == created_by.user_id:
3723 if u.user_id == created_by.user_id:
3715 assoc.read = True
3724 assoc.read = True
3716
3725
3717 u.notifications.append(assoc)
3726 u.notifications.append(assoc)
3718 Session().add(notification)
3727 Session().add(notification)
3719
3728
3720 return notification
3729 return notification
3721
3730
3722
3731
3723 class UserNotification(Base, BaseModel):
3732 class UserNotification(Base, BaseModel):
3724 __tablename__ = 'user_to_notification'
3733 __tablename__ = 'user_to_notification'
3725 __table_args__ = (
3734 __table_args__ = (
3726 UniqueConstraint('user_id', 'notification_id'),
3735 UniqueConstraint('user_id', 'notification_id'),
3727 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3736 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3728 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3737 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3729 )
3738 )
3730 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3739 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3731 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3740 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3732 read = Column('read', Boolean, default=False)
3741 read = Column('read', Boolean, default=False)
3733 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3742 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3734
3743
3735 user = relationship('User', lazy="joined")
3744 user = relationship('User', lazy="joined")
3736 notification = relationship('Notification', lazy="joined",
3745 notification = relationship('Notification', lazy="joined",
3737 order_by=lambda: Notification.created_on.desc(),)
3746 order_by=lambda: Notification.created_on.desc(),)
3738
3747
3739 def mark_as_read(self):
3748 def mark_as_read(self):
3740 self.read = True
3749 self.read = True
3741 Session().add(self)
3750 Session().add(self)
3742
3751
3743
3752
3744 class Gist(Base, BaseModel):
3753 class Gist(Base, BaseModel):
3745 __tablename__ = 'gists'
3754 __tablename__ = 'gists'
3746 __table_args__ = (
3755 __table_args__ = (
3747 Index('g_gist_access_id_idx', 'gist_access_id'),
3756 Index('g_gist_access_id_idx', 'gist_access_id'),
3748 Index('g_created_on_idx', 'created_on'),
3757 Index('g_created_on_idx', 'created_on'),
3749 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3758 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3750 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3759 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3751 )
3760 )
3752 GIST_PUBLIC = u'public'
3761 GIST_PUBLIC = u'public'
3753 GIST_PRIVATE = u'private'
3762 GIST_PRIVATE = u'private'
3754 DEFAULT_FILENAME = u'gistfile1.txt'
3763 DEFAULT_FILENAME = u'gistfile1.txt'
3755
3764
3756 ACL_LEVEL_PUBLIC = u'acl_public'
3765 ACL_LEVEL_PUBLIC = u'acl_public'
3757 ACL_LEVEL_PRIVATE = u'acl_private'
3766 ACL_LEVEL_PRIVATE = u'acl_private'
3758
3767
3759 gist_id = Column('gist_id', Integer(), primary_key=True)
3768 gist_id = Column('gist_id', Integer(), primary_key=True)
3760 gist_access_id = Column('gist_access_id', Unicode(250))
3769 gist_access_id = Column('gist_access_id', Unicode(250))
3761 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3770 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3762 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3771 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3763 gist_expires = Column('gist_expires', Float(53), nullable=False)
3772 gist_expires = Column('gist_expires', Float(53), nullable=False)
3764 gist_type = Column('gist_type', Unicode(128), nullable=False)
3773 gist_type = Column('gist_type', Unicode(128), nullable=False)
3765 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3774 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3766 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3775 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3767 acl_level = Column('acl_level', Unicode(128), nullable=True)
3776 acl_level = Column('acl_level', Unicode(128), nullable=True)
3768
3777
3769 owner = relationship('User')
3778 owner = relationship('User')
3770
3779
3771 def __repr__(self):
3780 def __repr__(self):
3772 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3781 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3773
3782
3774 @hybrid_property
3783 @hybrid_property
3775 def description_safe(self):
3784 def description_safe(self):
3776 from rhodecode.lib import helpers as h
3785 from rhodecode.lib import helpers as h
3777 return h.escape(self.gist_description)
3786 return h.escape(self.gist_description)
3778
3787
3779 @classmethod
3788 @classmethod
3780 def get_or_404(cls, id_):
3789 def get_or_404(cls, id_):
3781 from pyramid.httpexceptions import HTTPNotFound
3790 from pyramid.httpexceptions import HTTPNotFound
3782
3791
3783 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3792 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3784 if not res:
3793 if not res:
3785 raise HTTPNotFound()
3794 raise HTTPNotFound()
3786 return res
3795 return res
3787
3796
3788 @classmethod
3797 @classmethod
3789 def get_by_access_id(cls, gist_access_id):
3798 def get_by_access_id(cls, gist_access_id):
3790 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3799 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3791
3800
3792 def gist_url(self):
3801 def gist_url(self):
3793 from rhodecode.model.gist import GistModel
3802 from rhodecode.model.gist import GistModel
3794 return GistModel().get_url(self)
3803 return GistModel().get_url(self)
3795
3804
3796 @classmethod
3805 @classmethod
3797 def base_path(cls):
3806 def base_path(cls):
3798 """
3807 """
3799 Returns base path when all gists are stored
3808 Returns base path when all gists are stored
3800
3809
3801 :param cls:
3810 :param cls:
3802 """
3811 """
3803 from rhodecode.model.gist import GIST_STORE_LOC
3812 from rhodecode.model.gist import GIST_STORE_LOC
3804 q = Session().query(RhodeCodeUi)\
3813 q = Session().query(RhodeCodeUi)\
3805 .filter(RhodeCodeUi.ui_key == URL_SEP)
3814 .filter(RhodeCodeUi.ui_key == URL_SEP)
3806 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3815 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3807 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3816 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3808
3817
3809 def get_api_data(self):
3818 def get_api_data(self):
3810 """
3819 """
3811 Common function for generating gist related data for API
3820 Common function for generating gist related data for API
3812 """
3821 """
3813 gist = self
3822 gist = self
3814 data = {
3823 data = {
3815 'gist_id': gist.gist_id,
3824 'gist_id': gist.gist_id,
3816 'type': gist.gist_type,
3825 'type': gist.gist_type,
3817 'access_id': gist.gist_access_id,
3826 'access_id': gist.gist_access_id,
3818 'description': gist.gist_description,
3827 'description': gist.gist_description,
3819 'url': gist.gist_url(),
3828 'url': gist.gist_url(),
3820 'expires': gist.gist_expires,
3829 'expires': gist.gist_expires,
3821 'created_on': gist.created_on,
3830 'created_on': gist.created_on,
3822 'modified_at': gist.modified_at,
3831 'modified_at': gist.modified_at,
3823 'content': None,
3832 'content': None,
3824 'acl_level': gist.acl_level,
3833 'acl_level': gist.acl_level,
3825 }
3834 }
3826 return data
3835 return data
3827
3836
3828 def __json__(self):
3837 def __json__(self):
3829 data = dict(
3838 data = dict(
3830 )
3839 )
3831 data.update(self.get_api_data())
3840 data.update(self.get_api_data())
3832 return data
3841 return data
3833 # SCM functions
3842 # SCM functions
3834
3843
3835 def scm_instance(self, **kwargs):
3844 def scm_instance(self, **kwargs):
3836 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3845 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3837 return get_vcs_instance(
3846 return get_vcs_instance(
3838 repo_path=safe_str(full_repo_path), create=False)
3847 repo_path=safe_str(full_repo_path), create=False)
3839
3848
3840
3849
3841 class ExternalIdentity(Base, BaseModel):
3850 class ExternalIdentity(Base, BaseModel):
3842 __tablename__ = 'external_identities'
3851 __tablename__ = 'external_identities'
3843 __table_args__ = (
3852 __table_args__ = (
3844 Index('local_user_id_idx', 'local_user_id'),
3853 Index('local_user_id_idx', 'local_user_id'),
3845 Index('external_id_idx', 'external_id'),
3854 Index('external_id_idx', 'external_id'),
3846 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3855 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3847 'mysql_charset': 'utf8'})
3856 'mysql_charset': 'utf8'})
3848
3857
3849 external_id = Column('external_id', Unicode(255), default=u'',
3858 external_id = Column('external_id', Unicode(255), default=u'',
3850 primary_key=True)
3859 primary_key=True)
3851 external_username = Column('external_username', Unicode(1024), default=u'')
3860 external_username = Column('external_username', Unicode(1024), default=u'')
3852 local_user_id = Column('local_user_id', Integer(),
3861 local_user_id = Column('local_user_id', Integer(),
3853 ForeignKey('users.user_id'), primary_key=True)
3862 ForeignKey('users.user_id'), primary_key=True)
3854 provider_name = Column('provider_name', Unicode(255), default=u'',
3863 provider_name = Column('provider_name', Unicode(255), default=u'',
3855 primary_key=True)
3864 primary_key=True)
3856 access_token = Column('access_token', String(1024), default=u'')
3865 access_token = Column('access_token', String(1024), default=u'')
3857 alt_token = Column('alt_token', String(1024), default=u'')
3866 alt_token = Column('alt_token', String(1024), default=u'')
3858 token_secret = Column('token_secret', String(1024), default=u'')
3867 token_secret = Column('token_secret', String(1024), default=u'')
3859
3868
3860 @classmethod
3869 @classmethod
3861 def by_external_id_and_provider(cls, external_id, provider_name,
3870 def by_external_id_and_provider(cls, external_id, provider_name,
3862 local_user_id=None):
3871 local_user_id=None):
3863 """
3872 """
3864 Returns ExternalIdentity instance based on search params
3873 Returns ExternalIdentity instance based on search params
3865
3874
3866 :param external_id:
3875 :param external_id:
3867 :param provider_name:
3876 :param provider_name:
3868 :return: ExternalIdentity
3877 :return: ExternalIdentity
3869 """
3878 """
3870 query = cls.query()
3879 query = cls.query()
3871 query = query.filter(cls.external_id == external_id)
3880 query = query.filter(cls.external_id == external_id)
3872 query = query.filter(cls.provider_name == provider_name)
3881 query = query.filter(cls.provider_name == provider_name)
3873 if local_user_id:
3882 if local_user_id:
3874 query = query.filter(cls.local_user_id == local_user_id)
3883 query = query.filter(cls.local_user_id == local_user_id)
3875 return query.first()
3884 return query.first()
3876
3885
3877 @classmethod
3886 @classmethod
3878 def user_by_external_id_and_provider(cls, external_id, provider_name):
3887 def user_by_external_id_and_provider(cls, external_id, provider_name):
3879 """
3888 """
3880 Returns User instance based on search params
3889 Returns User instance based on search params
3881
3890
3882 :param external_id:
3891 :param external_id:
3883 :param provider_name:
3892 :param provider_name:
3884 :return: User
3893 :return: User
3885 """
3894 """
3886 query = User.query()
3895 query = User.query()
3887 query = query.filter(cls.external_id == external_id)
3896 query = query.filter(cls.external_id == external_id)
3888 query = query.filter(cls.provider_name == provider_name)
3897 query = query.filter(cls.provider_name == provider_name)
3889 query = query.filter(User.user_id == cls.local_user_id)
3898 query = query.filter(User.user_id == cls.local_user_id)
3890 return query.first()
3899 return query.first()
3891
3900
3892 @classmethod
3901 @classmethod
3893 def by_local_user_id(cls, local_user_id):
3902 def by_local_user_id(cls, local_user_id):
3894 """
3903 """
3895 Returns all tokens for user
3904 Returns all tokens for user
3896
3905
3897 :param local_user_id:
3906 :param local_user_id:
3898 :return: ExternalIdentity
3907 :return: ExternalIdentity
3899 """
3908 """
3900 query = cls.query()
3909 query = cls.query()
3901 query = query.filter(cls.local_user_id == local_user_id)
3910 query = query.filter(cls.local_user_id == local_user_id)
3902 return query
3911 return query
3903
3912
3904
3913
3905 class Integration(Base, BaseModel):
3914 class Integration(Base, BaseModel):
3906 __tablename__ = 'integrations'
3915 __tablename__ = 'integrations'
3907 __table_args__ = (
3916 __table_args__ = (
3908 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3917 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3909 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3918 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3910 )
3919 )
3911
3920
3912 integration_id = Column('integration_id', Integer(), primary_key=True)
3921 integration_id = Column('integration_id', Integer(), primary_key=True)
3913 integration_type = Column('integration_type', String(255))
3922 integration_type = Column('integration_type', String(255))
3914 enabled = Column('enabled', Boolean(), nullable=False)
3923 enabled = Column('enabled', Boolean(), nullable=False)
3915 name = Column('name', String(255), nullable=False)
3924 name = Column('name', String(255), nullable=False)
3916 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3925 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3917 default=False)
3926 default=False)
3918
3927
3919 settings = Column(
3928 settings = Column(
3920 'settings_json', MutationObj.as_mutable(
3929 'settings_json', MutationObj.as_mutable(
3921 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3930 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3922 repo_id = Column(
3931 repo_id = Column(
3923 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3932 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3924 nullable=True, unique=None, default=None)
3933 nullable=True, unique=None, default=None)
3925 repo = relationship('Repository', lazy='joined')
3934 repo = relationship('Repository', lazy='joined')
3926
3935
3927 repo_group_id = Column(
3936 repo_group_id = Column(
3928 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3937 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3929 nullable=True, unique=None, default=None)
3938 nullable=True, unique=None, default=None)
3930 repo_group = relationship('RepoGroup', lazy='joined')
3939 repo_group = relationship('RepoGroup', lazy='joined')
3931
3940
3932 @property
3941 @property
3933 def scope(self):
3942 def scope(self):
3934 if self.repo:
3943 if self.repo:
3935 return repr(self.repo)
3944 return repr(self.repo)
3936 if self.repo_group:
3945 if self.repo_group:
3937 if self.child_repos_only:
3946 if self.child_repos_only:
3938 return repr(self.repo_group) + ' (child repos only)'
3947 return repr(self.repo_group) + ' (child repos only)'
3939 else:
3948 else:
3940 return repr(self.repo_group) + ' (recursive)'
3949 return repr(self.repo_group) + ' (recursive)'
3941 if self.child_repos_only:
3950 if self.child_repos_only:
3942 return 'root_repos'
3951 return 'root_repos'
3943 return 'global'
3952 return 'global'
3944
3953
3945 def __repr__(self):
3954 def __repr__(self):
3946 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3955 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3947
3956
3948
3957
3949 class RepoReviewRuleUser(Base, BaseModel):
3958 class RepoReviewRuleUser(Base, BaseModel):
3950 __tablename__ = 'repo_review_rules_users'
3959 __tablename__ = 'repo_review_rules_users'
3951 __table_args__ = (
3960 __table_args__ = (
3952 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3953 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3954 )
3963 )
3955 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3964 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3956 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3965 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3957 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3966 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3958 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3967 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3959 user = relationship('User')
3968 user = relationship('User')
3960
3969
3961 def rule_data(self):
3970 def rule_data(self):
3962 return {
3971 return {
3963 'mandatory': self.mandatory
3972 'mandatory': self.mandatory
3964 }
3973 }
3965
3974
3966
3975
3967 class RepoReviewRuleUserGroup(Base, BaseModel):
3976 class RepoReviewRuleUserGroup(Base, BaseModel):
3968 __tablename__ = 'repo_review_rules_users_groups'
3977 __tablename__ = 'repo_review_rules_users_groups'
3969 __table_args__ = (
3978 __table_args__ = (
3970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3971 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3972 )
3981 )
3973 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3982 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3974 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3983 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3975 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3984 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3976 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3985 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3977 users_group = relationship('UserGroup')
3986 users_group = relationship('UserGroup')
3978
3987
3979 def rule_data(self):
3988 def rule_data(self):
3980 return {
3989 return {
3981 'mandatory': self.mandatory
3990 'mandatory': self.mandatory
3982 }
3991 }
3983
3992
3984
3993
3985 class RepoReviewRule(Base, BaseModel):
3994 class RepoReviewRule(Base, BaseModel):
3986 __tablename__ = 'repo_review_rules'
3995 __tablename__ = 'repo_review_rules'
3987 __table_args__ = (
3996 __table_args__ = (
3988 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3989 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3990 )
3999 )
3991
4000
3992 repo_review_rule_id = Column(
4001 repo_review_rule_id = Column(
3993 'repo_review_rule_id', Integer(), primary_key=True)
4002 'repo_review_rule_id', Integer(), primary_key=True)
3994 repo_id = Column(
4003 repo_id = Column(
3995 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4004 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3996 repo = relationship('Repository', backref='review_rules')
4005 repo = relationship('Repository', backref='review_rules')
3997
4006
3998 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4007 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3999 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4008 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4000
4009
4001 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4010 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4002 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4011 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4003 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4012 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4004 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4013 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4005
4014
4006 rule_users = relationship('RepoReviewRuleUser')
4015 rule_users = relationship('RepoReviewRuleUser')
4007 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4016 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4008
4017
4009 @hybrid_property
4018 @hybrid_property
4010 def branch_pattern(self):
4019 def branch_pattern(self):
4011 return self._branch_pattern or '*'
4020 return self._branch_pattern or '*'
4012
4021
4013 def _validate_glob(self, value):
4022 def _validate_glob(self, value):
4014 re.compile('^' + glob2re(value) + '$')
4023 re.compile('^' + glob2re(value) + '$')
4015
4024
4016 @branch_pattern.setter
4025 @branch_pattern.setter
4017 def branch_pattern(self, value):
4026 def branch_pattern(self, value):
4018 self._validate_glob(value)
4027 self._validate_glob(value)
4019 self._branch_pattern = value or '*'
4028 self._branch_pattern = value or '*'
4020
4029
4021 @hybrid_property
4030 @hybrid_property
4022 def file_pattern(self):
4031 def file_pattern(self):
4023 return self._file_pattern or '*'
4032 return self._file_pattern or '*'
4024
4033
4025 @file_pattern.setter
4034 @file_pattern.setter
4026 def file_pattern(self, value):
4035 def file_pattern(self, value):
4027 self._validate_glob(value)
4036 self._validate_glob(value)
4028 self._file_pattern = value or '*'
4037 self._file_pattern = value or '*'
4029
4038
4030 def matches(self, branch, files_changed):
4039 def matches(self, branch, files_changed):
4031 """
4040 """
4032 Check if this review rule matches a branch/files in a pull request
4041 Check if this review rule matches a branch/files in a pull request
4033
4042
4034 :param branch: branch name for the commit
4043 :param branch: branch name for the commit
4035 :param files_changed: list of file paths changed in the pull request
4044 :param files_changed: list of file paths changed in the pull request
4036 """
4045 """
4037
4046
4038 branch = branch or ''
4047 branch = branch or ''
4039 files_changed = files_changed or []
4048 files_changed = files_changed or []
4040
4049
4041 branch_matches = True
4050 branch_matches = True
4042 if branch:
4051 if branch:
4043 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4052 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4044 branch_matches = bool(branch_regex.search(branch))
4053 branch_matches = bool(branch_regex.search(branch))
4045
4054
4046 files_matches = True
4055 files_matches = True
4047 if self.file_pattern != '*':
4056 if self.file_pattern != '*':
4048 files_matches = False
4057 files_matches = False
4049 file_regex = re.compile(glob2re(self.file_pattern))
4058 file_regex = re.compile(glob2re(self.file_pattern))
4050 for filename in files_changed:
4059 for filename in files_changed:
4051 if file_regex.search(filename):
4060 if file_regex.search(filename):
4052 files_matches = True
4061 files_matches = True
4053 break
4062 break
4054
4063
4055 return branch_matches and files_matches
4064 return branch_matches and files_matches
4056
4065
4057 @property
4066 @property
4058 def review_users(self):
4067 def review_users(self):
4059 """ Returns the users which this rule applies to """
4068 """ Returns the users which this rule applies to """
4060
4069
4061 users = collections.OrderedDict()
4070 users = collections.OrderedDict()
4062
4071
4063 for rule_user in self.rule_users:
4072 for rule_user in self.rule_users:
4064 if rule_user.user.active:
4073 if rule_user.user.active:
4065 if rule_user.user not in users:
4074 if rule_user.user not in users:
4066 users[rule_user.user.username] = {
4075 users[rule_user.user.username] = {
4067 'user': rule_user.user,
4076 'user': rule_user.user,
4068 'source': 'user',
4077 'source': 'user',
4069 'source_data': {},
4078 'source_data': {},
4070 'data': rule_user.rule_data()
4079 'data': rule_user.rule_data()
4071 }
4080 }
4072
4081
4073 for rule_user_group in self.rule_user_groups:
4082 for rule_user_group in self.rule_user_groups:
4074 source_data = {
4083 source_data = {
4075 'name': rule_user_group.users_group.users_group_name,
4084 'name': rule_user_group.users_group.users_group_name,
4076 'members': len(rule_user_group.users_group.members)
4085 'members': len(rule_user_group.users_group.members)
4077 }
4086 }
4078 for member in rule_user_group.users_group.members:
4087 for member in rule_user_group.users_group.members:
4079 if member.user.active:
4088 if member.user.active:
4080 users[member.user.username] = {
4089 users[member.user.username] = {
4081 'user': member.user,
4090 'user': member.user,
4082 'source': 'user_group',
4091 'source': 'user_group',
4083 'source_data': source_data,
4092 'source_data': source_data,
4084 'data': rule_user_group.rule_data()
4093 'data': rule_user_group.rule_data()
4085 }
4094 }
4086
4095
4087 return users
4096 return users
4088
4097
4089 def __repr__(self):
4098 def __repr__(self):
4090 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4099 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4091 self.repo_review_rule_id, self.repo)
4100 self.repo_review_rule_id, self.repo)
4092
4101
4093
4102
4094 class DbMigrateVersion(Base, BaseModel):
4103 class DbMigrateVersion(Base, BaseModel):
4095 __tablename__ = 'db_migrate_version'
4104 __tablename__ = 'db_migrate_version'
4096 __table_args__ = (
4105 __table_args__ = (
4097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4098 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4099 )
4108 )
4100 repository_id = Column('repository_id', String(250), primary_key=True)
4109 repository_id = Column('repository_id', String(250), primary_key=True)
4101 repository_path = Column('repository_path', Text)
4110 repository_path = Column('repository_path', Text)
4102 version = Column('version', Integer)
4111 version = Column('version', Integer)
4103
4112
4104
4113
4105 class DbSession(Base, BaseModel):
4114 class DbSession(Base, BaseModel):
4106 __tablename__ = 'db_session'
4115 __tablename__ = 'db_session'
4107 __table_args__ = (
4116 __table_args__ = (
4108 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4117 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4109 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4118 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4110 )
4119 )
4111
4120
4112 def __repr__(self):
4121 def __repr__(self):
4113 return '<DB:DbSession({})>'.format(self.id)
4122 return '<DB:DbSession({})>'.format(self.id)
4114
4123
4115 id = Column('id', Integer())
4124 id = Column('id', Integer())
4116 namespace = Column('namespace', String(255), primary_key=True)
4125 namespace = Column('namespace', String(255), primary_key=True)
4117 accessed = Column('accessed', DateTime, nullable=False)
4126 accessed = Column('accessed', DateTime, nullable=False)
4118 created = Column('created', DateTime, nullable=False)
4127 created = Column('created', DateTime, nullable=False)
4119 data = Column('data', PickleType, nullable=False)
4128 data = Column('data', PickleType, nullable=False)
@@ -1,218 +1,220 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('favicon', '/favicon.ico', []);
17 pyroutes.register('favicon', '/favicon.ico', []);
19 pyroutes.register('robots', '/robots.txt', []);
18 pyroutes.register('robots', '/robots.txt', []);
20 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
21 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
22 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
23 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
24 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
25 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
24 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
26 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
27 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
27 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
29 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
30 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
29 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
31 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
30 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
32 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
32 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
34 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
35 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
34 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
36 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
35 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
37 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
38 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
39 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
40 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
43 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
44 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
52 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
53 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
54 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
55 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
56 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
57 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
58 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
59 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
60 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
61 pyroutes.register('users', '/_admin/users', []);
60 pyroutes.register('users', '/_admin/users', []);
62 pyroutes.register('users_data', '/_admin/users_data', []);
61 pyroutes.register('users_data', '/_admin/users_data', []);
63 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
62 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
63 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
65 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
66 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
65 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
67 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
66 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
68 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
67 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
69 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
68 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
70 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
69 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
71 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
70 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
72 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
71 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
73 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
72 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
74 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
73 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
74 pyroutes.register('user_groups', '/_admin/user_groups', []);
75 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
76 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
75 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
77 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
76 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
78 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
77 pyroutes.register('channelstream_proxy', '/_channelstream', []);
79 pyroutes.register('channelstream_proxy', '/_channelstream', []);
78 pyroutes.register('login', '/_admin/login', []);
80 pyroutes.register('login', '/_admin/login', []);
79 pyroutes.register('logout', '/_admin/logout', []);
81 pyroutes.register('logout', '/_admin/logout', []);
80 pyroutes.register('register', '/_admin/register', []);
82 pyroutes.register('register', '/_admin/register', []);
81 pyroutes.register('reset_password', '/_admin/password_reset', []);
83 pyroutes.register('reset_password', '/_admin/password_reset', []);
82 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
84 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
83 pyroutes.register('home', '/', []);
85 pyroutes.register('home', '/', []);
84 pyroutes.register('user_autocomplete_data', '/_users', []);
86 pyroutes.register('user_autocomplete_data', '/_users', []);
85 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
87 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
86 pyroutes.register('repo_list_data', '/_repos', []);
88 pyroutes.register('repo_list_data', '/_repos', []);
87 pyroutes.register('goto_switcher_data', '/_goto_data', []);
89 pyroutes.register('goto_switcher_data', '/_goto_data', []);
88 pyroutes.register('journal', '/_admin/journal', []);
90 pyroutes.register('journal', '/_admin/journal', []);
89 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
91 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
90 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
92 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
91 pyroutes.register('journal_public', '/_admin/public_journal', []);
93 pyroutes.register('journal_public', '/_admin/public_journal', []);
92 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
94 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
93 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
95 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
94 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
96 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
95 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
97 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
96 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
98 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
97 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
99 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
98 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
100 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
99 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
101 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
100 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
102 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
101 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
103 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
102 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
104 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
103 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
105 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
104 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
105 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
109 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
110 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
109 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
111 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
110 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
112 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
111 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
113 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
112 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
114 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
113 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
117 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
116 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
135 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
134 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
136 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
135 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
137 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
136 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
138 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
137 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
140 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
139 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
141 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
140 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
142 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
141 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
143 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
142 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
144 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
143 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
145 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
144 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
146 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
145 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
147 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
146 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
148 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
147 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
149 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
148 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
150 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
149 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
151 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
150 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
152 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
151 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
153 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
152 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
154 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
153 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
154 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
156 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
157 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
156 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
158 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
157 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
159 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
158 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
160 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
159 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
161 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
160 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
162 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
161 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
163 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
162 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
164 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
163 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
165 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
164 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
166 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
165 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
167 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
166 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
168 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
167 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
169 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
168 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
170 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
169 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
171 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
170 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
172 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
171 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
173 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
172 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
174 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
173 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
175 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
174 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
176 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
175 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
177 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
176 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
178 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
177 pyroutes.register('search', '/_admin/search', []);
179 pyroutes.register('search', '/_admin/search', []);
178 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
180 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
179 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
181 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
180 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
182 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
181 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
183 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
182 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
184 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
183 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
185 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
184 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
186 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
185 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
187 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
186 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
188 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
187 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
189 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
188 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
190 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
189 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
191 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
190 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
192 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
191 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
193 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
192 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
194 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
193 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
195 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
194 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
196 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
195 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
197 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
196 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
198 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
197 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
199 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
198 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
200 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
199 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
201 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
200 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
202 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
201 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
203 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
202 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
204 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
203 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
205 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
204 pyroutes.register('gists_show', '/_admin/gists', []);
206 pyroutes.register('gists_show', '/_admin/gists', []);
205 pyroutes.register('gists_new', '/_admin/gists/new', []);
207 pyroutes.register('gists_new', '/_admin/gists/new', []);
206 pyroutes.register('gists_create', '/_admin/gists/create', []);
208 pyroutes.register('gists_create', '/_admin/gists/create', []);
207 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
209 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
208 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
210 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
209 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
211 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
210 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
212 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
211 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
213 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
212 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
214 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
213 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
215 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
214 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
216 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
215 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
217 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
216 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
218 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
217 pyroutes.register('apiv2', '/_admin/api', []);
219 pyroutes.register('apiv2', '/_admin/api', []);
218 }
220 }
@@ -1,186 +1,186 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
9 ${h.secure_form(h.url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13 <div class="field">
13 <div class="field">
14 <div class="label">
14 <div class="label">
15 <label for="users_group_name">${_('Group name')}:</label>
15 <label for="users_group_name">${_('Group name')}:</label>
16 </div>
16 </div>
17 <div class="input">
17 <div class="input">
18 ${h.text('users_group_name',class_='medium')}
18 ${h.text('users_group_name',class_='medium')}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field badged-field">
22 <div class="field badged-field">
23 <div class="label">
23 <div class="label">
24 <label for="user">${_('Owner')}:</label>
24 <label for="user">${_('Owner')}:</label>
25 </div>
25 </div>
26 <div class="input">
26 <div class="input">
27 <div class="badge-input-container">
27 <div class="badge-input-container">
28 <div class="user-badge">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
30 </div>
30 </div>
31 <div class="badge-input-wrap">
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
33 </div>
34 </div>
34 </div>
35 <form:error name="user"/>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="user_group_description">${_('Description')}:</label>
42 <label for="user_group_description">${_('Description')}:</label>
43 </div>
43 </div>
44 <div class="textarea textarea-small editor">
44 <div class="textarea textarea-small editor">
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
47 </div>
47 </div>
48 </div>
48 </div>
49 <div class="field">
49 <div class="field">
50 <div class="label label-checkbox">
50 <div class="label label-checkbox">
51 <label for="users_group_active">${_('Active')}:</label>
51 <label for="users_group_active">${_('Active')}:</label>
52 </div>
52 </div>
53 <div class="checkboxes">
53 <div class="checkboxes">
54 ${h.checkbox('users_group_active',value=True)}
54 ${h.checkbox('users_group_active',value=True)}
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="field">
58 <div class="field">
59 <div class="label label-checkbox">
59 <div class="label label-checkbox">
60 <label for="users_group_active">${_('Add members')}:</label>
60 <label for="users_group_active">${_('Add members')}:</label>
61 </div>
61 </div>
62 <div class="input">
62 <div class="input">
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
68 <table id="group_members_placeholder" class="rctable group_members">
68 <table id="group_members_placeholder" class="rctable group_members">
69 <tr>
69 <tr>
70 <th>${_('Username')}</th>
70 <th>${_('Username')}</th>
71 <th>${_('Action')}</th>
71 <th>${_('Action')}</th>
72 </tr>
72 </tr>
73
73
74 % if c.group_members_obj:
74 % if c.group_members_obj:
75 % for user in c.group_members_obj:
75 % for user in c.group_members_obj:
76 <tr>
76 <tr>
77 <td id="member_user_${user.user_id}" class="td-author">
77 <td id="member_user_${user.user_id}" class="td-author">
78 <div class="group_member">
78 <div class="group_member">
79 ${base.gravatar(user.email, 16)}
79 ${base.gravatar(user.email, 16)}
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
81 <input type="hidden" name="__start__" value="member:mapping">
81 <input type="hidden" name="__start__" value="member:mapping">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
84 <input type="hidden" name="__end__" value="member:mapping">
84 <input type="hidden" name="__end__" value="member:mapping">
85 </div>
85 </div>
86 </td>
86 </td>
87 <td class="">
87 <td class="">
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
89 <i class="icon-remove-sign"></i>
89 <i class="icon-remove-sign"></i>
90 </div>
90 </div>
91 </td>
91 </td>
92 </tr>
92 </tr>
93 % endfor
93 % endfor
94
94
95 % else:
95 % else:
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
97 % endif
97 % endif
98 </table>
98 </table>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
100
100
101 <div class="buttons">
101 <div class="buttons">
102 ${h.submit('Save',_('Save'),class_="btn")}
102 ${h.submit('Save',_('Save'),class_="btn")}
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 ${h.end_form()}
106 ${h.end_form()}
107 </div>
107 </div>
108 </div>
108 </div>
109 <script>
109 <script>
110 $(document).ready(function(){
110 $(document).ready(function(){
111 $("#group_parent_id").select2({
111 $("#group_parent_id").select2({
112 'containerCssClass': "drop-menu",
112 'containerCssClass': "drop-menu",
113 'dropdownCssClass': "drop-menu-dropdown",
113 'dropdownCssClass': "drop-menu-dropdown",
114 'dropdownAutoWidth': true
114 'dropdownAutoWidth': true
115 });
115 });
116
116
117 removeUserGroupMember = function(userId){
117 removeUserGroupMember = function(userId){
118 $('#member_'+userId).val('remove');
118 $('#member_'+userId).val('remove');
119 $('#member_user_'+userId).addClass('to-delete');
119 $('#member_user_'+userId).addClass('to-delete');
120 };
120 };
121
121
122 $('#user_group_add_members').autocomplete({
122 $('#user_group_add_members').autocomplete({
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
124 minChars:2,
124 minChars:2,
125 maxHeight:400,
125 maxHeight:400,
126 width:300,
126 width:300,
127 deferRequestBy: 300, //miliseconds
127 deferRequestBy: 300, //miliseconds
128 showNoSuggestionNotice: true,
128 showNoSuggestionNotice: true,
129 params: { user_groups:true },
129 params: { user_groups:true },
130 formatResult: autocompleteFormatResult,
130 formatResult: autocompleteFormatResult,
131 lookupFilter: autocompleteFilterResult,
131 lookupFilter: autocompleteFilterResult,
132 onSelect: function(element, suggestion){
132 onSelect: function(element, suggestion){
133
133
134 function addMember(user, fromUserGroup) {
134 function addMember(user, fromUserGroup) {
135 var gravatar = user.icon_link;
135 var gravatar = user.icon_link;
136 var username = user.value_display;
136 var username = user.value_display;
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
138 var uid = user.id;
138 var uid = user.id;
139
139
140 if (fromUserGroup) {
140 if (fromUserGroup) {
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
142 }
142 }
143
143
144 var elem = $(
144 var elem = $(
145 ('<tr>'+
145 ('<tr>'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
147 '<div class="group_member">'+
147 '<div class="group_member">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
154 '</div>'+
154 '</div>'+
155 '</td>'+
155 '</td>'+
156 '<td class="td-author-new-entry">'+
156 '<td class="td-author-new-entry">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
158 '<i class="icon-remove-sign"></i>'+
158 '<i class="icon-remove-sign"></i>'+
159 '</div>'+
159 '</div>'+
160 '</td>'+
160 '</td>'+
161 '</tr>').format(gravatar, userLink, username,
161 '</tr>').format(gravatar, userLink, username,
162 uid, uid, uid, uid)
162 uid, uid, uid, uid)
163 );
163 );
164 $('#group_members_placeholder').append(elem)
164 $('#group_members_placeholder').append(elem)
165 }
165 }
166
166
167 if (suggestion.value_type == 'user_group') {
167 if (suggestion.value_type == 'user_group') {
168 $.getJSON(
168 $.getJSON(
169 pyroutes.url('edit_user_group_members',
169 pyroutes.url('user_group_members_data',
170 {'user_group_id': suggestion.id}),
170 {'user_group_id': suggestion.id}),
171 function(data) {
171 function(data) {
172 $.each(data.members, function(idx, user) {
172 $.each(data.members, function(idx, user) {
173 addMember(user, suggestion.value)
173 addMember(user, suggestion.value)
174 });
174 });
175 }
175 }
176 );
176 );
177 } else if (suggestion.value_type == 'user') {
177 } else if (suggestion.value_type == 'user') {
178 addMember(suggestion, null);
178 addMember(suggestion, null);
179 }
179 }
180 }
180 }
181 });
181 });
182
182
183
183
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
185 })
185 })
186 </script>
186 </script>
@@ -1,100 +1,108 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('User groups administration')}
5 ${_('User groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 <ul class="links">
25 <ul class="links">
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 <li>
27 <li>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 </li>
29 </li>
30 %endif
30 %endif
31 </ul>
31 </ul>
32 </div>
32 </div>
33
33
34 <div id="repos_list_wrap">
34 <div id="repos_list_wrap">
35 <table id="user_group_list_table" class="display"></table>
35 <table id="user_group_list_table" class="display"></table>
36 </div>
36 </div>
37
37
38 </div>
38 </div>
39 <script>
39 <script>
40 $(document).ready(function() {
40 $(document).ready(function() {
41 var getDatatableCount = function(){
42 var table = $('#user_group_list_table').dataTable();
43 var page = table.api().page.info();
44 var active = page.recordsDisplay;
45 var total = page.recordsTotal;
41
46
42 var get_datatable_count = function(){
47 var _text = _gettext("{0} out of {1} users").format(active, total);
43 var api = $('#user_group_list_table').dataTable().api();
48 $('#user_group_count').text(_text);
44 $('#user_group_count').text(api.page.info().recordsDisplay);
45 };
49 };
46
50
47 // user list
51 // user list
48 $('#user_group_list_table').DataTable({
52 $('#user_group_list_table').DataTable({
49 data: ${c.data|n},
53 processing: true,
54 serverSide: true,
55 ajax: "${h.route_path('user_groups_data')}",
50 dom: 'rtp',
56 dom: 'rtp',
51 pageLength: ${c.visual.admin_grid_items},
57 pageLength: ${c.visual.admin_grid_items},
52 order: [[ 0, "asc" ]],
58 order: [[ 0, "asc" ]],
53 columns: [
59 columns: [
54 { data: {"_": "group_name",
60 { data: {"_": "users_group_name",
55 "sort": "group_name_raw"}, title: "${_('Name')}", className: "td-componentname" },
61 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
56 { data: {"_": "desc",
62 { data: {"_": "description",
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
63 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
58 { data: {"_": "members",
64 { data: {"_": "members",
59 "sort": "members",
65 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
60 "type": Number}, title: "${_('Members')}", className: "td-number" },
61 { data: {"_": "sync",
66 { data: {"_": "sync",
62 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
67 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
63 { data: {"_": "active",
68 { data: {"_": "active",
64 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
69 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
65 { data: {"_": "owner",
70 { data: {"_": "owner",
66 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
71 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
67 { data: {"_": "action",
72 { data: {"_": "action",
68 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
69 ],
74 ],
70 language: {
75 language: {
71 paginate: DEFAULT_GRID_PAGINATION,
76 paginate: DEFAULT_GRID_PAGINATION,
77 sProcessing: _gettext('loading...'),
72 emptyTable: _gettext("No user groups available yet.")
78 emptyTable: _gettext("No user groups available yet.")
73 },
74 "initComplete": function( settings, json ) {
75 get_datatable_count();
76 }
79 }
77 });
80 });
78
81
79 // update the counter when doing search
82 $('#user_group_list_table').on('xhr.dt', function(e, settings, json, xhr){
80 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
83 $('#user_group_list_table').css('opacity', 1);
81 get_datatable_count();
84 });
85
86 $('#user_group_list_table').on('preXhr.dt', function(e, settings, data){
87 $('#user_group_list_table').css('opacity', 0.3);
82 });
88 });
83
89
84 // filter, filter both grids
90 // refresh counters on draw
85 $('#q_filter').on( 'keyup', function () {
91 $('#user_group_list_table').on('draw.dt', function(){
86 var user_api = $('#user_group_list_table').dataTable().api();
92 getDatatableCount();
87 user_api
88 .columns(0)
89 .search(this.value)
90 .draw();
91 });
93 });
92
94
93 // refilter table if page load via back button
95 // filter
94 $("#q_filter").trigger('keyup');
96 $('#q_filter').on('keyup',
97 $.debounce(250, function() {
98 $('#user_group_list_table').DataTable().search(
99 $('#q_filter').val()
100 ).draw();
101 })
102 );
95
103
96 });
104 });
97
105
98 </script>
106 </script>
99
107
100 </%def>
108 </%def>
@@ -1,270 +1,225 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import (
23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
25 from rhodecode.model.db import User, UserGroup
25 from rhodecode.model.db import User, UserGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 TEST_USER_GROUP = 'admins_test'
29 TEST_USER_GROUP = 'admins_test'
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 class TestAdminUsersGroupsController(TestController):
34 class TestAdminUsersGroupsController(TestController):
35
35
36 def test_index(self):
37 self.log_user()
38 response = self.app.get(url('users_groups'))
39 assert response.status_int == 200
40
41 def test_create(self):
36 def test_create(self):
42 self.log_user()
37 self.log_user()
43 users_group_name = TEST_USER_GROUP
38 users_group_name = TEST_USER_GROUP
44 response = self.app.post(url('users_groups'), {
39 response = self.app.post(url('users_groups'), {
45 'users_group_name': users_group_name,
40 'users_group_name': users_group_name,
46 'user_group_description': 'DESC',
41 'user_group_description': 'DESC',
47 'active': True,
42 'active': True,
48 'csrf_token': self.csrf_token})
43 'csrf_token': self.csrf_token})
49
44
50 user_group_link = link_to(
45 user_group_link = link_to(
51 users_group_name,
46 users_group_name,
52 url('edit_users_group',
47 url('edit_users_group',
53 user_group_id=UserGroup.get_by_group_name(
48 user_group_id=UserGroup.get_by_group_name(
54 users_group_name).users_group_id))
49 users_group_name).users_group_id))
55 assert_session_flash(
50 assert_session_flash(
56 response,
51 response,
57 'Created user group %s' % user_group_link)
52 'Created user group %s' % user_group_link)
58
53
59 def test_set_synchronization(self):
54 def test_set_synchronization(self):
60 self.log_user()
55 self.log_user()
61 users_group_name = TEST_USER_GROUP + 'sync'
56 users_group_name = TEST_USER_GROUP + 'sync'
62 response = self.app.post(url('users_groups'), {
57 response = self.app.post(url('users_groups'), {
63 'users_group_name': users_group_name,
58 'users_group_name': users_group_name,
64 'user_group_description': 'DESC',
59 'user_group_description': 'DESC',
65 'active': True,
60 'active': True,
66 'csrf_token': self.csrf_token})
61 'csrf_token': self.csrf_token})
67
62
68 group = Session().query(UserGroup).filter(
63 group = Session().query(UserGroup).filter(
69 UserGroup.users_group_name == users_group_name).one()
64 UserGroup.users_group_name == users_group_name).one()
70
65
71 assert group.group_data.get('extern_type') is None
66 assert group.group_data.get('extern_type') is None
72
67
73 # enable
68 # enable
74 self.app.post(
69 self.app.post(
75 url('edit_user_group_advanced_sync', user_group_id=group.users_group_id),
70 url('edit_user_group_advanced_sync', user_group_id=group.users_group_id),
76 params={'csrf_token': self.csrf_token}, status=302)
71 params={'csrf_token': self.csrf_token}, status=302)
77
72
78 group = Session().query(UserGroup).filter(
73 group = Session().query(UserGroup).filter(
79 UserGroup.users_group_name == users_group_name).one()
74 UserGroup.users_group_name == users_group_name).one()
80 assert group.group_data.get('extern_type') == 'manual'
75 assert group.group_data.get('extern_type') == 'manual'
81 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
76 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
82
77
83 # disable
78 # disable
84 self.app.post(
79 self.app.post(
85 url('edit_user_group_advanced_sync',
80 url('edit_user_group_advanced_sync',
86 user_group_id=group.users_group_id),
81 user_group_id=group.users_group_id),
87 params={'csrf_token': self.csrf_token}, status=302)
82 params={'csrf_token': self.csrf_token}, status=302)
88
83
89 group = Session().query(UserGroup).filter(
84 group = Session().query(UserGroup).filter(
90 UserGroup.users_group_name == users_group_name).one()
85 UserGroup.users_group_name == users_group_name).one()
91 assert group.group_data.get('extern_type') is None
86 assert group.group_data.get('extern_type') is None
92 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
87 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
93
88
94 def test_delete(self):
89 def test_delete(self):
95 self.log_user()
90 self.log_user()
96 users_group_name = TEST_USER_GROUP + 'another'
91 users_group_name = TEST_USER_GROUP + 'another'
97 response = self.app.post(url('users_groups'), {
92 response = self.app.post(url('users_groups'), {
98 'users_group_name': users_group_name,
93 'users_group_name': users_group_name,
99 'user_group_description': 'DESC',
94 'user_group_description': 'DESC',
100 'active': True,
95 'active': True,
101 'csrf_token': self.csrf_token})
96 'csrf_token': self.csrf_token})
102
97
103 user_group_link = link_to(
98 user_group_link = link_to(
104 users_group_name,
99 users_group_name,
105 url('edit_users_group',
100 url('edit_users_group',
106 user_group_id=UserGroup.get_by_group_name(
101 user_group_id=UserGroup.get_by_group_name(
107 users_group_name).users_group_id))
102 users_group_name).users_group_id))
108 assert_session_flash(
103 assert_session_flash(
109 response,
104 response,
110 'Created user group %s' % user_group_link)
105 'Created user group %s' % user_group_link)
111
106
112 group = Session().query(UserGroup).filter(
107 group = Session().query(UserGroup).filter(
113 UserGroup.users_group_name == users_group_name).one()
108 UserGroup.users_group_name == users_group_name).one()
114
109
115 self.app.post(
110 self.app.post(
116 url('delete_users_group', user_group_id=group.users_group_id),
111 url('delete_users_group', user_group_id=group.users_group_id),
117 params={'_method': 'delete', 'csrf_token': self.csrf_token})
112 params={'_method': 'delete', 'csrf_token': self.csrf_token})
118
113
119 group = Session().query(UserGroup).filter(
114 group = Session().query(UserGroup).filter(
120 UserGroup.users_group_name == users_group_name).scalar()
115 UserGroup.users_group_name == users_group_name).scalar()
121
116
122 assert group is None
117 assert group is None
123
118
124 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
119 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
125 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
120 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
126 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
121 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
127 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
122 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
128 ('', '', '', '', '', '', True, False),
123 ('', '', '', '', '', '', True, False),
129 ])
124 ])
130 def test_global_perms_on_group(
125 def test_global_perms_on_group(
131 self, repo_create, repo_create_write, user_group_create,
126 self, repo_create, repo_create_write, user_group_create,
132 repo_group_create, fork_create, expect_error, expect_form_error,
127 repo_group_create, fork_create, expect_error, expect_form_error,
133 inherit_default_permissions):
128 inherit_default_permissions):
134 self.log_user()
129 self.log_user()
135 users_group_name = TEST_USER_GROUP + 'another2'
130 users_group_name = TEST_USER_GROUP + 'another2'
136 response = self.app.post(url('users_groups'),
131 response = self.app.post(url('users_groups'),
137 {'users_group_name': users_group_name,
132 {'users_group_name': users_group_name,
138 'user_group_description': 'DESC',
133 'user_group_description': 'DESC',
139 'active': True,
134 'active': True,
140 'csrf_token': self.csrf_token})
135 'csrf_token': self.csrf_token})
141
136
142 ug = UserGroup.get_by_group_name(users_group_name)
137 ug = UserGroup.get_by_group_name(users_group_name)
143 user_group_link = link_to(
138 user_group_link = link_to(
144 users_group_name,
139 users_group_name,
145 url('edit_users_group', user_group_id=ug.users_group_id))
140 url('edit_users_group', user_group_id=ug.users_group_id))
146 assert_session_flash(
141 assert_session_flash(
147 response,
142 response,
148 'Created user group %s' % user_group_link)
143 'Created user group %s' % user_group_link)
149 response.follow()
144 response.follow()
150
145
151 # ENABLE REPO CREATE ON A GROUP
146 # ENABLE REPO CREATE ON A GROUP
152 perm_params = {
147 perm_params = {
153 'inherit_default_permissions': False,
148 'inherit_default_permissions': False,
154 'default_repo_create': repo_create,
149 'default_repo_create': repo_create,
155 'default_repo_create_on_write': repo_create_write,
150 'default_repo_create_on_write': repo_create_write,
156 'default_user_group_create': user_group_create,
151 'default_user_group_create': user_group_create,
157 'default_repo_group_create': repo_group_create,
152 'default_repo_group_create': repo_group_create,
158 'default_fork_create': fork_create,
153 'default_fork_create': fork_create,
159 'default_inherit_default_permissions': inherit_default_permissions,
154 'default_inherit_default_permissions': inherit_default_permissions,
160
155
161 '_method': 'put',
156 '_method': 'put',
162 'csrf_token': self.csrf_token,
157 'csrf_token': self.csrf_token,
163 }
158 }
164 response = self.app.post(
159 response = self.app.post(
165 url('edit_user_group_global_perms',
160 url('edit_user_group_global_perms',
166 user_group_id=ug.users_group_id),
161 user_group_id=ug.users_group_id),
167 params=perm_params)
162 params=perm_params)
168
163
169 if expect_form_error:
164 if expect_form_error:
170 assert response.status_int == 200
165 assert response.status_int == 200
171 response.mustcontain('Value must be one of')
166 response.mustcontain('Value must be one of')
172 else:
167 else:
173 if expect_error:
168 if expect_error:
174 msg = 'An error occurred during permissions saving'
169 msg = 'An error occurred during permissions saving'
175 else:
170 else:
176 msg = 'User Group global permissions updated successfully'
171 msg = 'User Group global permissions updated successfully'
177 ug = UserGroup.get_by_group_name(users_group_name)
172 ug = UserGroup.get_by_group_name(users_group_name)
178 del perm_params['_method']
173 del perm_params['_method']
179 del perm_params['csrf_token']
174 del perm_params['csrf_token']
180 del perm_params['inherit_default_permissions']
175 del perm_params['inherit_default_permissions']
181 assert perm_params == ug.get_default_perms()
176 assert perm_params == ug.get_default_perms()
182 assert_session_flash(response, msg)
177 assert_session_flash(response, msg)
183
178
184 fixture.destroy_user_group(users_group_name)
179 fixture.destroy_user_group(users_group_name)
185
180
186 def test_edit_autocomplete(self):
181 def test_edit_autocomplete(self):
187 self.log_user()
182 self.log_user()
188 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
183 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
189 response = self.app.get(
184 response = self.app.get(
190 url('edit_users_group', user_group_id=ug.users_group_id))
185 url('edit_users_group', user_group_id=ug.users_group_id))
191 fixture.destroy_user_group(TEST_USER_GROUP)
186 fixture.destroy_user_group(TEST_USER_GROUP)
192
187
193 def test_edit_user_group_autocomplete_members(self, xhr_header):
194 self.log_user()
195 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
196 response = self.app.get(
197 url('edit_user_group_members', user_group_id=ug.users_group_id),
198 extra_environ=xhr_header)
199
200 assert response.body == '{"members": []}'
201 fixture.destroy_user_group(TEST_USER_GROUP)
202
203 def test_usergroup_escape(self, user_util):
204 user = user_util.create_user(
205 username='escape_user',
206 firstname='<img src="/image2" onload="alert(\'Hello, World!\');">',
207 lastname='<img src="/image2" onload="alert(\'Hello, World!\');">'
208 )
209
210 user_util.create_user_group(owner=user.username)
211
212 self.log_user()
213 users_group_name = 'samplegroup'
214 data = {
215 'users_group_name': users_group_name,
216 'user_group_description': (
217 '<strong onload="alert();">DESC</strong>'),
218 'active': True,
219 'csrf_token': self.csrf_token
220 }
221
222 self.app.post(url('users_groups'), data)
223 response = self.app.get(url('users_groups'))
224
225 response.mustcontain(
226 '&lt;strong onload=&#34;alert();&#34;&gt;'
227 'DESC&lt;/strong&gt;')
228 # TODO(marcink): fix this test after user-group grid rewrite
229 # response.mustcontain(
230 # '&lt;img src=&#34;/image2&#34; onload=&#34;'
231 # 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
232
233 def test_update_members_from_user_ids(self, user_regular):
188 def test_update_members_from_user_ids(self, user_regular):
234 uid = user_regular.user_id
189 uid = user_regular.user_id
235 username = user_regular.username
190 username = user_regular.username
236 self.log_user()
191 self.log_user()
237
192
238 user_group = fixture.create_user_group('test_gr_ids')
193 user_group = fixture.create_user_group('test_gr_ids')
239 assert user_group.members == []
194 assert user_group.members == []
240 assert user_group.user != user_regular
195 assert user_group.user != user_regular
241 expected_active_state = not user_group.users_group_active
196 expected_active_state = not user_group.users_group_active
242
197
243 form_data = [
198 form_data = [
244 ('csrf_token', self.csrf_token),
199 ('csrf_token', self.csrf_token),
245 ('_method', 'put'),
200 ('_method', 'put'),
246 ('user', username),
201 ('user', username),
247 ('users_group_name', 'changed_name'),
202 ('users_group_name', 'changed_name'),
248 ('users_group_active', expected_active_state),
203 ('users_group_active', expected_active_state),
249 ('user_group_description', 'changed_description'),
204 ('user_group_description', 'changed_description'),
250
205
251 ('__start__', 'user_group_members:sequence'),
206 ('__start__', 'user_group_members:sequence'),
252 ('__start__', 'member:mapping'),
207 ('__start__', 'member:mapping'),
253 ('member_user_id', uid),
208 ('member_user_id', uid),
254 ('type', 'existing'),
209 ('type', 'existing'),
255 ('__end__', 'member:mapping'),
210 ('__end__', 'member:mapping'),
256 ('__end__', 'user_group_members:sequence'),
211 ('__end__', 'user_group_members:sequence'),
257 ]
212 ]
258 ugid = user_group.users_group_id
213 ugid = user_group.users_group_id
259 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
214 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
260
215
261 user_group = UserGroup.get(ugid)
216 user_group = UserGroup.get(ugid)
262 assert user_group
217 assert user_group
263
218
264 assert user_group.members[0].user_id == uid
219 assert user_group.members[0].user_id == uid
265 assert user_group.user_id == uid
220 assert user_group.user_id == uid
266 assert 'changed_name' in user_group.users_group_name
221 assert 'changed_name' in user_group.users_group_name
267 assert 'changed_description' in user_group.user_group_description
222 assert 'changed_description' in user_group.user_group_description
268 assert user_group.users_group_active == expected_active_state
223 assert user_group.users_group_active == expected_active_state
269
224
270 fixture.destroy_user_group(user_group)
225 fixture.destroy_user_group(user_group)
@@ -1,134 +1,151 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import (
23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to)
24 TestController, url, assert_session_flash, link_to)
25 from rhodecode.model.db import User, UserGroup
25 from rhodecode.model.db import User, UserGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29
29
30 def route_path(name, params=None, **kwargs):
31 import urllib
32 from rhodecode.apps._base import ADMIN_PREFIX
33
34 base_url = {
35 'home': '/',
36 'user_groups':
37 ADMIN_PREFIX + '/user_groups',
38 'user_groups_data':
39 ADMIN_PREFIX + '/user_groups_data',
40 }[name].format(**kwargs)
41
42 if params:
43 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
44 return base_url
45
46
30 fixture = Fixture()
47 fixture = Fixture()
31
48
32
49
33 def route_path(name, **kwargs):
50 class TestAdminDelegatedUser(TestController):
34 return {
35 'home': '/',
36 }[name].format(**kwargs)
37
51
38
52 def test_regular_user_cannot_see_admin_interfaces(
39 class TestAdminUsersGroupsController(TestController):
53 self, user_util, xhr_header):
40
41 def test_regular_user_cannot_see_admin_interfaces(self, user_util):
42 user = user_util.create_user(password='qweqwe')
54 user = user_util.create_user(password='qweqwe')
43 self.log_user(user.username, 'qweqwe')
55 self.log_user(user.username, 'qweqwe')
44
56
45 # check if in home view, such user doesn't see the "admin" menus
57 # check if in home view, such user doesn't see the "admin" menus
46 response = self.app.get(route_path('home'))
58 response = self.app.get(route_path('home'))
47
59
48 assert_response = response.assert_response()
60 assert_response = response.assert_response()
49
61
50 assert_response.no_element_exists('li.local-admin-repos')
62 assert_response.no_element_exists('li.local-admin-repos')
51 assert_response.no_element_exists('li.local-admin-repo-groups')
63 assert_response.no_element_exists('li.local-admin-repo-groups')
52 assert_response.no_element_exists('li.local-admin-user-groups')
64 assert_response.no_element_exists('li.local-admin-user-groups')
53
65
54 response = self.app.get(url('repos'), status=200)
66 response = self.app.get(url('repos'), status=200)
55 response.mustcontain('data: []')
67 response.mustcontain('data: []')
56
68
57 response = self.app.get(url('repo_groups'), status=200)
69 response = self.app.get(url('repo_groups'), status=200)
58 response.mustcontain('data: []')
70 response.mustcontain('data: []')
59
71
60 response = self.app.get(url('users_groups'), status=200)
72 response = self.app.get(route_path('user_groups_data'),
61 response.mustcontain('data: []')
73 status=200, extra_environ=xhr_header)
74 assert response.json['data'] == []
62
75
63 def test_regular_user_can_see_admin_interfaces_if_owner(self, user_util):
76 def test_regular_user_can_see_admin_interfaces_if_owner(
77 self, user_util, xhr_header):
64 user = user_util.create_user(password='qweqwe')
78 user = user_util.create_user(password='qweqwe')
65 username = user.username
79 username = user.username
66
80
67 repo = user_util.create_repo(owner=username)
81 repo = user_util.create_repo(owner=username)
68 repo_name = repo.repo_name
82 repo_name = repo.repo_name
69
83
70 repo_group = user_util.create_repo_group(owner=username)
84 repo_group = user_util.create_repo_group(owner=username)
71 repo_group_name = repo_group.group_name
85 repo_group_name = repo_group.group_name
72
86
73 user_group = user_util.create_user_group(owner=username)
87 user_group = user_util.create_user_group(owner=username)
74 user_group_name = user_group.users_group_name
88 user_group_name = user_group.users_group_name
75
89
76 self.log_user(username, 'qweqwe')
90 self.log_user(username, 'qweqwe')
77 # check if in home view, such user doesn't see the "admin" menus
91 # check if in home view, such user doesn't see the "admin" menus
78 response = self.app.get(route_path('home'))
92 response = self.app.get(route_path('home'))
79
93
80 assert_response = response.assert_response()
94 assert_response = response.assert_response()
81
95
82 assert_response.one_element_exists('li.local-admin-repos')
96 assert_response.one_element_exists('li.local-admin-repos')
83 assert_response.one_element_exists('li.local-admin-repo-groups')
97 assert_response.one_element_exists('li.local-admin-repo-groups')
84 assert_response.one_element_exists('li.local-admin-user-groups')
98 assert_response.one_element_exists('li.local-admin-user-groups')
85
99
86 # admin interfaces have visible elements
100 # admin interfaces have visible elements
87 response = self.app.get(url('repos'), status=200)
101 response = self.app.get(url('repos'), status=200)
88 response.mustcontain('"name_raw": "{}"'.format(repo_name))
102 response.mustcontain('"name_raw": "{}"'.format(repo_name))
89
103
90 response = self.app.get(url('repo_groups'), status=200)
104 response = self.app.get(url('repo_groups'), status=200)
91 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
105 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
92
106
93 response = self.app.get(url('users_groups'), status=200)
107 response = self.app.get(route_path('user_groups_data'),
94 response.mustcontain('"group_name_raw": "{}"'.format(user_group_name))
108 extra_environ=xhr_header, status=200)
109 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
95
110
96 def test_regular_user_can_see_admin_interfaces_if_admin_perm(self, user_util):
111 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
112 self, user_util, xhr_header):
97 user = user_util.create_user(password='qweqwe')
113 user = user_util.create_user(password='qweqwe')
98 username = user.username
114 username = user.username
99
115
100 repo = user_util.create_repo()
116 repo = user_util.create_repo()
101 repo_name = repo.repo_name
117 repo_name = repo.repo_name
102
118
103 repo_group = user_util.create_repo_group()
119 repo_group = user_util.create_repo_group()
104 repo_group_name = repo_group.group_name
120 repo_group_name = repo_group.group_name
105
121
106 user_group = user_util.create_user_group()
122 user_group = user_util.create_user_group()
107 user_group_name = user_group.users_group_name
123 user_group_name = user_group.users_group_name
108
124
109 user_util.grant_user_permission_to_repo(
125 user_util.grant_user_permission_to_repo(
110 repo, user, 'repository.admin')
126 repo, user, 'repository.admin')
111 user_util.grant_user_permission_to_repo_group(
127 user_util.grant_user_permission_to_repo_group(
112 repo_group, user, 'group.admin')
128 repo_group, user, 'group.admin')
113 user_util.grant_user_permission_to_user_group(
129 user_util.grant_user_permission_to_user_group(
114 user_group, user, 'usergroup.admin')
130 user_group, user, 'usergroup.admin')
115
131
116 self.log_user(username, 'qweqwe')
132 self.log_user(username, 'qweqwe')
117 # check if in home view, such user doesn't see the "admin" menus
133 # check if in home view, such user doesn't see the "admin" menus
118 response = self.app.get(route_path('home'))
134 response = self.app.get(route_path('home'))
119
135
120 assert_response = response.assert_response()
136 assert_response = response.assert_response()
121
137
122 assert_response.one_element_exists('li.local-admin-repos')
138 assert_response.one_element_exists('li.local-admin-repos')
123 assert_response.one_element_exists('li.local-admin-repo-groups')
139 assert_response.one_element_exists('li.local-admin-repo-groups')
124 assert_response.one_element_exists('li.local-admin-user-groups')
140 assert_response.one_element_exists('li.local-admin-user-groups')
125
141
126 # admin interfaces have visible elements
142 # admin interfaces have visible elements
127 response = self.app.get(url('repos'), status=200)
143 response = self.app.get(url('repos'), status=200)
128 response.mustcontain('"name_raw": "{}"'.format(repo_name))
144 response.mustcontain('"name_raw": "{}"'.format(repo_name))
129
145
130 response = self.app.get(url('repo_groups'), status=200)
146 response = self.app.get(url('repo_groups'), status=200)
131 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
147 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
132
148
133 response = self.app.get(url('users_groups'), status=200)
149 response = self.app.get(route_path('user_groups_data'),
134 response.mustcontain('"group_name_raw": "{}"'.format(user_group_name))
150 extra_environ=xhr_header, status=200)
151 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
General Comments 0
You need to be logged in to leave comments. Login now