##// END OF EJS Templates
my-account: use audit logs for email and token actions.
marcink -
r1820:0c30378e default
parent child Browse files
Show More
@@ -1,111 +1,111 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.apps._base import ADMIN_PREFIX
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.tests import (
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
27 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash)
27 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
29 from rhodecode.tests.utils import AssertResponse
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 def route_path(name, **kwargs):
34 def route_path(name, **kwargs):
35 return {
35 return {
36 'my_account_auth_tokens':
36 'my_account_auth_tokens':
37 ADMIN_PREFIX + '/my_account/auth_tokens',
37 ADMIN_PREFIX + '/my_account/auth_tokens',
38 'my_account_auth_tokens_add':
38 'my_account_auth_tokens_add':
39 ADMIN_PREFIX + '/my_account/auth_tokens/new',
39 ADMIN_PREFIX + '/my_account/auth_tokens/new',
40 'my_account_auth_tokens_delete':
40 'my_account_auth_tokens_delete':
41 ADMIN_PREFIX + '/my_account/auth_tokens/delete',
41 ADMIN_PREFIX + '/my_account/auth_tokens/delete',
42 }[name].format(**kwargs)
42 }[name].format(**kwargs)
43
43
44
44
45 class TestMyAccountAuthTokens(TestController):
45 class TestMyAccountAuthTokens(TestController):
46
46
47 def test_my_account_auth_tokens(self):
47 def test_my_account_auth_tokens(self):
48 usr = self.log_user('test_regular2', 'test12')
48 usr = self.log_user('test_regular2', 'test12')
49 user = User.get(usr['user_id'])
49 user = User.get(usr['user_id'])
50 response = self.app.get(route_path('my_account_auth_tokens'))
50 response = self.app.get(route_path('my_account_auth_tokens'))
51 for token in user.auth_tokens:
51 for token in user.auth_tokens:
52 response.mustcontain(token)
52 response.mustcontain(token)
53 response.mustcontain('never')
53 response.mustcontain('never')
54
54
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
56 user = user_util.create_user(password='qweqwe')
56 user = user_util.create_user(password='qweqwe')
57 self.log_user(user.username, 'qweqwe')
57 self.log_user(user.username, 'qweqwe')
58
58
59 self.app.post(
59 self.app.post(
60 route_path('my_account_auth_tokens_add'),
60 route_path('my_account_auth_tokens_add'),
61 {'description': 'desc', 'lifetime': -1}, status=403)
61 {'description': 'desc', 'lifetime': -1}, status=403)
62
62
63 @pytest.mark.parametrize("desc, lifetime", [
63 @pytest.mark.parametrize("desc, lifetime", [
64 ('forever', -1),
64 ('forever', -1),
65 ('5mins', 60*5),
65 ('5mins', 60*5),
66 ('30days', 60*60*24*30),
66 ('30days', 60*60*24*30),
67 ])
67 ])
68 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
68 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
69 user = user_util.create_user(password='qweqwe')
69 user = user_util.create_user(password='qweqwe')
70 user_id = user.user_id
70 user_id = user.user_id
71 self.log_user(user.username, 'qweqwe')
71 self.log_user(user.username, 'qweqwe')
72
72
73 response = self.app.post(
73 response = self.app.post(
74 route_path('my_account_auth_tokens_add'),
74 route_path('my_account_auth_tokens_add'),
75 {'description': desc, 'lifetime': lifetime,
75 {'description': desc, 'lifetime': lifetime,
76 'csrf_token': self.csrf_token})
76 'csrf_token': self.csrf_token})
77 assert_session_flash(response, 'Auth token successfully created')
77 assert_session_flash(response, 'Auth token successfully created')
78
78
79 response = response.follow()
79 response = response.follow()
80 user = User.get(user_id)
80 user = User.get(user_id)
81 for auth_token in user.auth_tokens:
81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
82 response.mustcontain(auth_token)
83
83
84 def test_my_account_delete_auth_token(self, user_util):
84 def test_my_account_delete_auth_token(self, user_util):
85 user = user_util.create_user(password='qweqwe')
85 user = user_util.create_user(password='qweqwe')
86 user_id = user.user_id
86 user_id = user.user_id
87 self.log_user(user.username, 'qweqwe')
87 self.log_user(user.username, 'qweqwe')
88
88
89 user = User.get(user_id)
89 user = User.get(user_id)
90 keys = user.extra_auth_tokens
90 keys = user.extra_auth_tokens
91 assert 2 == len(keys)
91 assert 2 == len(keys)
92
92
93 response = self.app.post(
93 response = self.app.post(
94 route_path('my_account_auth_tokens_add'),
94 route_path('my_account_auth_tokens_add'),
95 {'description': 'desc', 'lifetime': -1,
95 {'description': 'desc', 'lifetime': -1,
96 'csrf_token': self.csrf_token})
96 'csrf_token': self.csrf_token})
97 assert_session_flash(response, 'Auth token successfully created')
97 assert_session_flash(response, 'Auth token successfully created')
98 response.follow()
98 response.follow()
99
99
100 user = User.get(user_id)
100 user = User.get(user_id)
101 keys = user.extra_auth_tokens
101 keys = user.extra_auth_tokens
102 assert 3 == len(keys)
102 assert 3 == len(keys)
103
103
104 response = self.app.post(
104 response = self.app.post(
105 route_path('my_account_auth_tokens_delete'),
105 route_path('my_account_auth_tokens_delete'),
106 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
106 {'del_auth_token': keys[0].user_api_key_id, 'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'Auth token successfully deleted')
107 assert_session_flash(response, 'Auth token successfully deleted')
108
108
109 user = User.get(user_id)
109 user = User.get(user_id)
110 keys = user.extra_auth_tokens
110 keys = user.extra_auth_tokens
111 assert 2 == len(keys)
111 assert 2 == len(keys)
@@ -1,378 +1,400 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
23
24 import formencode
24 import formencode
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
27
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode import forms
29 from rhodecode import forms
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
33 from rhodecode.lib.channelstream import channelstream_request, \
34 from rhodecode.lib.channelstream import channelstream_request, \
34 ChannelstreamException
35 ChannelstreamException
35 from rhodecode.lib.utils2 import safe_int, md5
36 from rhodecode.lib.utils2 import safe_int, md5
36 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.db import (
38 from rhodecode.model.db import (
38 Repository, PullRequest, UserEmailMap, User, UserFollowing, joinedload)
39 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
39 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
40 from rhodecode.model.scm import RepoList
41 from rhodecode.model.scm import RepoList
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.validation_schema.schemas import user_schema
44 from rhodecode.model.validation_schema.schemas import user_schema
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47
48
48 class MyAccountView(BaseAppView):
49 class MyAccountView(BaseAppView):
49 ALLOW_SCOPED_TOKENS = False
50 ALLOW_SCOPED_TOKENS = False
50 """
51 """
51 This view has alternative version inside EE, if modified please take a look
52 This view has alternative version inside EE, if modified please take a look
52 in there as well.
53 in there as well.
53 """
54 """
54
55
55 def load_default_context(self):
56 def load_default_context(self):
56 c = self._get_local_tmpl_context()
57 c = self._get_local_tmpl_context()
57 c.user = c.auth_user.get_instance()
58 c.user = c.auth_user.get_instance()
58 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
59 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
59 self._register_global_c(c)
60 self._register_global_c(c)
60 return c
61 return c
61
62
62 @LoginRequired()
63 @LoginRequired()
63 @NotAnonymous()
64 @NotAnonymous()
64 @view_config(
65 @view_config(
65 route_name='my_account_profile', request_method='GET',
66 route_name='my_account_profile', request_method='GET',
66 renderer='rhodecode:templates/admin/my_account/my_account.mako')
67 renderer='rhodecode:templates/admin/my_account/my_account.mako')
67 def my_account_profile(self):
68 def my_account_profile(self):
68 c = self.load_default_context()
69 c = self.load_default_context()
69 c.active = 'profile'
70 c.active = 'profile'
70 return self._get_template_context(c)
71 return self._get_template_context(c)
71
72
72 @LoginRequired()
73 @LoginRequired()
73 @NotAnonymous()
74 @NotAnonymous()
74 @view_config(
75 @view_config(
75 route_name='my_account_password', request_method='GET',
76 route_name='my_account_password', request_method='GET',
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 def my_account_password(self):
78 def my_account_password(self):
78 c = self.load_default_context()
79 c = self.load_default_context()
79 c.active = 'password'
80 c.active = 'password'
80 c.extern_type = c.user.extern_type
81 c.extern_type = c.user.extern_type
81
82
82 schema = user_schema.ChangePasswordSchema().bind(
83 schema = user_schema.ChangePasswordSchema().bind(
83 username=c.user.username)
84 username=c.user.username)
84
85
85 form = forms.Form(
86 form = forms.Form(
86 schema, buttons=(forms.buttons.save, forms.buttons.reset))
87 schema, buttons=(forms.buttons.save, forms.buttons.reset))
87
88
88 c.form = form
89 c.form = form
89 return self._get_template_context(c)
90 return self._get_template_context(c)
90
91
91 @LoginRequired()
92 @LoginRequired()
92 @NotAnonymous()
93 @NotAnonymous()
93 @CSRFRequired()
94 @CSRFRequired()
94 @view_config(
95 @view_config(
95 route_name='my_account_password', request_method='POST',
96 route_name='my_account_password', request_method='POST',
96 renderer='rhodecode:templates/admin/my_account/my_account.mako')
97 renderer='rhodecode:templates/admin/my_account/my_account.mako')
97 def my_account_password_update(self):
98 def my_account_password_update(self):
98 _ = self.request.translate
99 _ = self.request.translate
99 c = self.load_default_context()
100 c = self.load_default_context()
100 c.active = 'password'
101 c.active = 'password'
101 c.extern_type = c.user.extern_type
102 c.extern_type = c.user.extern_type
102
103
103 schema = user_schema.ChangePasswordSchema().bind(
104 schema = user_schema.ChangePasswordSchema().bind(
104 username=c.user.username)
105 username=c.user.username)
105
106
106 form = forms.Form(
107 form = forms.Form(
107 schema, buttons=(forms.buttons.save, forms.buttons.reset))
108 schema, buttons=(forms.buttons.save, forms.buttons.reset))
108
109
109 if c.extern_type != 'rhodecode':
110 if c.extern_type != 'rhodecode':
110 raise HTTPFound(self.request.route_path('my_account_password'))
111 raise HTTPFound(self.request.route_path('my_account_password'))
111
112
112 controls = self.request.POST.items()
113 controls = self.request.POST.items()
113 try:
114 try:
114 valid_data = form.validate(controls)
115 valid_data = form.validate(controls)
115 UserModel().update_user(c.user.user_id, **valid_data)
116 UserModel().update_user(c.user.user_id, **valid_data)
116 c.user.update_userdata(force_password_change=False)
117 c.user.update_userdata(force_password_change=False)
117 Session().commit()
118 Session().commit()
118 except forms.ValidationFailure as e:
119 except forms.ValidationFailure as e:
119 c.form = e
120 c.form = e
120 return self._get_template_context(c)
121 return self._get_template_context(c)
121
122
122 except Exception:
123 except Exception:
123 log.exception("Exception updating password")
124 log.exception("Exception updating password")
124 h.flash(_('Error occurred during update of user password'),
125 h.flash(_('Error occurred during update of user password'),
125 category='error')
126 category='error')
126 else:
127 else:
127 instance = c.auth_user.get_instance()
128 instance = c.auth_user.get_instance()
128 self.session.setdefault('rhodecode_user', {}).update(
129 self.session.setdefault('rhodecode_user', {}).update(
129 {'password': md5(instance.password)})
130 {'password': md5(instance.password)})
130 self.session.save()
131 self.session.save()
131 h.flash(_("Successfully updated password"), category='success')
132 h.flash(_("Successfully updated password"), category='success')
132
133
133 raise HTTPFound(self.request.route_path('my_account_password'))
134 raise HTTPFound(self.request.route_path('my_account_password'))
134
135
135 @LoginRequired()
136 @LoginRequired()
136 @NotAnonymous()
137 @NotAnonymous()
137 @view_config(
138 @view_config(
138 route_name='my_account_auth_tokens', request_method='GET',
139 route_name='my_account_auth_tokens', request_method='GET',
139 renderer='rhodecode:templates/admin/my_account/my_account.mako')
140 renderer='rhodecode:templates/admin/my_account/my_account.mako')
140 def my_account_auth_tokens(self):
141 def my_account_auth_tokens(self):
141 _ = self.request.translate
142 _ = self.request.translate
142
143
143 c = self.load_default_context()
144 c = self.load_default_context()
144 c.active = 'auth_tokens'
145 c.active = 'auth_tokens'
145
146
146 c.lifetime_values = [
147 c.lifetime_values = [
147 (str(-1), _('forever')),
148 (str(-1), _('forever')),
148 (str(5), _('5 minutes')),
149 (str(5), _('5 minutes')),
149 (str(60), _('1 hour')),
150 (str(60), _('1 hour')),
150 (str(60 * 24), _('1 day')),
151 (str(60 * 24), _('1 day')),
151 (str(60 * 24 * 30), _('1 month')),
152 (str(60 * 24 * 30), _('1 month')),
152 ]
153 ]
153 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
154 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
154 c.role_values = [
155 c.role_values = [
155 (x, AuthTokenModel.cls._get_role_name(x))
156 (x, AuthTokenModel.cls._get_role_name(x))
156 for x in AuthTokenModel.cls.ROLES]
157 for x in AuthTokenModel.cls.ROLES]
157 c.role_options = [(c.role_values, _("Role"))]
158 c.role_options = [(c.role_values, _("Role"))]
158 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
159 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
159 c.user.user_id, show_expired=True)
160 c.user.user_id, show_expired=True)
160 return self._get_template_context(c)
161 return self._get_template_context(c)
161
162
162 def maybe_attach_token_scope(self, token):
163 def maybe_attach_token_scope(self, token):
163 # implemented in EE edition
164 # implemented in EE edition
164 pass
165 pass
165
166
166 @LoginRequired()
167 @LoginRequired()
167 @NotAnonymous()
168 @NotAnonymous()
168 @CSRFRequired()
169 @CSRFRequired()
169 @view_config(
170 @view_config(
170 route_name='my_account_auth_tokens_add', request_method='POST',)
171 route_name='my_account_auth_tokens_add', request_method='POST',)
171 def my_account_auth_tokens_add(self):
172 def my_account_auth_tokens_add(self):
172 _ = self.request.translate
173 _ = self.request.translate
173 c = self.load_default_context()
174 c = self.load_default_context()
174
175
175 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
176 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
176 description = self.request.POST.get('description')
177 description = self.request.POST.get('description')
177 role = self.request.POST.get('role')
178 role = self.request.POST.get('role')
178
179
179 token = AuthTokenModel().create(
180 token = AuthTokenModel().create(
180 c.user.user_id, description, lifetime, role)
181 c.user.user_id, description, lifetime, role)
182 token_data = token.get_api_data()
183
181 self.maybe_attach_token_scope(token)
184 self.maybe_attach_token_scope(token)
185 audit_logger.store(
186 action='user.edit.token.add',
187 action_data={'data': {'token': token_data}},
188 user=self._rhodecode_user, )
182 Session().commit()
189 Session().commit()
183
190
184 h.flash(_("Auth token successfully created"), category='success')
191 h.flash(_("Auth token successfully created"), category='success')
185 return HTTPFound(h.route_path('my_account_auth_tokens'))
192 return HTTPFound(h.route_path('my_account_auth_tokens'))
186
193
187 @LoginRequired()
194 @LoginRequired()
188 @NotAnonymous()
195 @NotAnonymous()
189 @CSRFRequired()
196 @CSRFRequired()
190 @view_config(
197 @view_config(
191 route_name='my_account_auth_tokens_delete', request_method='POST')
198 route_name='my_account_auth_tokens_delete', request_method='POST')
192 def my_account_auth_tokens_delete(self):
199 def my_account_auth_tokens_delete(self):
193 _ = self.request.translate
200 _ = self.request.translate
194 c = self.load_default_context()
201 c = self.load_default_context()
195
202
196 del_auth_token = self.request.POST.get('del_auth_token')
203 del_auth_token = self.request.POST.get('del_auth_token')
197
204
198 if del_auth_token:
205 if del_auth_token:
206 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
207 token_data = token.get_api_data()
208
199 AuthTokenModel().delete(del_auth_token, c.user.user_id)
209 AuthTokenModel().delete(del_auth_token, c.user.user_id)
210 audit_logger.store(
211 action='user.edit.token.delete',
212 action_data={'data': {'token': token_data}},
213 user=self._rhodecode_user,)
200 Session().commit()
214 Session().commit()
201 h.flash(_("Auth token successfully deleted"), category='success')
215 h.flash(_("Auth token successfully deleted"), category='success')
202
216
203 return HTTPFound(h.route_path('my_account_auth_tokens'))
217 return HTTPFound(h.route_path('my_account_auth_tokens'))
204
218
205 @LoginRequired()
219 @LoginRequired()
206 @NotAnonymous()
220 @NotAnonymous()
207 @view_config(
221 @view_config(
208 route_name='my_account_emails', request_method='GET',
222 route_name='my_account_emails', request_method='GET',
209 renderer='rhodecode:templates/admin/my_account/my_account.mako')
223 renderer='rhodecode:templates/admin/my_account/my_account.mako')
210 def my_account_emails(self):
224 def my_account_emails(self):
211 _ = self.request.translate
225 _ = self.request.translate
212
226
213 c = self.load_default_context()
227 c = self.load_default_context()
214 c.active = 'emails'
228 c.active = 'emails'
215
229
216 c.user_email_map = UserEmailMap.query()\
230 c.user_email_map = UserEmailMap.query()\
217 .filter(UserEmailMap.user == c.user).all()
231 .filter(UserEmailMap.user == c.user).all()
218 return self._get_template_context(c)
232 return self._get_template_context(c)
219
233
220 @LoginRequired()
234 @LoginRequired()
221 @NotAnonymous()
235 @NotAnonymous()
222 @CSRFRequired()
236 @CSRFRequired()
223 @view_config(
237 @view_config(
224 route_name='my_account_emails_add', request_method='POST')
238 route_name='my_account_emails_add', request_method='POST')
225 def my_account_emails_add(self):
239 def my_account_emails_add(self):
226 _ = self.request.translate
240 _ = self.request.translate
227 c = self.load_default_context()
241 c = self.load_default_context()
228
242
229 email = self.request.POST.get('new_email')
243 email = self.request.POST.get('new_email')
230
244
231 try:
245 try:
232 UserModel().add_extra_email(c.user.user_id, email)
246 UserModel().add_extra_email(c.user.user_id, email)
247 audit_logger.store(
248 action='user.edit.email.add',
249 action_data={'data': {'email': email}},
250 user=self._rhodecode_user,)
251
233 Session().commit()
252 Session().commit()
234 h.flash(_("Added new email address `%s` for user account") % email,
253 h.flash(_("Added new email address `%s` for user account") % email,
235 category='success')
254 category='success')
236 except formencode.Invalid as error:
255 except formencode.Invalid as error:
237 msg = error.error_dict['email']
256 msg = error.error_dict['email']
238 h.flash(msg, category='error')
257 h.flash(msg, category='error')
239 except Exception:
258 except Exception:
240 log.exception("Exception in my_account_emails")
259 log.exception("Exception in my_account_emails")
241 h.flash(_('An error occurred during email saving'),
260 h.flash(_('An error occurred during email saving'),
242 category='error')
261 category='error')
243 return HTTPFound(h.route_path('my_account_emails'))
262 return HTTPFound(h.route_path('my_account_emails'))
244
263
245 @LoginRequired()
264 @LoginRequired()
246 @NotAnonymous()
265 @NotAnonymous()
247 @CSRFRequired()
266 @CSRFRequired()
248 @view_config(
267 @view_config(
249 route_name='my_account_emails_delete', request_method='POST')
268 route_name='my_account_emails_delete', request_method='POST')
250 def my_account_emails_delete(self):
269 def my_account_emails_delete(self):
251 _ = self.request.translate
270 _ = self.request.translate
252 c = self.load_default_context()
271 c = self.load_default_context()
253
272
254 del_email_id = self.request.POST.get('del_email_id')
273 del_email_id = self.request.POST.get('del_email_id')
255 if del_email_id:
274 if del_email_id:
256
275 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
257 UserModel().delete_extra_email(
276 UserModel().delete_extra_email(c.user.user_id, del_email_id)
258 c.user.user_id, del_email_id)
277 audit_logger.store(
278 action='user.edit.email.delete',
279 action_data={'data': {'email': email}},
280 user=self._rhodecode_user,)
259 Session().commit()
281 Session().commit()
260 h.flash(_("Email successfully deleted"),
282 h.flash(_("Email successfully deleted"),
261 category='success')
283 category='success')
262 return HTTPFound(h.route_path('my_account_emails'))
284 return HTTPFound(h.route_path('my_account_emails'))
263
285
264 @LoginRequired()
286 @LoginRequired()
265 @NotAnonymous()
287 @NotAnonymous()
266 @CSRFRequired()
288 @CSRFRequired()
267 @view_config(
289 @view_config(
268 route_name='my_account_notifications_test_channelstream',
290 route_name='my_account_notifications_test_channelstream',
269 request_method='POST', renderer='json_ext')
291 request_method='POST', renderer='json_ext')
270 def my_account_notifications_test_channelstream(self):
292 def my_account_notifications_test_channelstream(self):
271 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
293 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
272 self._rhodecode_user.username, datetime.datetime.now())
294 self._rhodecode_user.username, datetime.datetime.now())
273 payload = {
295 payload = {
274 # 'channel': 'broadcast',
296 # 'channel': 'broadcast',
275 'type': 'message',
297 'type': 'message',
276 'timestamp': datetime.datetime.utcnow(),
298 'timestamp': datetime.datetime.utcnow(),
277 'user': 'system',
299 'user': 'system',
278 'pm_users': [self._rhodecode_user.username],
300 'pm_users': [self._rhodecode_user.username],
279 'message': {
301 'message': {
280 'message': message,
302 'message': message,
281 'level': 'info',
303 'level': 'info',
282 'topic': '/notifications'
304 'topic': '/notifications'
283 }
305 }
284 }
306 }
285
307
286 registry = self.request.registry
308 registry = self.request.registry
287 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
309 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
288 channelstream_config = rhodecode_plugins.get('channelstream', {})
310 channelstream_config = rhodecode_plugins.get('channelstream', {})
289
311
290 try:
312 try:
291 channelstream_request(channelstream_config, [payload], '/message')
313 channelstream_request(channelstream_config, [payload], '/message')
292 except ChannelstreamException as e:
314 except ChannelstreamException as e:
293 log.exception('Failed to send channelstream data')
315 log.exception('Failed to send channelstream data')
294 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
316 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
295 return {"response": 'Channelstream data sent. '
317 return {"response": 'Channelstream data sent. '
296 'You should see a new live message now.'}
318 'You should see a new live message now.'}
297
319
298 def _load_my_repos_data(self, watched=False):
320 def _load_my_repos_data(self, watched=False):
299 if watched:
321 if watched:
300 admin = False
322 admin = False
301 follows_repos = Session().query(UserFollowing)\
323 follows_repos = Session().query(UserFollowing)\
302 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
324 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
303 .options(joinedload(UserFollowing.follows_repository))\
325 .options(joinedload(UserFollowing.follows_repository))\
304 .all()
326 .all()
305 repo_list = [x.follows_repository for x in follows_repos]
327 repo_list = [x.follows_repository for x in follows_repos]
306 else:
328 else:
307 admin = True
329 admin = True
308 repo_list = Repository.get_all_repos(
330 repo_list = Repository.get_all_repos(
309 user_id=self._rhodecode_user.user_id)
331 user_id=self._rhodecode_user.user_id)
310 repo_list = RepoList(repo_list, perm_set=[
332 repo_list = RepoList(repo_list, perm_set=[
311 'repository.read', 'repository.write', 'repository.admin'])
333 'repository.read', 'repository.write', 'repository.admin'])
312
334
313 repos_data = RepoModel().get_repos_as_dict(
335 repos_data = RepoModel().get_repos_as_dict(
314 repo_list=repo_list, admin=admin)
336 repo_list=repo_list, admin=admin)
315 # json used to render the grid
337 # json used to render the grid
316 return json.dumps(repos_data)
338 return json.dumps(repos_data)
317
339
318 @LoginRequired()
340 @LoginRequired()
319 @NotAnonymous()
341 @NotAnonymous()
320 @view_config(
342 @view_config(
321 route_name='my_account_repos', request_method='GET',
343 route_name='my_account_repos', request_method='GET',
322 renderer='rhodecode:templates/admin/my_account/my_account.mako')
344 renderer='rhodecode:templates/admin/my_account/my_account.mako')
323 def my_account_repos(self):
345 def my_account_repos(self):
324 c = self.load_default_context()
346 c = self.load_default_context()
325 c.active = 'repos'
347 c.active = 'repos'
326
348
327 # json used to render the grid
349 # json used to render the grid
328 c.data = self._load_my_repos_data()
350 c.data = self._load_my_repos_data()
329 return self._get_template_context(c)
351 return self._get_template_context(c)
330
352
331 @LoginRequired()
353 @LoginRequired()
332 @NotAnonymous()
354 @NotAnonymous()
333 @view_config(
355 @view_config(
334 route_name='my_account_watched', request_method='GET',
356 route_name='my_account_watched', request_method='GET',
335 renderer='rhodecode:templates/admin/my_account/my_account.mako')
357 renderer='rhodecode:templates/admin/my_account/my_account.mako')
336 def my_account_watched(self):
358 def my_account_watched(self):
337 c = self.load_default_context()
359 c = self.load_default_context()
338 c.active = 'watched'
360 c.active = 'watched'
339
361
340 # json used to render the grid
362 # json used to render the grid
341 c.data = self._load_my_repos_data(watched=True)
363 c.data = self._load_my_repos_data(watched=True)
342 return self._get_template_context(c)
364 return self._get_template_context(c)
343
365
344 @LoginRequired()
366 @LoginRequired()
345 @NotAnonymous()
367 @NotAnonymous()
346 @view_config(
368 @view_config(
347 route_name='my_account_perms', request_method='GET',
369 route_name='my_account_perms', request_method='GET',
348 renderer='rhodecode:templates/admin/my_account/my_account.mako')
370 renderer='rhodecode:templates/admin/my_account/my_account.mako')
349 def my_account_perms(self):
371 def my_account_perms(self):
350 c = self.load_default_context()
372 c = self.load_default_context()
351 c.active = 'perms'
373 c.active = 'perms'
352
374
353 c.perm_user = c.auth_user
375 c.perm_user = c.auth_user
354 return self._get_template_context(c)
376 return self._get_template_context(c)
355
377
356 @LoginRequired()
378 @LoginRequired()
357 @NotAnonymous()
379 @NotAnonymous()
358 @view_config(
380 @view_config(
359 route_name='my_account_notifications', request_method='GET',
381 route_name='my_account_notifications', request_method='GET',
360 renderer='rhodecode:templates/admin/my_account/my_account.mako')
382 renderer='rhodecode:templates/admin/my_account/my_account.mako')
361 def my_notifications(self):
383 def my_notifications(self):
362 c = self.load_default_context()
384 c = self.load_default_context()
363 c.active = 'notifications'
385 c.active = 'notifications'
364
386
365 return self._get_template_context(c)
387 return self._get_template_context(c)
366
388
367 @LoginRequired()
389 @LoginRequired()
368 @NotAnonymous()
390 @NotAnonymous()
369 @CSRFRequired()
391 @CSRFRequired()
370 @view_config(
392 @view_config(
371 route_name='my_account_notifications_toggle_visibility',
393 route_name='my_account_notifications_toggle_visibility',
372 request_method='POST', renderer='json_ext')
394 request_method='POST', renderer='json_ext')
373 def my_notifications_toggle_visibility(self):
395 def my_notifications_toggle_visibility(self):
374 user = self._rhodecode_db_user
396 user = self._rhodecode_db_user
375 new_status = not user.user_data.get('notification_status', True)
397 new_status = not user.user_data.get('notification_status', True)
376 user.update_userdata(notification_status=new_status)
398 user.update_userdata(notification_status=new_status)
377 Session().commit()
399 Session().commit()
378 return user.user_data['notification_status'] No newline at end of file
400 return user.user_data['notification_status']
@@ -1,100 +1,100 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication tokens model for RhodeCode
22 authentication tokens model for RhodeCode
23 """
23 """
24
24
25 import time
25 import time
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29
29
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import UserApiKeys
31 from rhodecode.model.db import UserApiKeys
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 class AuthTokenModel(BaseModel):
37 class AuthTokenModel(BaseModel):
38 cls = UserApiKeys
38 cls = UserApiKeys
39
39
40 def create(self, user, description, lifetime=-1, role=UserApiKeys.ROLE_ALL):
40 def create(self, user, description, lifetime=-1, role=UserApiKeys.ROLE_ALL):
41 """
41 """
42 :param user: user or user_id
42 :param user: user or user_id
43 :param description: description of ApiKey
43 :param description: description of ApiKey
44 :param lifetime: expiration time in minutes
44 :param lifetime: expiration time in minutes
45 :param role: role for the apikey
45 :param role: role for the apikey
46 """
46 """
47 from rhodecode.lib.auth import generate_auth_token
47 from rhodecode.lib.auth import generate_auth_token
48
48
49 user = self._get_user(user)
49 user = self._get_user(user)
50
50
51 new_auth_token = UserApiKeys()
51 new_auth_token = UserApiKeys()
52 new_auth_token.api_key = generate_auth_token(user.username)
52 new_auth_token.api_key = generate_auth_token(user.username)
53 new_auth_token.user_id = user.user_id
53 new_auth_token.user_id = user.user_id
54 new_auth_token.description = description
54 new_auth_token.description = description
55 new_auth_token.role = role
55 new_auth_token.role = role
56 new_auth_token.expires = time.time() + (lifetime * 60) \
56 new_auth_token.expires = time.time() + (lifetime * 60) \
57 if lifetime != -1 else -1
57 if lifetime != -1 else -1
58 Session().add(new_auth_token)
58 Session().add(new_auth_token)
59
59
60 return new_auth_token
60 return new_auth_token
61
61
62 def delete(self, api_key, user=None):
62 def delete(self, auth_token_id, user=None):
63 """
63 """
64 Deletes given api_key, if user is set it also filters the object for
64 Deletes given api_key, if user is set it also filters the object for
65 deletion by given user.
65 deletion by given user.
66 """
66 """
67 api_key = UserApiKeys.query().filter(UserApiKeys.api_key == api_key)
67 auth_token = UserApiKeys.query().filter(
68 UserApiKeys.user_api_key_id == auth_token_id)
68
69
69 if user:
70 if user:
70 user = self._get_user(user)
71 user = self._get_user(user)
71 api_key = api_key.filter(UserApiKeys.user_id == user.user_id)
72 auth_token = auth_token.filter(UserApiKeys.user_id == user.user_id)
72
73 auth_token = auth_token.scalar()
73 api_key = api_key.scalar()
74 try:
74 try:
75 Session().delete(api_key)
75 Session().delete(auth_token)
76 except Exception:
76 except Exception:
77 log.error(traceback.format_exc())
77 log.error(traceback.format_exc())
78 raise
78 raise
79
79
80 def get_auth_tokens(self, user, show_expired=True):
80 def get_auth_tokens(self, user, show_expired=True):
81 user = self._get_user(user)
81 user = self._get_user(user)
82 user_auth_tokens = UserApiKeys.query()\
82 user_auth_tokens = UserApiKeys.query()\
83 .filter(UserApiKeys.user_id == user.user_id)
83 .filter(UserApiKeys.user_id == user.user_id)
84 if not show_expired:
84 if not show_expired:
85 user_auth_tokens = user_auth_tokens\
85 user_auth_tokens = user_auth_tokens\
86 .filter(or_(UserApiKeys.expires == -1,
86 .filter(or_(UserApiKeys.expires == -1,
87 UserApiKeys.expires >= time.time()))
87 UserApiKeys.expires >= time.time()))
88 user_auth_tokens = user_auth_tokens.order_by(
88 user_auth_tokens = user_auth_tokens.order_by(
89 UserApiKeys.user_api_key_id)
89 UserApiKeys.user_api_key_id)
90 return user_auth_tokens
90 return user_auth_tokens
91
91
92 def get_auth_token(self, auth_token):
92 def get_auth_token(self, auth_token):
93 auth_token = UserApiKeys.query().filter(
93 auth_token = UserApiKeys.query().filter(
94 UserApiKeys.api_key == auth_token)
94 UserApiKeys.api_key == auth_token)
95 auth_token = auth_token \
95 auth_token = auth_token \
96 .filter(or_(UserApiKeys.expires == -1,
96 .filter(or_(UserApiKeys.expires == -1,
97 UserApiKeys.expires >= time.time()))\
97 UserApiKeys.expires >= time.time()))\
98 .first()
98 .first()
99
99
100 return auth_token
100 return auth_token
@@ -1,4079 +1,4092 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 beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons.i18n.translation import lazy_ugettext as _
47 from pylons.i18n.translation import lazy_ugettext as _
48 from pyramid.threadlocal import get_current_request
48 from pyramid.threadlocal import get_current_request
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # 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.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
362
362
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
364 # git part is currently hardcoded.
364 # git part is currently hardcoded.
365
365
366 # SVN PATTERNS
366 # SVN PATTERNS
367 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_BRANCH_ID = 'vcs_svn_branch'
368 SVN_TAG_ID = 'vcs_svn_tag'
368 SVN_TAG_ID = 'vcs_svn_tag'
369
369
370 ui_id = Column(
370 ui_id = Column(
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
372 primary_key=True)
372 primary_key=True)
373 ui_section = Column(
373 ui_section = Column(
374 "ui_section", String(255), nullable=True, unique=None, default=None)
374 "ui_section", String(255), nullable=True, unique=None, default=None)
375 ui_key = Column(
375 ui_key = Column(
376 "ui_key", String(255), nullable=True, unique=None, default=None)
376 "ui_key", String(255), nullable=True, unique=None, default=None)
377 ui_value = Column(
377 ui_value = Column(
378 "ui_value", String(255), nullable=True, unique=None, default=None)
378 "ui_value", String(255), nullable=True, unique=None, default=None)
379 ui_active = Column(
379 ui_active = Column(
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
381
381
382 def __repr__(self):
382 def __repr__(self):
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
384 self.ui_key, self.ui_value)
384 self.ui_key, self.ui_value)
385
385
386
386
387 class RepoRhodeCodeSetting(Base, BaseModel):
387 class RepoRhodeCodeSetting(Base, BaseModel):
388 __tablename__ = 'repo_rhodecode_settings'
388 __tablename__ = 'repo_rhodecode_settings'
389 __table_args__ = (
389 __table_args__ = (
390 UniqueConstraint(
390 UniqueConstraint(
391 'app_settings_name', 'repository_id',
391 'app_settings_name', 'repository_id',
392 name='uq_repo_rhodecode_setting_name_repo_id'),
392 name='uq_repo_rhodecode_setting_name_repo_id'),
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
395 )
395 )
396
396
397 repository_id = Column(
397 repository_id = Column(
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
399 nullable=False)
399 nullable=False)
400 app_settings_id = Column(
400 app_settings_id = Column(
401 "app_settings_id", Integer(), nullable=False, unique=True,
401 "app_settings_id", Integer(), nullable=False, unique=True,
402 default=None, primary_key=True)
402 default=None, primary_key=True)
403 app_settings_name = Column(
403 app_settings_name = Column(
404 "app_settings_name", String(255), nullable=True, unique=None,
404 "app_settings_name", String(255), nullable=True, unique=None,
405 default=None)
405 default=None)
406 _app_settings_value = Column(
406 _app_settings_value = Column(
407 "app_settings_value", String(4096), nullable=True, unique=None,
407 "app_settings_value", String(4096), nullable=True, unique=None,
408 default=None)
408 default=None)
409 _app_settings_type = Column(
409 _app_settings_type = Column(
410 "app_settings_type", String(255), nullable=True, unique=None,
410 "app_settings_type", String(255), nullable=True, unique=None,
411 default=None)
411 default=None)
412
412
413 repository = relationship('Repository')
413 repository = relationship('Repository')
414
414
415 def __init__(self, repository_id, key='', val='', type='unicode'):
415 def __init__(self, repository_id, key='', val='', type='unicode'):
416 self.repository_id = repository_id
416 self.repository_id = repository_id
417 self.app_settings_name = key
417 self.app_settings_name = key
418 self.app_settings_type = type
418 self.app_settings_type = type
419 self.app_settings_value = val
419 self.app_settings_value = val
420
420
421 @validates('_app_settings_value')
421 @validates('_app_settings_value')
422 def validate_settings_value(self, key, val):
422 def validate_settings_value(self, key, val):
423 assert type(val) == unicode
423 assert type(val) == unicode
424 return val
424 return val
425
425
426 @hybrid_property
426 @hybrid_property
427 def app_settings_value(self):
427 def app_settings_value(self):
428 v = self._app_settings_value
428 v = self._app_settings_value
429 type_ = self.app_settings_type
429 type_ = self.app_settings_type
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
432 return converter(v)
432 return converter(v)
433
433
434 @app_settings_value.setter
434 @app_settings_value.setter
435 def app_settings_value(self, val):
435 def app_settings_value(self, val):
436 """
436 """
437 Setter that will always make sure we use unicode in app_settings_value
437 Setter that will always make sure we use unicode in app_settings_value
438
438
439 :param val:
439 :param val:
440 """
440 """
441 self._app_settings_value = safe_unicode(val)
441 self._app_settings_value = safe_unicode(val)
442
442
443 @hybrid_property
443 @hybrid_property
444 def app_settings_type(self):
444 def app_settings_type(self):
445 return self._app_settings_type
445 return self._app_settings_type
446
446
447 @app_settings_type.setter
447 @app_settings_type.setter
448 def app_settings_type(self, val):
448 def app_settings_type(self, val):
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
450 if val not in SETTINGS_TYPES:
450 if val not in SETTINGS_TYPES:
451 raise Exception('type must be one of %s got %s'
451 raise Exception('type must be one of %s got %s'
452 % (SETTINGS_TYPES.keys(), val))
452 % (SETTINGS_TYPES.keys(), val))
453 self._app_settings_type = val
453 self._app_settings_type = val
454
454
455 def __unicode__(self):
455 def __unicode__(self):
456 return u"<%s('%s:%s:%s[%s]')>" % (
456 return u"<%s('%s:%s:%s[%s]')>" % (
457 self.__class__.__name__, self.repository.repo_name,
457 self.__class__.__name__, self.repository.repo_name,
458 self.app_settings_name, self.app_settings_value,
458 self.app_settings_name, self.app_settings_value,
459 self.app_settings_type
459 self.app_settings_type
460 )
460 )
461
461
462
462
463 class RepoRhodeCodeUi(Base, BaseModel):
463 class RepoRhodeCodeUi(Base, BaseModel):
464 __tablename__ = 'repo_rhodecode_ui'
464 __tablename__ = 'repo_rhodecode_ui'
465 __table_args__ = (
465 __table_args__ = (
466 UniqueConstraint(
466 UniqueConstraint(
467 'repository_id', 'ui_section', 'ui_key',
467 'repository_id', 'ui_section', 'ui_key',
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
471 )
471 )
472
472
473 repository_id = Column(
473 repository_id = Column(
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
475 nullable=False)
475 nullable=False)
476 ui_id = Column(
476 ui_id = Column(
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
478 primary_key=True)
478 primary_key=True)
479 ui_section = Column(
479 ui_section = Column(
480 "ui_section", String(255), nullable=True, unique=None, default=None)
480 "ui_section", String(255), nullable=True, unique=None, default=None)
481 ui_key = Column(
481 ui_key = Column(
482 "ui_key", String(255), nullable=True, unique=None, default=None)
482 "ui_key", String(255), nullable=True, unique=None, default=None)
483 ui_value = Column(
483 ui_value = Column(
484 "ui_value", String(255), nullable=True, unique=None, default=None)
484 "ui_value", String(255), nullable=True, unique=None, default=None)
485 ui_active = Column(
485 ui_active = Column(
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
487
487
488 repository = relationship('Repository')
488 repository = relationship('Repository')
489
489
490 def __repr__(self):
490 def __repr__(self):
491 return '<%s[%s:%s]%s=>%s]>' % (
491 return '<%s[%s:%s]%s=>%s]>' % (
492 self.__class__.__name__, self.repository.repo_name,
492 self.__class__.__name__, self.repository.repo_name,
493 self.ui_section, self.ui_key, self.ui_value)
493 self.ui_section, self.ui_key, self.ui_value)
494
494
495
495
496 class User(Base, BaseModel):
496 class User(Base, BaseModel):
497 __tablename__ = 'users'
497 __tablename__ = 'users'
498 __table_args__ = (
498 __table_args__ = (
499 UniqueConstraint('username'), UniqueConstraint('email'),
499 UniqueConstraint('username'), UniqueConstraint('email'),
500 Index('u_username_idx', 'username'),
500 Index('u_username_idx', 'username'),
501 Index('u_email_idx', 'email'),
501 Index('u_email_idx', 'email'),
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
504 )
504 )
505 DEFAULT_USER = 'default'
505 DEFAULT_USER = 'default'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
508
508
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
519
519
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
526
526
527 user_log = relationship('UserLog')
527 user_log = relationship('UserLog')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
529
529
530 repositories = relationship('Repository')
530 repositories = relationship('Repository')
531 repository_groups = relationship('RepoGroup')
531 repository_groups = relationship('RepoGroup')
532 user_groups = relationship('UserGroup')
532 user_groups = relationship('UserGroup')
533
533
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
536
536
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
540
540
541 group_member = relationship('UserGroupMember', cascade='all')
541 group_member = relationship('UserGroupMember', cascade='all')
542
542
543 notifications = relationship('UserNotification', cascade='all')
543 notifications = relationship('UserNotification', cascade='all')
544 # notifications assigned to this user
544 # notifications assigned to this user
545 user_created_notifications = relationship('Notification', cascade='all')
545 user_created_notifications = relationship('Notification', cascade='all')
546 # comments created by this user
546 # comments created by this user
547 user_comments = relationship('ChangesetComment', cascade='all')
547 user_comments = relationship('ChangesetComment', cascade='all')
548 # user profile extra info
548 # user profile extra info
549 user_emails = relationship('UserEmailMap', cascade='all')
549 user_emails = relationship('UserEmailMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
552 # gists
552 # gists
553 user_gists = relationship('Gist', cascade='all')
553 user_gists = relationship('Gist', cascade='all')
554 # user pull requests
554 # user pull requests
555 user_pull_requests = relationship('PullRequest', cascade='all')
555 user_pull_requests = relationship('PullRequest', cascade='all')
556 # external identities
556 # external identities
557 extenal_identities = relationship(
557 extenal_identities = relationship(
558 'ExternalIdentity',
558 'ExternalIdentity',
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
560 cascade='all')
560 cascade='all')
561
561
562 def __unicode__(self):
562 def __unicode__(self):
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
564 self.user_id, self.username)
564 self.user_id, self.username)
565
565
566 @hybrid_property
566 @hybrid_property
567 def email(self):
567 def email(self):
568 return self._email
568 return self._email
569
569
570 @email.setter
570 @email.setter
571 def email(self, val):
571 def email(self, val):
572 self._email = val.lower() if val else None
572 self._email = val.lower() if val else None
573
573
574 @hybrid_property
574 @hybrid_property
575 def first_name(self):
575 def first_name(self):
576 from rhodecode.lib import helpers as h
576 from rhodecode.lib import helpers as h
577 if self.name:
577 if self.name:
578 return h.escape(self.name)
578 return h.escape(self.name)
579 return self.name
579 return self.name
580
580
581 @hybrid_property
581 @hybrid_property
582 def last_name(self):
582 def last_name(self):
583 from rhodecode.lib import helpers as h
583 from rhodecode.lib import helpers as h
584 if self.lastname:
584 if self.lastname:
585 return h.escape(self.lastname)
585 return h.escape(self.lastname)
586 return self.lastname
586 return self.lastname
587
587
588 @hybrid_property
588 @hybrid_property
589 def api_key(self):
589 def api_key(self):
590 """
590 """
591 Fetch if exist an auth-token with role ALL connected to this user
591 Fetch if exist an auth-token with role ALL connected to this user
592 """
592 """
593 user_auth_token = UserApiKeys.query()\
593 user_auth_token = UserApiKeys.query()\
594 .filter(UserApiKeys.user_id == self.user_id)\
594 .filter(UserApiKeys.user_id == self.user_id)\
595 .filter(or_(UserApiKeys.expires == -1,
595 .filter(or_(UserApiKeys.expires == -1,
596 UserApiKeys.expires >= time.time()))\
596 UserApiKeys.expires >= time.time()))\
597 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
597 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
598 if user_auth_token:
598 if user_auth_token:
599 user_auth_token = user_auth_token.api_key
599 user_auth_token = user_auth_token.api_key
600
600
601 return user_auth_token
601 return user_auth_token
602
602
603 @api_key.setter
603 @api_key.setter
604 def api_key(self, val):
604 def api_key(self, val):
605 # don't allow to set API key this is deprecated for now
605 # don't allow to set API key this is deprecated for now
606 self._api_key = None
606 self._api_key = None
607
607
608 @property
608 @property
609 def firstname(self):
609 def firstname(self):
610 # alias for future
610 # alias for future
611 return self.name
611 return self.name
612
612
613 @property
613 @property
614 def emails(self):
614 def emails(self):
615 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
615 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
616 return [self.email] + [x.email for x in other]
616 return [self.email] + [x.email for x in other]
617
617
618 @property
618 @property
619 def auth_tokens(self):
619 def auth_tokens(self):
620 return [x.api_key for x in self.extra_auth_tokens]
620 return [x.api_key for x in self.extra_auth_tokens]
621
621
622 @property
622 @property
623 def extra_auth_tokens(self):
623 def extra_auth_tokens(self):
624 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
624 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
625
625
626 @property
626 @property
627 def feed_token(self):
627 def feed_token(self):
628 return self.get_feed_token()
628 return self.get_feed_token()
629
629
630 def get_feed_token(self):
630 def get_feed_token(self):
631 feed_tokens = UserApiKeys.query()\
631 feed_tokens = UserApiKeys.query()\
632 .filter(UserApiKeys.user == self)\
632 .filter(UserApiKeys.user == self)\
633 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
633 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
634 .all()
634 .all()
635 if feed_tokens:
635 if feed_tokens:
636 return feed_tokens[0].api_key
636 return feed_tokens[0].api_key
637 return 'NO_FEED_TOKEN_AVAILABLE'
637 return 'NO_FEED_TOKEN_AVAILABLE'
638
638
639 @classmethod
639 @classmethod
640 def extra_valid_auth_tokens(cls, user, role=None):
640 def extra_valid_auth_tokens(cls, user, role=None):
641 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
641 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
642 .filter(or_(UserApiKeys.expires == -1,
642 .filter(or_(UserApiKeys.expires == -1,
643 UserApiKeys.expires >= time.time()))
643 UserApiKeys.expires >= time.time()))
644 if role:
644 if role:
645 tokens = tokens.filter(or_(UserApiKeys.role == role,
645 tokens = tokens.filter(or_(UserApiKeys.role == role,
646 UserApiKeys.role == UserApiKeys.ROLE_ALL))
646 UserApiKeys.role == UserApiKeys.ROLE_ALL))
647 return tokens.all()
647 return tokens.all()
648
648
649 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
649 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
650 from rhodecode.lib import auth
650 from rhodecode.lib import auth
651
651
652 log.debug('Trying to authenticate user: %s via auth-token, '
652 log.debug('Trying to authenticate user: %s via auth-token, '
653 'and roles: %s', self, roles)
653 'and roles: %s', self, roles)
654
654
655 if not auth_token:
655 if not auth_token:
656 return False
656 return False
657
657
658 crypto_backend = auth.crypto_backend()
658 crypto_backend = auth.crypto_backend()
659
659
660 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
660 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
661 tokens_q = UserApiKeys.query()\
661 tokens_q = UserApiKeys.query()\
662 .filter(UserApiKeys.user_id == self.user_id)\
662 .filter(UserApiKeys.user_id == self.user_id)\
663 .filter(or_(UserApiKeys.expires == -1,
663 .filter(or_(UserApiKeys.expires == -1,
664 UserApiKeys.expires >= time.time()))
664 UserApiKeys.expires >= time.time()))
665
665
666 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
666 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
667
667
668 plain_tokens = []
668 plain_tokens = []
669 hash_tokens = []
669 hash_tokens = []
670
670
671 for token in tokens_q.all():
671 for token in tokens_q.all():
672 # verify scope first
672 # verify scope first
673 if token.repo_id:
673 if token.repo_id:
674 # token has a scope, we need to verify it
674 # token has a scope, we need to verify it
675 if scope_repo_id != token.repo_id:
675 if scope_repo_id != token.repo_id:
676 log.debug(
676 log.debug(
677 'Scope mismatch: token has a set repo scope: %s, '
677 'Scope mismatch: token has a set repo scope: %s, '
678 'and calling scope is:%s, skipping further checks',
678 'and calling scope is:%s, skipping further checks',
679 token.repo, scope_repo_id)
679 token.repo, scope_repo_id)
680 # token has a scope, and it doesn't match, skip token
680 # token has a scope, and it doesn't match, skip token
681 continue
681 continue
682
682
683 if token.api_key.startswith(crypto_backend.ENC_PREF):
683 if token.api_key.startswith(crypto_backend.ENC_PREF):
684 hash_tokens.append(token.api_key)
684 hash_tokens.append(token.api_key)
685 else:
685 else:
686 plain_tokens.append(token.api_key)
686 plain_tokens.append(token.api_key)
687
687
688 is_plain_match = auth_token in plain_tokens
688 is_plain_match = auth_token in plain_tokens
689 if is_plain_match:
689 if is_plain_match:
690 return True
690 return True
691
691
692 for hashed in hash_tokens:
692 for hashed in hash_tokens:
693 # TODO(marcink): this is expensive to calculate, but most secure
693 # TODO(marcink): this is expensive to calculate, but most secure
694 match = crypto_backend.hash_check(auth_token, hashed)
694 match = crypto_backend.hash_check(auth_token, hashed)
695 if match:
695 if match:
696 return True
696 return True
697
697
698 return False
698 return False
699
699
700 @property
700 @property
701 def ip_addresses(self):
701 def ip_addresses(self):
702 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
702 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
703 return [x.ip_addr for x in ret]
703 return [x.ip_addr for x in ret]
704
704
705 @property
705 @property
706 def username_and_name(self):
706 def username_and_name(self):
707 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
707 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
708
708
709 @property
709 @property
710 def username_or_name_or_email(self):
710 def username_or_name_or_email(self):
711 full_name = self.full_name if self.full_name is not ' ' else None
711 full_name = self.full_name if self.full_name is not ' ' else None
712 return self.username or full_name or self.email
712 return self.username or full_name or self.email
713
713
714 @property
714 @property
715 def full_name(self):
715 def full_name(self):
716 return '%s %s' % (self.first_name, self.last_name)
716 return '%s %s' % (self.first_name, self.last_name)
717
717
718 @property
718 @property
719 def full_name_or_username(self):
719 def full_name_or_username(self):
720 return ('%s %s' % (self.first_name, self.last_name)
720 return ('%s %s' % (self.first_name, self.last_name)
721 if (self.first_name and self.last_name) else self.username)
721 if (self.first_name and self.last_name) else self.username)
722
722
723 @property
723 @property
724 def full_contact(self):
724 def full_contact(self):
725 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
725 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
726
726
727 @property
727 @property
728 def short_contact(self):
728 def short_contact(self):
729 return '%s %s' % (self.first_name, self.last_name)
729 return '%s %s' % (self.first_name, self.last_name)
730
730
731 @property
731 @property
732 def is_admin(self):
732 def is_admin(self):
733 return self.admin
733 return self.admin
734
734
735 @property
735 @property
736 def AuthUser(self):
736 def AuthUser(self):
737 """
737 """
738 Returns instance of AuthUser for this user
738 Returns instance of AuthUser for this user
739 """
739 """
740 from rhodecode.lib.auth import AuthUser
740 from rhodecode.lib.auth import AuthUser
741 return AuthUser(user_id=self.user_id, username=self.username)
741 return AuthUser(user_id=self.user_id, username=self.username)
742
742
743 @hybrid_property
743 @hybrid_property
744 def user_data(self):
744 def user_data(self):
745 if not self._user_data:
745 if not self._user_data:
746 return {}
746 return {}
747
747
748 try:
748 try:
749 return json.loads(self._user_data)
749 return json.loads(self._user_data)
750 except TypeError:
750 except TypeError:
751 return {}
751 return {}
752
752
753 @user_data.setter
753 @user_data.setter
754 def user_data(self, val):
754 def user_data(self, val):
755 if not isinstance(val, dict):
755 if not isinstance(val, dict):
756 raise Exception('user_data must be dict, got %s' % type(val))
756 raise Exception('user_data must be dict, got %s' % type(val))
757 try:
757 try:
758 self._user_data = json.dumps(val)
758 self._user_data = json.dumps(val)
759 except Exception:
759 except Exception:
760 log.error(traceback.format_exc())
760 log.error(traceback.format_exc())
761
761
762 @classmethod
762 @classmethod
763 def get_by_username(cls, username, case_insensitive=False,
763 def get_by_username(cls, username, case_insensitive=False,
764 cache=False, identity_cache=False):
764 cache=False, identity_cache=False):
765 session = Session()
765 session = Session()
766
766
767 if case_insensitive:
767 if case_insensitive:
768 q = cls.query().filter(
768 q = cls.query().filter(
769 func.lower(cls.username) == func.lower(username))
769 func.lower(cls.username) == func.lower(username))
770 else:
770 else:
771 q = cls.query().filter(cls.username == username)
771 q = cls.query().filter(cls.username == username)
772
772
773 if cache:
773 if cache:
774 if identity_cache:
774 if identity_cache:
775 val = cls.identity_cache(session, 'username', username)
775 val = cls.identity_cache(session, 'username', username)
776 if val:
776 if val:
777 return val
777 return val
778 else:
778 else:
779 cache_key = "get_user_by_name_%s" % _hash_key(username)
779 cache_key = "get_user_by_name_%s" % _hash_key(username)
780 q = q.options(
780 q = q.options(
781 FromCache("sql_cache_short", cache_key))
781 FromCache("sql_cache_short", cache_key))
782
782
783 return q.scalar()
783 return q.scalar()
784
784
785 @classmethod
785 @classmethod
786 def get_by_auth_token(cls, auth_token, cache=False):
786 def get_by_auth_token(cls, auth_token, cache=False):
787 q = UserApiKeys.query()\
787 q = UserApiKeys.query()\
788 .filter(UserApiKeys.api_key == auth_token)\
788 .filter(UserApiKeys.api_key == auth_token)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791 if cache:
791 if cache:
792 q = q.options(
792 q = q.options(
793 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
793 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
794
794
795 match = q.first()
795 match = q.first()
796 if match:
796 if match:
797 return match.user
797 return match.user
798
798
799 @classmethod
799 @classmethod
800 def get_by_email(cls, email, case_insensitive=False, cache=False):
800 def get_by_email(cls, email, case_insensitive=False, cache=False):
801
801
802 if case_insensitive:
802 if case_insensitive:
803 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
803 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
804
804
805 else:
805 else:
806 q = cls.query().filter(cls.email == email)
806 q = cls.query().filter(cls.email == email)
807
807
808 email_key = _hash_key(email)
808 email_key = _hash_key(email)
809 if cache:
809 if cache:
810 q = q.options(
810 q = q.options(
811 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
811 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
812
812
813 ret = q.scalar()
813 ret = q.scalar()
814 if ret is None:
814 if ret is None:
815 q = UserEmailMap.query()
815 q = UserEmailMap.query()
816 # try fetching in alternate email map
816 # try fetching in alternate email map
817 if case_insensitive:
817 if case_insensitive:
818 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
818 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
819 else:
819 else:
820 q = q.filter(UserEmailMap.email == email)
820 q = q.filter(UserEmailMap.email == email)
821 q = q.options(joinedload(UserEmailMap.user))
821 q = q.options(joinedload(UserEmailMap.user))
822 if cache:
822 if cache:
823 q = q.options(
823 q = q.options(
824 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
824 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
825 ret = getattr(q.scalar(), 'user', None)
825 ret = getattr(q.scalar(), 'user', None)
826
826
827 return ret
827 return ret
828
828
829 @classmethod
829 @classmethod
830 def get_from_cs_author(cls, author):
830 def get_from_cs_author(cls, author):
831 """
831 """
832 Tries to get User objects out of commit author string
832 Tries to get User objects out of commit author string
833
833
834 :param author:
834 :param author:
835 """
835 """
836 from rhodecode.lib.helpers import email, author_name
836 from rhodecode.lib.helpers import email, author_name
837 # Valid email in the attribute passed, see if they're in the system
837 # Valid email in the attribute passed, see if they're in the system
838 _email = email(author)
838 _email = email(author)
839 if _email:
839 if _email:
840 user = cls.get_by_email(_email, case_insensitive=True)
840 user = cls.get_by_email(_email, case_insensitive=True)
841 if user:
841 if user:
842 return user
842 return user
843 # Maybe we can match by username?
843 # Maybe we can match by username?
844 _author = author_name(author)
844 _author = author_name(author)
845 user = cls.get_by_username(_author, case_insensitive=True)
845 user = cls.get_by_username(_author, case_insensitive=True)
846 if user:
846 if user:
847 return user
847 return user
848
848
849 def update_userdata(self, **kwargs):
849 def update_userdata(self, **kwargs):
850 usr = self
850 usr = self
851 old = usr.user_data
851 old = usr.user_data
852 old.update(**kwargs)
852 old.update(**kwargs)
853 usr.user_data = old
853 usr.user_data = old
854 Session().add(usr)
854 Session().add(usr)
855 log.debug('updated userdata with ', kwargs)
855 log.debug('updated userdata with ', kwargs)
856
856
857 def update_lastlogin(self):
857 def update_lastlogin(self):
858 """Update user lastlogin"""
858 """Update user lastlogin"""
859 self.last_login = datetime.datetime.now()
859 self.last_login = datetime.datetime.now()
860 Session().add(self)
860 Session().add(self)
861 log.debug('updated user %s lastlogin', self.username)
861 log.debug('updated user %s lastlogin', self.username)
862
862
863 def update_lastactivity(self):
863 def update_lastactivity(self):
864 """Update user lastactivity"""
864 """Update user lastactivity"""
865 self.last_activity = datetime.datetime.now()
865 self.last_activity = datetime.datetime.now()
866 Session().add(self)
866 Session().add(self)
867 log.debug('updated user %s lastactivity', self.username)
867 log.debug('updated user %s lastactivity', self.username)
868
868
869 def update_password(self, new_password):
869 def update_password(self, new_password):
870 from rhodecode.lib.auth import get_crypt_password
870 from rhodecode.lib.auth import get_crypt_password
871
871
872 self.password = get_crypt_password(new_password)
872 self.password = get_crypt_password(new_password)
873 Session().add(self)
873 Session().add(self)
874
874
875 @classmethod
875 @classmethod
876 def get_first_super_admin(cls):
876 def get_first_super_admin(cls):
877 user = User.query().filter(User.admin == true()).first()
877 user = User.query().filter(User.admin == true()).first()
878 if user is None:
878 if user is None:
879 raise Exception('FATAL: Missing administrative account!')
879 raise Exception('FATAL: Missing administrative account!')
880 return user
880 return user
881
881
882 @classmethod
882 @classmethod
883 def get_all_super_admins(cls):
883 def get_all_super_admins(cls):
884 """
884 """
885 Returns all admin accounts sorted by username
885 Returns all admin accounts sorted by username
886 """
886 """
887 return User.query().filter(User.admin == true())\
887 return User.query().filter(User.admin == true())\
888 .order_by(User.username.asc()).all()
888 .order_by(User.username.asc()).all()
889
889
890 @classmethod
890 @classmethod
891 def get_default_user(cls, cache=False, refresh=False):
891 def get_default_user(cls, cache=False, refresh=False):
892 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
892 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
893 if user is None:
893 if user is None:
894 raise Exception('FATAL: Missing default account!')
894 raise Exception('FATAL: Missing default account!')
895 if refresh:
895 if refresh:
896 # The default user might be based on outdated state which
896 # The default user might be based on outdated state which
897 # has been loaded from the cache.
897 # has been loaded from the cache.
898 # A call to refresh() ensures that the
898 # A call to refresh() ensures that the
899 # latest state from the database is used.
899 # latest state from the database is used.
900 Session().refresh(user)
900 Session().refresh(user)
901 return user
901 return user
902
902
903 def _get_default_perms(self, user, suffix=''):
903 def _get_default_perms(self, user, suffix=''):
904 from rhodecode.model.permission import PermissionModel
904 from rhodecode.model.permission import PermissionModel
905 return PermissionModel().get_default_perms(user.user_perms, suffix)
905 return PermissionModel().get_default_perms(user.user_perms, suffix)
906
906
907 def get_default_perms(self, suffix=''):
907 def get_default_perms(self, suffix=''):
908 return self._get_default_perms(self, suffix)
908 return self._get_default_perms(self, suffix)
909
909
910 def get_api_data(self, include_secrets=False, details='full'):
910 def get_api_data(self, include_secrets=False, details='full'):
911 """
911 """
912 Common function for generating user related data for API
912 Common function for generating user related data for API
913
913
914 :param include_secrets: By default secrets in the API data will be replaced
914 :param include_secrets: By default secrets in the API data will be replaced
915 by a placeholder value to prevent exposing this data by accident. In case
915 by a placeholder value to prevent exposing this data by accident. In case
916 this data shall be exposed, set this flag to ``True``.
916 this data shall be exposed, set this flag to ``True``.
917
917
918 :param details: details can be 'basic|full' basic gives only a subset of
918 :param details: details can be 'basic|full' basic gives only a subset of
919 the available user information that includes user_id, name and emails.
919 the available user information that includes user_id, name and emails.
920 """
920 """
921 user = self
921 user = self
922 user_data = self.user_data
922 user_data = self.user_data
923 data = {
923 data = {
924 'user_id': user.user_id,
924 'user_id': user.user_id,
925 'username': user.username,
925 'username': user.username,
926 'firstname': user.name,
926 'firstname': user.name,
927 'lastname': user.lastname,
927 'lastname': user.lastname,
928 'email': user.email,
928 'email': user.email,
929 'emails': user.emails,
929 'emails': user.emails,
930 }
930 }
931 if details == 'basic':
931 if details == 'basic':
932 return data
932 return data
933
933
934 api_key_length = 40
934 api_key_length = 40
935 api_key_replacement = '*' * api_key_length
935 api_key_replacement = '*' * api_key_length
936
936
937 extras = {
937 extras = {
938 'api_keys': [api_key_replacement],
938 'api_keys': [api_key_replacement],
939 'auth_tokens': [api_key_replacement],
939 'auth_tokens': [api_key_replacement],
940 'active': user.active,
940 'active': user.active,
941 'admin': user.admin,
941 'admin': user.admin,
942 'extern_type': user.extern_type,
942 'extern_type': user.extern_type,
943 'extern_name': user.extern_name,
943 'extern_name': user.extern_name,
944 'last_login': user.last_login,
944 'last_login': user.last_login,
945 'last_activity': user.last_activity,
945 'last_activity': user.last_activity,
946 'ip_addresses': user.ip_addresses,
946 'ip_addresses': user.ip_addresses,
947 'language': user_data.get('language')
947 'language': user_data.get('language')
948 }
948 }
949 data.update(extras)
949 data.update(extras)
950
950
951 if include_secrets:
951 if include_secrets:
952 data['api_keys'] = user.auth_tokens
952 data['api_keys'] = user.auth_tokens
953 data['auth_tokens'] = user.extra_auth_tokens
953 data['auth_tokens'] = user.extra_auth_tokens
954 return data
954 return data
955
955
956 def __json__(self):
956 def __json__(self):
957 data = {
957 data = {
958 'full_name': self.full_name,
958 'full_name': self.full_name,
959 'full_name_or_username': self.full_name_or_username,
959 'full_name_or_username': self.full_name_or_username,
960 'short_contact': self.short_contact,
960 'short_contact': self.short_contact,
961 'full_contact': self.full_contact,
961 'full_contact': self.full_contact,
962 }
962 }
963 data.update(self.get_api_data())
963 data.update(self.get_api_data())
964 return data
964 return data
965
965
966
966
967 class UserApiKeys(Base, BaseModel):
967 class UserApiKeys(Base, BaseModel):
968 __tablename__ = 'user_api_keys'
968 __tablename__ = 'user_api_keys'
969 __table_args__ = (
969 __table_args__ = (
970 Index('uak_api_key_idx', 'api_key'),
970 Index('uak_api_key_idx', 'api_key'),
971 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
971 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
972 UniqueConstraint('api_key'),
972 UniqueConstraint('api_key'),
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
974 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
974 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
975 )
975 )
976 __mapper_args__ = {}
976 __mapper_args__ = {}
977
977
978 # ApiKey role
978 # ApiKey role
979 ROLE_ALL = 'token_role_all'
979 ROLE_ALL = 'token_role_all'
980 ROLE_HTTP = 'token_role_http'
980 ROLE_HTTP = 'token_role_http'
981 ROLE_VCS = 'token_role_vcs'
981 ROLE_VCS = 'token_role_vcs'
982 ROLE_API = 'token_role_api'
982 ROLE_API = 'token_role_api'
983 ROLE_FEED = 'token_role_feed'
983 ROLE_FEED = 'token_role_feed'
984 ROLE_PASSWORD_RESET = 'token_password_reset'
984 ROLE_PASSWORD_RESET = 'token_password_reset'
985
985
986 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
986 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
987
987
988 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)
989 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)
990 api_key = Column("api_key", String(255), nullable=False, unique=True)
990 api_key = Column("api_key", String(255), nullable=False, unique=True)
991 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
991 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
992 expires = Column('expires', Float(53), nullable=False)
992 expires = Column('expires', Float(53), nullable=False)
993 role = Column('role', String(255), nullable=True)
993 role = Column('role', String(255), nullable=True)
994 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)
995
995
996 # scope columns
996 # scope columns
997 repo_id = Column(
997 repo_id = Column(
998 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
998 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
999 nullable=True, unique=None, default=None)
999 nullable=True, unique=None, default=None)
1000 repo = relationship('Repository', lazy='joined')
1000 repo = relationship('Repository', lazy='joined')
1001
1001
1002 repo_group_id = Column(
1002 repo_group_id = Column(
1003 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1003 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1004 nullable=True, unique=None, default=None)
1004 nullable=True, unique=None, default=None)
1005 repo_group = relationship('RepoGroup', lazy='joined')
1005 repo_group = relationship('RepoGroup', lazy='joined')
1006
1006
1007 user = relationship('User', lazy='joined')
1007 user = relationship('User', lazy='joined')
1008
1008
1009 def __unicode__(self):
1009 def __unicode__(self):
1010 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1010 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1011
1011
1012 def __json__(self):
1012 def __json__(self):
1013 data = {
1013 data = {
1014 'auth_token': self.api_key,
1014 'auth_token': self.api_key,
1015 'role': self.role,
1015 'role': self.role,
1016 'scope': self.scope_humanized,
1016 'scope': self.scope_humanized,
1017 'expired': self.expired
1017 'expired': self.expired
1018 }
1018 }
1019 return data
1019 return data
1020
1020
1021 def get_api_data(self, include_secrets=False):
1022 data = self.__json__()
1023 if include_secrets:
1024 return data
1025 else:
1026 data['auth_token'] = self.token_obfuscated
1027 return data
1028
1021 @property
1029 @property
1022 def expired(self):
1030 def expired(self):
1023 if self.expires == -1:
1031 if self.expires == -1:
1024 return False
1032 return False
1025 return time.time() > self.expires
1033 return time.time() > self.expires
1026
1034
1027 @classmethod
1035 @classmethod
1028 def _get_role_name(cls, role):
1036 def _get_role_name(cls, role):
1029 return {
1037 return {
1030 cls.ROLE_ALL: _('all'),
1038 cls.ROLE_ALL: _('all'),
1031 cls.ROLE_HTTP: _('http/web interface'),
1039 cls.ROLE_HTTP: _('http/web interface'),
1032 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1040 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1033 cls.ROLE_API: _('api calls'),
1041 cls.ROLE_API: _('api calls'),
1034 cls.ROLE_FEED: _('feed access'),
1042 cls.ROLE_FEED: _('feed access'),
1035 }.get(role, role)
1043 }.get(role, role)
1036
1044
1037 @property
1045 @property
1038 def role_humanized(self):
1046 def role_humanized(self):
1039 return self._get_role_name(self.role)
1047 return self._get_role_name(self.role)
1040
1048
1041 def _get_scope(self):
1049 def _get_scope(self):
1042 if self.repo:
1050 if self.repo:
1043 return repr(self.repo)
1051 return repr(self.repo)
1044 if self.repo_group:
1052 if self.repo_group:
1045 return repr(self.repo_group) + ' (recursive)'
1053 return repr(self.repo_group) + ' (recursive)'
1046 return 'global'
1054 return 'global'
1047
1055
1048 @property
1056 @property
1049 def scope_humanized(self):
1057 def scope_humanized(self):
1050 return self._get_scope()
1058 return self._get_scope()
1051
1059
1060 @property
1061 def token_obfuscated(self):
1062 if self.api_key:
1063 return self.api_key[:4] + "****"
1064
1052
1065
1053 class UserEmailMap(Base, BaseModel):
1066 class UserEmailMap(Base, BaseModel):
1054 __tablename__ = 'user_email_map'
1067 __tablename__ = 'user_email_map'
1055 __table_args__ = (
1068 __table_args__ = (
1056 Index('uem_email_idx', 'email'),
1069 Index('uem_email_idx', 'email'),
1057 UniqueConstraint('email'),
1070 UniqueConstraint('email'),
1058 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1059 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1072 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1060 )
1073 )
1061 __mapper_args__ = {}
1074 __mapper_args__ = {}
1062
1075
1063 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1076 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1077 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1065 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1078 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1066 user = relationship('User', lazy='joined')
1079 user = relationship('User', lazy='joined')
1067
1080
1068 @validates('_email')
1081 @validates('_email')
1069 def validate_email(self, key, email):
1082 def validate_email(self, key, email):
1070 # check if this email is not main one
1083 # check if this email is not main one
1071 main_email = Session().query(User).filter(User.email == email).scalar()
1084 main_email = Session().query(User).filter(User.email == email).scalar()
1072 if main_email is not None:
1085 if main_email is not None:
1073 raise AttributeError('email %s is present is user table' % email)
1086 raise AttributeError('email %s is present is user table' % email)
1074 return email
1087 return email
1075
1088
1076 @hybrid_property
1089 @hybrid_property
1077 def email(self):
1090 def email(self):
1078 return self._email
1091 return self._email
1079
1092
1080 @email.setter
1093 @email.setter
1081 def email(self, val):
1094 def email(self, val):
1082 self._email = val.lower() if val else None
1095 self._email = val.lower() if val else None
1083
1096
1084
1097
1085 class UserIpMap(Base, BaseModel):
1098 class UserIpMap(Base, BaseModel):
1086 __tablename__ = 'user_ip_map'
1099 __tablename__ = 'user_ip_map'
1087 __table_args__ = (
1100 __table_args__ = (
1088 UniqueConstraint('user_id', 'ip_addr'),
1101 UniqueConstraint('user_id', 'ip_addr'),
1089 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1090 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1091 )
1104 )
1092 __mapper_args__ = {}
1105 __mapper_args__ = {}
1093
1106
1094 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1107 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1108 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1096 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1109 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1097 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1110 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1098 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1111 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1099 user = relationship('User', lazy='joined')
1112 user = relationship('User', lazy='joined')
1100
1113
1101 @classmethod
1114 @classmethod
1102 def _get_ip_range(cls, ip_addr):
1115 def _get_ip_range(cls, ip_addr):
1103 net = ipaddress.ip_network(ip_addr, strict=False)
1116 net = ipaddress.ip_network(ip_addr, strict=False)
1104 return [str(net.network_address), str(net.broadcast_address)]
1117 return [str(net.network_address), str(net.broadcast_address)]
1105
1118
1106 def __json__(self):
1119 def __json__(self):
1107 return {
1120 return {
1108 'ip_addr': self.ip_addr,
1121 'ip_addr': self.ip_addr,
1109 'ip_range': self._get_ip_range(self.ip_addr),
1122 'ip_range': self._get_ip_range(self.ip_addr),
1110 }
1123 }
1111
1124
1112 def __unicode__(self):
1125 def __unicode__(self):
1113 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1126 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1114 self.user_id, self.ip_addr)
1127 self.user_id, self.ip_addr)
1115
1128
1116
1129
1117 class UserLog(Base, BaseModel):
1130 class UserLog(Base, BaseModel):
1118 __tablename__ = 'user_logs'
1131 __tablename__ = 'user_logs'
1119 __table_args__ = (
1132 __table_args__ = (
1120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1133 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1121 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1134 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1122 )
1135 )
1123 VERSION_1 = 'v1'
1136 VERSION_1 = 'v1'
1124 VERSION_2 = 'v2'
1137 VERSION_2 = 'v2'
1125 VERSIONS = [VERSION_1, VERSION_2]
1138 VERSIONS = [VERSION_1, VERSION_2]
1126
1139
1127 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1140 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1128 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1141 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1129 username = Column("username", String(255), nullable=True, unique=None, default=None)
1142 username = Column("username", String(255), nullable=True, unique=None, default=None)
1130 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1143 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1131 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1144 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1132 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1145 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1133 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1146 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1134 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1147 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1135
1148
1136 version = Column("version", String(255), nullable=True, default=VERSION_1)
1149 version = Column("version", String(255), nullable=True, default=VERSION_1)
1137 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1150 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1138 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1151 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1139
1152
1140 def __unicode__(self):
1153 def __unicode__(self):
1141 return u"<%s('id:%s:%s')>" % (
1154 return u"<%s('id:%s:%s')>" % (
1142 self.__class__.__name__, self.repository_name, self.action)
1155 self.__class__.__name__, self.repository_name, self.action)
1143
1156
1144 def __json__(self):
1157 def __json__(self):
1145 return {
1158 return {
1146 'user_id': self.user_id,
1159 'user_id': self.user_id,
1147 'username': self.username,
1160 'username': self.username,
1148 'repository_id': self.repository_id,
1161 'repository_id': self.repository_id,
1149 'repository_name': self.repository_name,
1162 'repository_name': self.repository_name,
1150 'user_ip': self.user_ip,
1163 'user_ip': self.user_ip,
1151 'action_date': self.action_date,
1164 'action_date': self.action_date,
1152 'action': self.action,
1165 'action': self.action,
1153 }
1166 }
1154
1167
1155 @property
1168 @property
1156 def action_as_day(self):
1169 def action_as_day(self):
1157 return datetime.date(*self.action_date.timetuple()[:3])
1170 return datetime.date(*self.action_date.timetuple()[:3])
1158
1171
1159 user = relationship('User')
1172 user = relationship('User')
1160 repository = relationship('Repository', cascade='')
1173 repository = relationship('Repository', cascade='')
1161
1174
1162
1175
1163 class UserGroup(Base, BaseModel):
1176 class UserGroup(Base, BaseModel):
1164 __tablename__ = 'users_groups'
1177 __tablename__ = 'users_groups'
1165 __table_args__ = (
1178 __table_args__ = (
1166 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1167 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1168 )
1181 )
1169
1182
1170 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1183 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1171 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1184 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1172 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1185 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1173 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1186 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1174 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1187 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1175 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1188 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1176 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1189 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1177 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1190 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1178
1191
1179 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1192 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1180 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1193 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1181 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1194 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1182 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1195 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1183 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1196 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1184 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1197 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1185
1198
1186 user = relationship('User')
1199 user = relationship('User')
1187
1200
1188 @hybrid_property
1201 @hybrid_property
1189 def group_data(self):
1202 def group_data(self):
1190 if not self._group_data:
1203 if not self._group_data:
1191 return {}
1204 return {}
1192
1205
1193 try:
1206 try:
1194 return json.loads(self._group_data)
1207 return json.loads(self._group_data)
1195 except TypeError:
1208 except TypeError:
1196 return {}
1209 return {}
1197
1210
1198 @group_data.setter
1211 @group_data.setter
1199 def group_data(self, val):
1212 def group_data(self, val):
1200 try:
1213 try:
1201 self._group_data = json.dumps(val)
1214 self._group_data = json.dumps(val)
1202 except Exception:
1215 except Exception:
1203 log.error(traceback.format_exc())
1216 log.error(traceback.format_exc())
1204
1217
1205 def __unicode__(self):
1218 def __unicode__(self):
1206 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1219 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1207 self.users_group_id,
1220 self.users_group_id,
1208 self.users_group_name)
1221 self.users_group_name)
1209
1222
1210 @classmethod
1223 @classmethod
1211 def get_by_group_name(cls, group_name, cache=False,
1224 def get_by_group_name(cls, group_name, cache=False,
1212 case_insensitive=False):
1225 case_insensitive=False):
1213 if case_insensitive:
1226 if case_insensitive:
1214 q = cls.query().filter(func.lower(cls.users_group_name) ==
1227 q = cls.query().filter(func.lower(cls.users_group_name) ==
1215 func.lower(group_name))
1228 func.lower(group_name))
1216
1229
1217 else:
1230 else:
1218 q = cls.query().filter(cls.users_group_name == group_name)
1231 q = cls.query().filter(cls.users_group_name == group_name)
1219 if cache:
1232 if cache:
1220 q = q.options(
1233 q = q.options(
1221 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1234 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1222 return q.scalar()
1235 return q.scalar()
1223
1236
1224 @classmethod
1237 @classmethod
1225 def get(cls, user_group_id, cache=False):
1238 def get(cls, user_group_id, cache=False):
1226 user_group = cls.query()
1239 user_group = cls.query()
1227 if cache:
1240 if cache:
1228 user_group = user_group.options(
1241 user_group = user_group.options(
1229 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1242 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1230 return user_group.get(user_group_id)
1243 return user_group.get(user_group_id)
1231
1244
1232 def permissions(self, with_admins=True, with_owner=True):
1245 def permissions(self, with_admins=True, with_owner=True):
1233 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1246 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1234 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1247 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1235 joinedload(UserUserGroupToPerm.user),
1248 joinedload(UserUserGroupToPerm.user),
1236 joinedload(UserUserGroupToPerm.permission),)
1249 joinedload(UserUserGroupToPerm.permission),)
1237
1250
1238 # get owners and admins and permissions. We do a trick of re-writing
1251 # get owners and admins and permissions. We do a trick of re-writing
1239 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1252 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1240 # has a global reference and changing one object propagates to all
1253 # has a global reference and changing one object propagates to all
1241 # others. This means if admin is also an owner admin_row that change
1254 # others. This means if admin is also an owner admin_row that change
1242 # would propagate to both objects
1255 # would propagate to both objects
1243 perm_rows = []
1256 perm_rows = []
1244 for _usr in q.all():
1257 for _usr in q.all():
1245 usr = AttributeDict(_usr.user.get_dict())
1258 usr = AttributeDict(_usr.user.get_dict())
1246 usr.permission = _usr.permission.permission_name
1259 usr.permission = _usr.permission.permission_name
1247 perm_rows.append(usr)
1260 perm_rows.append(usr)
1248
1261
1249 # filter the perm rows by 'default' first and then sort them by
1262 # filter the perm rows by 'default' first and then sort them by
1250 # admin,write,read,none permissions sorted again alphabetically in
1263 # admin,write,read,none permissions sorted again alphabetically in
1251 # each group
1264 # each group
1252 perm_rows = sorted(perm_rows, key=display_sort)
1265 perm_rows = sorted(perm_rows, key=display_sort)
1253
1266
1254 _admin_perm = 'usergroup.admin'
1267 _admin_perm = 'usergroup.admin'
1255 owner_row = []
1268 owner_row = []
1256 if with_owner:
1269 if with_owner:
1257 usr = AttributeDict(self.user.get_dict())
1270 usr = AttributeDict(self.user.get_dict())
1258 usr.owner_row = True
1271 usr.owner_row = True
1259 usr.permission = _admin_perm
1272 usr.permission = _admin_perm
1260 owner_row.append(usr)
1273 owner_row.append(usr)
1261
1274
1262 super_admin_rows = []
1275 super_admin_rows = []
1263 if with_admins:
1276 if with_admins:
1264 for usr in User.get_all_super_admins():
1277 for usr in User.get_all_super_admins():
1265 # if this admin is also owner, don't double the record
1278 # if this admin is also owner, don't double the record
1266 if usr.user_id == owner_row[0].user_id:
1279 if usr.user_id == owner_row[0].user_id:
1267 owner_row[0].admin_row = True
1280 owner_row[0].admin_row = True
1268 else:
1281 else:
1269 usr = AttributeDict(usr.get_dict())
1282 usr = AttributeDict(usr.get_dict())
1270 usr.admin_row = True
1283 usr.admin_row = True
1271 usr.permission = _admin_perm
1284 usr.permission = _admin_perm
1272 super_admin_rows.append(usr)
1285 super_admin_rows.append(usr)
1273
1286
1274 return super_admin_rows + owner_row + perm_rows
1287 return super_admin_rows + owner_row + perm_rows
1275
1288
1276 def permission_user_groups(self):
1289 def permission_user_groups(self):
1277 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1290 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1278 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1291 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1279 joinedload(UserGroupUserGroupToPerm.target_user_group),
1292 joinedload(UserGroupUserGroupToPerm.target_user_group),
1280 joinedload(UserGroupUserGroupToPerm.permission),)
1293 joinedload(UserGroupUserGroupToPerm.permission),)
1281
1294
1282 perm_rows = []
1295 perm_rows = []
1283 for _user_group in q.all():
1296 for _user_group in q.all():
1284 usr = AttributeDict(_user_group.user_group.get_dict())
1297 usr = AttributeDict(_user_group.user_group.get_dict())
1285 usr.permission = _user_group.permission.permission_name
1298 usr.permission = _user_group.permission.permission_name
1286 perm_rows.append(usr)
1299 perm_rows.append(usr)
1287
1300
1288 return perm_rows
1301 return perm_rows
1289
1302
1290 def _get_default_perms(self, user_group, suffix=''):
1303 def _get_default_perms(self, user_group, suffix=''):
1291 from rhodecode.model.permission import PermissionModel
1304 from rhodecode.model.permission import PermissionModel
1292 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1305 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1293
1306
1294 def get_default_perms(self, suffix=''):
1307 def get_default_perms(self, suffix=''):
1295 return self._get_default_perms(self, suffix)
1308 return self._get_default_perms(self, suffix)
1296
1309
1297 def get_api_data(self, with_group_members=True, include_secrets=False):
1310 def get_api_data(self, with_group_members=True, include_secrets=False):
1298 """
1311 """
1299 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1312 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1300 basically forwarded.
1313 basically forwarded.
1301
1314
1302 """
1315 """
1303 user_group = self
1316 user_group = self
1304 data = {
1317 data = {
1305 'users_group_id': user_group.users_group_id,
1318 'users_group_id': user_group.users_group_id,
1306 'group_name': user_group.users_group_name,
1319 'group_name': user_group.users_group_name,
1307 'group_description': user_group.user_group_description,
1320 'group_description': user_group.user_group_description,
1308 'active': user_group.users_group_active,
1321 'active': user_group.users_group_active,
1309 'owner': user_group.user.username,
1322 'owner': user_group.user.username,
1310 'owner_email': user_group.user.email,
1323 'owner_email': user_group.user.email,
1311 }
1324 }
1312
1325
1313 if with_group_members:
1326 if with_group_members:
1314 users = []
1327 users = []
1315 for user in user_group.members:
1328 for user in user_group.members:
1316 user = user.user
1329 user = user.user
1317 users.append(user.get_api_data(include_secrets=include_secrets))
1330 users.append(user.get_api_data(include_secrets=include_secrets))
1318 data['users'] = users
1331 data['users'] = users
1319
1332
1320 return data
1333 return data
1321
1334
1322
1335
1323 class UserGroupMember(Base, BaseModel):
1336 class UserGroupMember(Base, BaseModel):
1324 __tablename__ = 'users_groups_members'
1337 __tablename__ = 'users_groups_members'
1325 __table_args__ = (
1338 __table_args__ = (
1326 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1327 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1340 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1328 )
1341 )
1329
1342
1330 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1343 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1331 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1344 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1345 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1333
1346
1334 user = relationship('User', lazy='joined')
1347 user = relationship('User', lazy='joined')
1335 users_group = relationship('UserGroup')
1348 users_group = relationship('UserGroup')
1336
1349
1337 def __init__(self, gr_id='', u_id=''):
1350 def __init__(self, gr_id='', u_id=''):
1338 self.users_group_id = gr_id
1351 self.users_group_id = gr_id
1339 self.user_id = u_id
1352 self.user_id = u_id
1340
1353
1341
1354
1342 class RepositoryField(Base, BaseModel):
1355 class RepositoryField(Base, BaseModel):
1343 __tablename__ = 'repositories_fields'
1356 __tablename__ = 'repositories_fields'
1344 __table_args__ = (
1357 __table_args__ = (
1345 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1358 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1359 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1360 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1348 )
1361 )
1349 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1362 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1350
1363
1351 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1364 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1352 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1365 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1353 field_key = Column("field_key", String(250))
1366 field_key = Column("field_key", String(250))
1354 field_label = Column("field_label", String(1024), nullable=False)
1367 field_label = Column("field_label", String(1024), nullable=False)
1355 field_value = Column("field_value", String(10000), nullable=False)
1368 field_value = Column("field_value", String(10000), nullable=False)
1356 field_desc = Column("field_desc", String(1024), nullable=False)
1369 field_desc = Column("field_desc", String(1024), nullable=False)
1357 field_type = Column("field_type", String(255), nullable=False, unique=None)
1370 field_type = Column("field_type", String(255), nullable=False, unique=None)
1358 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1371 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1359
1372
1360 repository = relationship('Repository')
1373 repository = relationship('Repository')
1361
1374
1362 @property
1375 @property
1363 def field_key_prefixed(self):
1376 def field_key_prefixed(self):
1364 return 'ex_%s' % self.field_key
1377 return 'ex_%s' % self.field_key
1365
1378
1366 @classmethod
1379 @classmethod
1367 def un_prefix_key(cls, key):
1380 def un_prefix_key(cls, key):
1368 if key.startswith(cls.PREFIX):
1381 if key.startswith(cls.PREFIX):
1369 return key[len(cls.PREFIX):]
1382 return key[len(cls.PREFIX):]
1370 return key
1383 return key
1371
1384
1372 @classmethod
1385 @classmethod
1373 def get_by_key_name(cls, key, repo):
1386 def get_by_key_name(cls, key, repo):
1374 row = cls.query()\
1387 row = cls.query()\
1375 .filter(cls.repository == repo)\
1388 .filter(cls.repository == repo)\
1376 .filter(cls.field_key == key).scalar()
1389 .filter(cls.field_key == key).scalar()
1377 return row
1390 return row
1378
1391
1379
1392
1380 class Repository(Base, BaseModel):
1393 class Repository(Base, BaseModel):
1381 __tablename__ = 'repositories'
1394 __tablename__ = 'repositories'
1382 __table_args__ = (
1395 __table_args__ = (
1383 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1396 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1384 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1385 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1398 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1386 )
1399 )
1387 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1400 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1388 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1401 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1389
1402
1390 STATE_CREATED = 'repo_state_created'
1403 STATE_CREATED = 'repo_state_created'
1391 STATE_PENDING = 'repo_state_pending'
1404 STATE_PENDING = 'repo_state_pending'
1392 STATE_ERROR = 'repo_state_error'
1405 STATE_ERROR = 'repo_state_error'
1393
1406
1394 LOCK_AUTOMATIC = 'lock_auto'
1407 LOCK_AUTOMATIC = 'lock_auto'
1395 LOCK_API = 'lock_api'
1408 LOCK_API = 'lock_api'
1396 LOCK_WEB = 'lock_web'
1409 LOCK_WEB = 'lock_web'
1397 LOCK_PULL = 'lock_pull'
1410 LOCK_PULL = 'lock_pull'
1398
1411
1399 NAME_SEP = URL_SEP
1412 NAME_SEP = URL_SEP
1400
1413
1401 repo_id = Column(
1414 repo_id = Column(
1402 "repo_id", Integer(), nullable=False, unique=True, default=None,
1415 "repo_id", Integer(), nullable=False, unique=True, default=None,
1403 primary_key=True)
1416 primary_key=True)
1404 _repo_name = Column(
1417 _repo_name = Column(
1405 "repo_name", Text(), nullable=False, default=None)
1418 "repo_name", Text(), nullable=False, default=None)
1406 _repo_name_hash = Column(
1419 _repo_name_hash = Column(
1407 "repo_name_hash", String(255), nullable=False, unique=True)
1420 "repo_name_hash", String(255), nullable=False, unique=True)
1408 repo_state = Column("repo_state", String(255), nullable=True)
1421 repo_state = Column("repo_state", String(255), nullable=True)
1409
1422
1410 clone_uri = Column(
1423 clone_uri = Column(
1411 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1424 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1412 default=None)
1425 default=None)
1413 repo_type = Column(
1426 repo_type = Column(
1414 "repo_type", String(255), nullable=False, unique=False, default=None)
1427 "repo_type", String(255), nullable=False, unique=False, default=None)
1415 user_id = Column(
1428 user_id = Column(
1416 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1429 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1417 unique=False, default=None)
1430 unique=False, default=None)
1418 private = Column(
1431 private = Column(
1419 "private", Boolean(), nullable=True, unique=None, default=None)
1432 "private", Boolean(), nullable=True, unique=None, default=None)
1420 enable_statistics = Column(
1433 enable_statistics = Column(
1421 "statistics", Boolean(), nullable=True, unique=None, default=True)
1434 "statistics", Boolean(), nullable=True, unique=None, default=True)
1422 enable_downloads = Column(
1435 enable_downloads = Column(
1423 "downloads", Boolean(), nullable=True, unique=None, default=True)
1436 "downloads", Boolean(), nullable=True, unique=None, default=True)
1424 description = Column(
1437 description = Column(
1425 "description", String(10000), nullable=True, unique=None, default=None)
1438 "description", String(10000), nullable=True, unique=None, default=None)
1426 created_on = Column(
1439 created_on = Column(
1427 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1440 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1428 default=datetime.datetime.now)
1441 default=datetime.datetime.now)
1429 updated_on = Column(
1442 updated_on = Column(
1430 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1443 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1431 default=datetime.datetime.now)
1444 default=datetime.datetime.now)
1432 _landing_revision = Column(
1445 _landing_revision = Column(
1433 "landing_revision", String(255), nullable=False, unique=False,
1446 "landing_revision", String(255), nullable=False, unique=False,
1434 default=None)
1447 default=None)
1435 enable_locking = Column(
1448 enable_locking = Column(
1436 "enable_locking", Boolean(), nullable=False, unique=None,
1449 "enable_locking", Boolean(), nullable=False, unique=None,
1437 default=False)
1450 default=False)
1438 _locked = Column(
1451 _locked = Column(
1439 "locked", String(255), nullable=True, unique=False, default=None)
1452 "locked", String(255), nullable=True, unique=False, default=None)
1440 _changeset_cache = Column(
1453 _changeset_cache = Column(
1441 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1454 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1442
1455
1443 fork_id = Column(
1456 fork_id = Column(
1444 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1457 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1445 nullable=True, unique=False, default=None)
1458 nullable=True, unique=False, default=None)
1446 group_id = Column(
1459 group_id = Column(
1447 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1460 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1448 unique=False, default=None)
1461 unique=False, default=None)
1449
1462
1450 user = relationship('User', lazy='joined')
1463 user = relationship('User', lazy='joined')
1451 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1464 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1452 group = relationship('RepoGroup', lazy='joined')
1465 group = relationship('RepoGroup', lazy='joined')
1453 repo_to_perm = relationship(
1466 repo_to_perm = relationship(
1454 'UserRepoToPerm', cascade='all',
1467 'UserRepoToPerm', cascade='all',
1455 order_by='UserRepoToPerm.repo_to_perm_id')
1468 order_by='UserRepoToPerm.repo_to_perm_id')
1456 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1469 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1457 stats = relationship('Statistics', cascade='all', uselist=False)
1470 stats = relationship('Statistics', cascade='all', uselist=False)
1458
1471
1459 followers = relationship(
1472 followers = relationship(
1460 'UserFollowing',
1473 'UserFollowing',
1461 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1474 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1462 cascade='all')
1475 cascade='all')
1463 extra_fields = relationship(
1476 extra_fields = relationship(
1464 'RepositoryField', cascade="all, delete, delete-orphan")
1477 'RepositoryField', cascade="all, delete, delete-orphan")
1465 logs = relationship('UserLog')
1478 logs = relationship('UserLog')
1466 comments = relationship(
1479 comments = relationship(
1467 'ChangesetComment', cascade="all, delete, delete-orphan")
1480 'ChangesetComment', cascade="all, delete, delete-orphan")
1468 pull_requests_source = relationship(
1481 pull_requests_source = relationship(
1469 'PullRequest',
1482 'PullRequest',
1470 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1483 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1471 cascade="all, delete, delete-orphan")
1484 cascade="all, delete, delete-orphan")
1472 pull_requests_target = relationship(
1485 pull_requests_target = relationship(
1473 'PullRequest',
1486 'PullRequest',
1474 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1487 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1475 cascade="all, delete, delete-orphan")
1488 cascade="all, delete, delete-orphan")
1476 ui = relationship('RepoRhodeCodeUi', cascade="all")
1489 ui = relationship('RepoRhodeCodeUi', cascade="all")
1477 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1490 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1478 integrations = relationship('Integration',
1491 integrations = relationship('Integration',
1479 cascade="all, delete, delete-orphan")
1492 cascade="all, delete, delete-orphan")
1480
1493
1481 def __unicode__(self):
1494 def __unicode__(self):
1482 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1495 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1483 safe_unicode(self.repo_name))
1496 safe_unicode(self.repo_name))
1484
1497
1485 @hybrid_property
1498 @hybrid_property
1486 def landing_rev(self):
1499 def landing_rev(self):
1487 # always should return [rev_type, rev]
1500 # always should return [rev_type, rev]
1488 if self._landing_revision:
1501 if self._landing_revision:
1489 _rev_info = self._landing_revision.split(':')
1502 _rev_info = self._landing_revision.split(':')
1490 if len(_rev_info) < 2:
1503 if len(_rev_info) < 2:
1491 _rev_info.insert(0, 'rev')
1504 _rev_info.insert(0, 'rev')
1492 return [_rev_info[0], _rev_info[1]]
1505 return [_rev_info[0], _rev_info[1]]
1493 return [None, None]
1506 return [None, None]
1494
1507
1495 @landing_rev.setter
1508 @landing_rev.setter
1496 def landing_rev(self, val):
1509 def landing_rev(self, val):
1497 if ':' not in val:
1510 if ':' not in val:
1498 raise ValueError('value must be delimited with `:` and consist '
1511 raise ValueError('value must be delimited with `:` and consist '
1499 'of <rev_type>:<rev>, got %s instead' % val)
1512 'of <rev_type>:<rev>, got %s instead' % val)
1500 self._landing_revision = val
1513 self._landing_revision = val
1501
1514
1502 @hybrid_property
1515 @hybrid_property
1503 def locked(self):
1516 def locked(self):
1504 if self._locked:
1517 if self._locked:
1505 user_id, timelocked, reason = self._locked.split(':')
1518 user_id, timelocked, reason = self._locked.split(':')
1506 lock_values = int(user_id), timelocked, reason
1519 lock_values = int(user_id), timelocked, reason
1507 else:
1520 else:
1508 lock_values = [None, None, None]
1521 lock_values = [None, None, None]
1509 return lock_values
1522 return lock_values
1510
1523
1511 @locked.setter
1524 @locked.setter
1512 def locked(self, val):
1525 def locked(self, val):
1513 if val and isinstance(val, (list, tuple)):
1526 if val and isinstance(val, (list, tuple)):
1514 self._locked = ':'.join(map(str, val))
1527 self._locked = ':'.join(map(str, val))
1515 else:
1528 else:
1516 self._locked = None
1529 self._locked = None
1517
1530
1518 @hybrid_property
1531 @hybrid_property
1519 def changeset_cache(self):
1532 def changeset_cache(self):
1520 from rhodecode.lib.vcs.backends.base import EmptyCommit
1533 from rhodecode.lib.vcs.backends.base import EmptyCommit
1521 dummy = EmptyCommit().__json__()
1534 dummy = EmptyCommit().__json__()
1522 if not self._changeset_cache:
1535 if not self._changeset_cache:
1523 return dummy
1536 return dummy
1524 try:
1537 try:
1525 return json.loads(self._changeset_cache)
1538 return json.loads(self._changeset_cache)
1526 except TypeError:
1539 except TypeError:
1527 return dummy
1540 return dummy
1528 except Exception:
1541 except Exception:
1529 log.error(traceback.format_exc())
1542 log.error(traceback.format_exc())
1530 return dummy
1543 return dummy
1531
1544
1532 @changeset_cache.setter
1545 @changeset_cache.setter
1533 def changeset_cache(self, val):
1546 def changeset_cache(self, val):
1534 try:
1547 try:
1535 self._changeset_cache = json.dumps(val)
1548 self._changeset_cache = json.dumps(val)
1536 except Exception:
1549 except Exception:
1537 log.error(traceback.format_exc())
1550 log.error(traceback.format_exc())
1538
1551
1539 @hybrid_property
1552 @hybrid_property
1540 def repo_name(self):
1553 def repo_name(self):
1541 return self._repo_name
1554 return self._repo_name
1542
1555
1543 @repo_name.setter
1556 @repo_name.setter
1544 def repo_name(self, value):
1557 def repo_name(self, value):
1545 self._repo_name = value
1558 self._repo_name = value
1546 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1559 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1547
1560
1548 @classmethod
1561 @classmethod
1549 def normalize_repo_name(cls, repo_name):
1562 def normalize_repo_name(cls, repo_name):
1550 """
1563 """
1551 Normalizes os specific repo_name to the format internally stored inside
1564 Normalizes os specific repo_name to the format internally stored inside
1552 database using URL_SEP
1565 database using URL_SEP
1553
1566
1554 :param cls:
1567 :param cls:
1555 :param repo_name:
1568 :param repo_name:
1556 """
1569 """
1557 return cls.NAME_SEP.join(repo_name.split(os.sep))
1570 return cls.NAME_SEP.join(repo_name.split(os.sep))
1558
1571
1559 @classmethod
1572 @classmethod
1560 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1573 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1561 session = Session()
1574 session = Session()
1562 q = session.query(cls).filter(cls.repo_name == repo_name)
1575 q = session.query(cls).filter(cls.repo_name == repo_name)
1563
1576
1564 if cache:
1577 if cache:
1565 if identity_cache:
1578 if identity_cache:
1566 val = cls.identity_cache(session, 'repo_name', repo_name)
1579 val = cls.identity_cache(session, 'repo_name', repo_name)
1567 if val:
1580 if val:
1568 return val
1581 return val
1569 else:
1582 else:
1570 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1583 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1571 q = q.options(
1584 q = q.options(
1572 FromCache("sql_cache_short", cache_key))
1585 FromCache("sql_cache_short", cache_key))
1573
1586
1574 return q.scalar()
1587 return q.scalar()
1575
1588
1576 @classmethod
1589 @classmethod
1577 def get_by_full_path(cls, repo_full_path):
1590 def get_by_full_path(cls, repo_full_path):
1578 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1591 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1579 repo_name = cls.normalize_repo_name(repo_name)
1592 repo_name = cls.normalize_repo_name(repo_name)
1580 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1593 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1581
1594
1582 @classmethod
1595 @classmethod
1583 def get_repo_forks(cls, repo_id):
1596 def get_repo_forks(cls, repo_id):
1584 return cls.query().filter(Repository.fork_id == repo_id)
1597 return cls.query().filter(Repository.fork_id == repo_id)
1585
1598
1586 @classmethod
1599 @classmethod
1587 def base_path(cls):
1600 def base_path(cls):
1588 """
1601 """
1589 Returns base path when all repos are stored
1602 Returns base path when all repos are stored
1590
1603
1591 :param cls:
1604 :param cls:
1592 """
1605 """
1593 q = Session().query(RhodeCodeUi)\
1606 q = Session().query(RhodeCodeUi)\
1594 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1607 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1595 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1608 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1596 return q.one().ui_value
1609 return q.one().ui_value
1597
1610
1598 @classmethod
1611 @classmethod
1599 def is_valid(cls, repo_name):
1612 def is_valid(cls, repo_name):
1600 """
1613 """
1601 returns True if given repo name is a valid filesystem repository
1614 returns True if given repo name is a valid filesystem repository
1602
1615
1603 :param cls:
1616 :param cls:
1604 :param repo_name:
1617 :param repo_name:
1605 """
1618 """
1606 from rhodecode.lib.utils import is_valid_repo
1619 from rhodecode.lib.utils import is_valid_repo
1607
1620
1608 return is_valid_repo(repo_name, cls.base_path())
1621 return is_valid_repo(repo_name, cls.base_path())
1609
1622
1610 @classmethod
1623 @classmethod
1611 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1624 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1612 case_insensitive=True):
1625 case_insensitive=True):
1613 q = Repository.query()
1626 q = Repository.query()
1614
1627
1615 if not isinstance(user_id, Optional):
1628 if not isinstance(user_id, Optional):
1616 q = q.filter(Repository.user_id == user_id)
1629 q = q.filter(Repository.user_id == user_id)
1617
1630
1618 if not isinstance(group_id, Optional):
1631 if not isinstance(group_id, Optional):
1619 q = q.filter(Repository.group_id == group_id)
1632 q = q.filter(Repository.group_id == group_id)
1620
1633
1621 if case_insensitive:
1634 if case_insensitive:
1622 q = q.order_by(func.lower(Repository.repo_name))
1635 q = q.order_by(func.lower(Repository.repo_name))
1623 else:
1636 else:
1624 q = q.order_by(Repository.repo_name)
1637 q = q.order_by(Repository.repo_name)
1625 return q.all()
1638 return q.all()
1626
1639
1627 @property
1640 @property
1628 def forks(self):
1641 def forks(self):
1629 """
1642 """
1630 Return forks of this repo
1643 Return forks of this repo
1631 """
1644 """
1632 return Repository.get_repo_forks(self.repo_id)
1645 return Repository.get_repo_forks(self.repo_id)
1633
1646
1634 @property
1647 @property
1635 def parent(self):
1648 def parent(self):
1636 """
1649 """
1637 Returns fork parent
1650 Returns fork parent
1638 """
1651 """
1639 return self.fork
1652 return self.fork
1640
1653
1641 @property
1654 @property
1642 def just_name(self):
1655 def just_name(self):
1643 return self.repo_name.split(self.NAME_SEP)[-1]
1656 return self.repo_name.split(self.NAME_SEP)[-1]
1644
1657
1645 @property
1658 @property
1646 def groups_with_parents(self):
1659 def groups_with_parents(self):
1647 groups = []
1660 groups = []
1648 if self.group is None:
1661 if self.group is None:
1649 return groups
1662 return groups
1650
1663
1651 cur_gr = self.group
1664 cur_gr = self.group
1652 groups.insert(0, cur_gr)
1665 groups.insert(0, cur_gr)
1653 while 1:
1666 while 1:
1654 gr = getattr(cur_gr, 'parent_group', None)
1667 gr = getattr(cur_gr, 'parent_group', None)
1655 cur_gr = cur_gr.parent_group
1668 cur_gr = cur_gr.parent_group
1656 if gr is None:
1669 if gr is None:
1657 break
1670 break
1658 groups.insert(0, gr)
1671 groups.insert(0, gr)
1659
1672
1660 return groups
1673 return groups
1661
1674
1662 @property
1675 @property
1663 def groups_and_repo(self):
1676 def groups_and_repo(self):
1664 return self.groups_with_parents, self
1677 return self.groups_with_parents, self
1665
1678
1666 @LazyProperty
1679 @LazyProperty
1667 def repo_path(self):
1680 def repo_path(self):
1668 """
1681 """
1669 Returns base full path for that repository means where it actually
1682 Returns base full path for that repository means where it actually
1670 exists on a filesystem
1683 exists on a filesystem
1671 """
1684 """
1672 q = Session().query(RhodeCodeUi).filter(
1685 q = Session().query(RhodeCodeUi).filter(
1673 RhodeCodeUi.ui_key == self.NAME_SEP)
1686 RhodeCodeUi.ui_key == self.NAME_SEP)
1674 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1687 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1675 return q.one().ui_value
1688 return q.one().ui_value
1676
1689
1677 @property
1690 @property
1678 def repo_full_path(self):
1691 def repo_full_path(self):
1679 p = [self.repo_path]
1692 p = [self.repo_path]
1680 # we need to split the name by / since this is how we store the
1693 # we need to split the name by / since this is how we store the
1681 # names in the database, but that eventually needs to be converted
1694 # names in the database, but that eventually needs to be converted
1682 # into a valid system path
1695 # into a valid system path
1683 p += self.repo_name.split(self.NAME_SEP)
1696 p += self.repo_name.split(self.NAME_SEP)
1684 return os.path.join(*map(safe_unicode, p))
1697 return os.path.join(*map(safe_unicode, p))
1685
1698
1686 @property
1699 @property
1687 def cache_keys(self):
1700 def cache_keys(self):
1688 """
1701 """
1689 Returns associated cache keys for that repo
1702 Returns associated cache keys for that repo
1690 """
1703 """
1691 return CacheKey.query()\
1704 return CacheKey.query()\
1692 .filter(CacheKey.cache_args == self.repo_name)\
1705 .filter(CacheKey.cache_args == self.repo_name)\
1693 .order_by(CacheKey.cache_key)\
1706 .order_by(CacheKey.cache_key)\
1694 .all()
1707 .all()
1695
1708
1696 def get_new_name(self, repo_name):
1709 def get_new_name(self, repo_name):
1697 """
1710 """
1698 returns new full repository name based on assigned group and new new
1711 returns new full repository name based on assigned group and new new
1699
1712
1700 :param group_name:
1713 :param group_name:
1701 """
1714 """
1702 path_prefix = self.group.full_path_splitted if self.group else []
1715 path_prefix = self.group.full_path_splitted if self.group else []
1703 return self.NAME_SEP.join(path_prefix + [repo_name])
1716 return self.NAME_SEP.join(path_prefix + [repo_name])
1704
1717
1705 @property
1718 @property
1706 def _config(self):
1719 def _config(self):
1707 """
1720 """
1708 Returns db based config object.
1721 Returns db based config object.
1709 """
1722 """
1710 from rhodecode.lib.utils import make_db_config
1723 from rhodecode.lib.utils import make_db_config
1711 return make_db_config(clear_session=False, repo=self)
1724 return make_db_config(clear_session=False, repo=self)
1712
1725
1713 def permissions(self, with_admins=True, with_owner=True):
1726 def permissions(self, with_admins=True, with_owner=True):
1714 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1727 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1715 q = q.options(joinedload(UserRepoToPerm.repository),
1728 q = q.options(joinedload(UserRepoToPerm.repository),
1716 joinedload(UserRepoToPerm.user),
1729 joinedload(UserRepoToPerm.user),
1717 joinedload(UserRepoToPerm.permission),)
1730 joinedload(UserRepoToPerm.permission),)
1718
1731
1719 # get owners and admins and permissions. We do a trick of re-writing
1732 # get owners and admins and permissions. We do a trick of re-writing
1720 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1733 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1721 # has a global reference and changing one object propagates to all
1734 # has a global reference and changing one object propagates to all
1722 # others. This means if admin is also an owner admin_row that change
1735 # others. This means if admin is also an owner admin_row that change
1723 # would propagate to both objects
1736 # would propagate to both objects
1724 perm_rows = []
1737 perm_rows = []
1725 for _usr in q.all():
1738 for _usr in q.all():
1726 usr = AttributeDict(_usr.user.get_dict())
1739 usr = AttributeDict(_usr.user.get_dict())
1727 usr.permission = _usr.permission.permission_name
1740 usr.permission = _usr.permission.permission_name
1728 perm_rows.append(usr)
1741 perm_rows.append(usr)
1729
1742
1730 # filter the perm rows by 'default' first and then sort them by
1743 # filter the perm rows by 'default' first and then sort them by
1731 # admin,write,read,none permissions sorted again alphabetically in
1744 # admin,write,read,none permissions sorted again alphabetically in
1732 # each group
1745 # each group
1733 perm_rows = sorted(perm_rows, key=display_sort)
1746 perm_rows = sorted(perm_rows, key=display_sort)
1734
1747
1735 _admin_perm = 'repository.admin'
1748 _admin_perm = 'repository.admin'
1736 owner_row = []
1749 owner_row = []
1737 if with_owner:
1750 if with_owner:
1738 usr = AttributeDict(self.user.get_dict())
1751 usr = AttributeDict(self.user.get_dict())
1739 usr.owner_row = True
1752 usr.owner_row = True
1740 usr.permission = _admin_perm
1753 usr.permission = _admin_perm
1741 owner_row.append(usr)
1754 owner_row.append(usr)
1742
1755
1743 super_admin_rows = []
1756 super_admin_rows = []
1744 if with_admins:
1757 if with_admins:
1745 for usr in User.get_all_super_admins():
1758 for usr in User.get_all_super_admins():
1746 # if this admin is also owner, don't double the record
1759 # if this admin is also owner, don't double the record
1747 if usr.user_id == owner_row[0].user_id:
1760 if usr.user_id == owner_row[0].user_id:
1748 owner_row[0].admin_row = True
1761 owner_row[0].admin_row = True
1749 else:
1762 else:
1750 usr = AttributeDict(usr.get_dict())
1763 usr = AttributeDict(usr.get_dict())
1751 usr.admin_row = True
1764 usr.admin_row = True
1752 usr.permission = _admin_perm
1765 usr.permission = _admin_perm
1753 super_admin_rows.append(usr)
1766 super_admin_rows.append(usr)
1754
1767
1755 return super_admin_rows + owner_row + perm_rows
1768 return super_admin_rows + owner_row + perm_rows
1756
1769
1757 def permission_user_groups(self):
1770 def permission_user_groups(self):
1758 q = UserGroupRepoToPerm.query().filter(
1771 q = UserGroupRepoToPerm.query().filter(
1759 UserGroupRepoToPerm.repository == self)
1772 UserGroupRepoToPerm.repository == self)
1760 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1773 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1761 joinedload(UserGroupRepoToPerm.users_group),
1774 joinedload(UserGroupRepoToPerm.users_group),
1762 joinedload(UserGroupRepoToPerm.permission),)
1775 joinedload(UserGroupRepoToPerm.permission),)
1763
1776
1764 perm_rows = []
1777 perm_rows = []
1765 for _user_group in q.all():
1778 for _user_group in q.all():
1766 usr = AttributeDict(_user_group.users_group.get_dict())
1779 usr = AttributeDict(_user_group.users_group.get_dict())
1767 usr.permission = _user_group.permission.permission_name
1780 usr.permission = _user_group.permission.permission_name
1768 perm_rows.append(usr)
1781 perm_rows.append(usr)
1769
1782
1770 return perm_rows
1783 return perm_rows
1771
1784
1772 def get_api_data(self, include_secrets=False):
1785 def get_api_data(self, include_secrets=False):
1773 """
1786 """
1774 Common function for generating repo api data
1787 Common function for generating repo api data
1775
1788
1776 :param include_secrets: See :meth:`User.get_api_data`.
1789 :param include_secrets: See :meth:`User.get_api_data`.
1777
1790
1778 """
1791 """
1779 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1792 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1780 # move this methods on models level.
1793 # move this methods on models level.
1781 from rhodecode.model.settings import SettingsModel
1794 from rhodecode.model.settings import SettingsModel
1782 from rhodecode.model.repo import RepoModel
1795 from rhodecode.model.repo import RepoModel
1783
1796
1784 repo = self
1797 repo = self
1785 _user_id, _time, _reason = self.locked
1798 _user_id, _time, _reason = self.locked
1786
1799
1787 data = {
1800 data = {
1788 'repo_id': repo.repo_id,
1801 'repo_id': repo.repo_id,
1789 'repo_name': repo.repo_name,
1802 'repo_name': repo.repo_name,
1790 'repo_type': repo.repo_type,
1803 'repo_type': repo.repo_type,
1791 'clone_uri': repo.clone_uri or '',
1804 'clone_uri': repo.clone_uri or '',
1792 'url': RepoModel().get_url(self),
1805 'url': RepoModel().get_url(self),
1793 'private': repo.private,
1806 'private': repo.private,
1794 'created_on': repo.created_on,
1807 'created_on': repo.created_on,
1795 'description': repo.description,
1808 'description': repo.description,
1796 'landing_rev': repo.landing_rev,
1809 'landing_rev': repo.landing_rev,
1797 'owner': repo.user.username,
1810 'owner': repo.user.username,
1798 'fork_of': repo.fork.repo_name if repo.fork else None,
1811 'fork_of': repo.fork.repo_name if repo.fork else None,
1799 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1812 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1800 'enable_statistics': repo.enable_statistics,
1813 'enable_statistics': repo.enable_statistics,
1801 'enable_locking': repo.enable_locking,
1814 'enable_locking': repo.enable_locking,
1802 'enable_downloads': repo.enable_downloads,
1815 'enable_downloads': repo.enable_downloads,
1803 'last_changeset': repo.changeset_cache,
1816 'last_changeset': repo.changeset_cache,
1804 'locked_by': User.get(_user_id).get_api_data(
1817 'locked_by': User.get(_user_id).get_api_data(
1805 include_secrets=include_secrets) if _user_id else None,
1818 include_secrets=include_secrets) if _user_id else None,
1806 'locked_date': time_to_datetime(_time) if _time else None,
1819 'locked_date': time_to_datetime(_time) if _time else None,
1807 'lock_reason': _reason if _reason else None,
1820 'lock_reason': _reason if _reason else None,
1808 }
1821 }
1809
1822
1810 # TODO: mikhail: should be per-repo settings here
1823 # TODO: mikhail: should be per-repo settings here
1811 rc_config = SettingsModel().get_all_settings()
1824 rc_config = SettingsModel().get_all_settings()
1812 repository_fields = str2bool(
1825 repository_fields = str2bool(
1813 rc_config.get('rhodecode_repository_fields'))
1826 rc_config.get('rhodecode_repository_fields'))
1814 if repository_fields:
1827 if repository_fields:
1815 for f in self.extra_fields:
1828 for f in self.extra_fields:
1816 data[f.field_key_prefixed] = f.field_value
1829 data[f.field_key_prefixed] = f.field_value
1817
1830
1818 return data
1831 return data
1819
1832
1820 @classmethod
1833 @classmethod
1821 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1834 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1822 if not lock_time:
1835 if not lock_time:
1823 lock_time = time.time()
1836 lock_time = time.time()
1824 if not lock_reason:
1837 if not lock_reason:
1825 lock_reason = cls.LOCK_AUTOMATIC
1838 lock_reason = cls.LOCK_AUTOMATIC
1826 repo.locked = [user_id, lock_time, lock_reason]
1839 repo.locked = [user_id, lock_time, lock_reason]
1827 Session().add(repo)
1840 Session().add(repo)
1828 Session().commit()
1841 Session().commit()
1829
1842
1830 @classmethod
1843 @classmethod
1831 def unlock(cls, repo):
1844 def unlock(cls, repo):
1832 repo.locked = None
1845 repo.locked = None
1833 Session().add(repo)
1846 Session().add(repo)
1834 Session().commit()
1847 Session().commit()
1835
1848
1836 @classmethod
1849 @classmethod
1837 def getlock(cls, repo):
1850 def getlock(cls, repo):
1838 return repo.locked
1851 return repo.locked
1839
1852
1840 def is_user_lock(self, user_id):
1853 def is_user_lock(self, user_id):
1841 if self.lock[0]:
1854 if self.lock[0]:
1842 lock_user_id = safe_int(self.lock[0])
1855 lock_user_id = safe_int(self.lock[0])
1843 user_id = safe_int(user_id)
1856 user_id = safe_int(user_id)
1844 # both are ints, and they are equal
1857 # both are ints, and they are equal
1845 return all([lock_user_id, user_id]) and lock_user_id == user_id
1858 return all([lock_user_id, user_id]) and lock_user_id == user_id
1846
1859
1847 return False
1860 return False
1848
1861
1849 def get_locking_state(self, action, user_id, only_when_enabled=True):
1862 def get_locking_state(self, action, user_id, only_when_enabled=True):
1850 """
1863 """
1851 Checks locking on this repository, if locking is enabled and lock is
1864 Checks locking on this repository, if locking is enabled and lock is
1852 present returns a tuple of make_lock, locked, locked_by.
1865 present returns a tuple of make_lock, locked, locked_by.
1853 make_lock can have 3 states None (do nothing) True, make lock
1866 make_lock can have 3 states None (do nothing) True, make lock
1854 False release lock, This value is later propagated to hooks, which
1867 False release lock, This value is later propagated to hooks, which
1855 do the locking. Think about this as signals passed to hooks what to do.
1868 do the locking. Think about this as signals passed to hooks what to do.
1856
1869
1857 """
1870 """
1858 # TODO: johbo: This is part of the business logic and should be moved
1871 # TODO: johbo: This is part of the business logic and should be moved
1859 # into the RepositoryModel.
1872 # into the RepositoryModel.
1860
1873
1861 if action not in ('push', 'pull'):
1874 if action not in ('push', 'pull'):
1862 raise ValueError("Invalid action value: %s" % repr(action))
1875 raise ValueError("Invalid action value: %s" % repr(action))
1863
1876
1864 # defines if locked error should be thrown to user
1877 # defines if locked error should be thrown to user
1865 currently_locked = False
1878 currently_locked = False
1866 # defines if new lock should be made, tri-state
1879 # defines if new lock should be made, tri-state
1867 make_lock = None
1880 make_lock = None
1868 repo = self
1881 repo = self
1869 user = User.get(user_id)
1882 user = User.get(user_id)
1870
1883
1871 lock_info = repo.locked
1884 lock_info = repo.locked
1872
1885
1873 if repo and (repo.enable_locking or not only_when_enabled):
1886 if repo and (repo.enable_locking or not only_when_enabled):
1874 if action == 'push':
1887 if action == 'push':
1875 # check if it's already locked !, if it is compare users
1888 # check if it's already locked !, if it is compare users
1876 locked_by_user_id = lock_info[0]
1889 locked_by_user_id = lock_info[0]
1877 if user.user_id == locked_by_user_id:
1890 if user.user_id == locked_by_user_id:
1878 log.debug(
1891 log.debug(
1879 'Got `push` action from user %s, now unlocking', user)
1892 'Got `push` action from user %s, now unlocking', user)
1880 # unlock if we have push from user who locked
1893 # unlock if we have push from user who locked
1881 make_lock = False
1894 make_lock = False
1882 else:
1895 else:
1883 # we're not the same user who locked, ban with
1896 # we're not the same user who locked, ban with
1884 # code defined in settings (default is 423 HTTP Locked) !
1897 # code defined in settings (default is 423 HTTP Locked) !
1885 log.debug('Repo %s is currently locked by %s', repo, user)
1898 log.debug('Repo %s is currently locked by %s', repo, user)
1886 currently_locked = True
1899 currently_locked = True
1887 elif action == 'pull':
1900 elif action == 'pull':
1888 # [0] user [1] date
1901 # [0] user [1] date
1889 if lock_info[0] and lock_info[1]:
1902 if lock_info[0] and lock_info[1]:
1890 log.debug('Repo %s is currently locked by %s', repo, user)
1903 log.debug('Repo %s is currently locked by %s', repo, user)
1891 currently_locked = True
1904 currently_locked = True
1892 else:
1905 else:
1893 log.debug('Setting lock on repo %s by %s', repo, user)
1906 log.debug('Setting lock on repo %s by %s', repo, user)
1894 make_lock = True
1907 make_lock = True
1895
1908
1896 else:
1909 else:
1897 log.debug('Repository %s do not have locking enabled', repo)
1910 log.debug('Repository %s do not have locking enabled', repo)
1898
1911
1899 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1912 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1900 make_lock, currently_locked, lock_info)
1913 make_lock, currently_locked, lock_info)
1901
1914
1902 from rhodecode.lib.auth import HasRepoPermissionAny
1915 from rhodecode.lib.auth import HasRepoPermissionAny
1903 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1916 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1904 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1917 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1905 # if we don't have at least write permission we cannot make a lock
1918 # if we don't have at least write permission we cannot make a lock
1906 log.debug('lock state reset back to FALSE due to lack '
1919 log.debug('lock state reset back to FALSE due to lack '
1907 'of at least read permission')
1920 'of at least read permission')
1908 make_lock = False
1921 make_lock = False
1909
1922
1910 return make_lock, currently_locked, lock_info
1923 return make_lock, currently_locked, lock_info
1911
1924
1912 @property
1925 @property
1913 def last_db_change(self):
1926 def last_db_change(self):
1914 return self.updated_on
1927 return self.updated_on
1915
1928
1916 @property
1929 @property
1917 def clone_uri_hidden(self):
1930 def clone_uri_hidden(self):
1918 clone_uri = self.clone_uri
1931 clone_uri = self.clone_uri
1919 if clone_uri:
1932 if clone_uri:
1920 import urlobject
1933 import urlobject
1921 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1934 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1922 if url_obj.password:
1935 if url_obj.password:
1923 clone_uri = url_obj.with_password('*****')
1936 clone_uri = url_obj.with_password('*****')
1924 return clone_uri
1937 return clone_uri
1925
1938
1926 def clone_url(self, **override):
1939 def clone_url(self, **override):
1927
1940
1928 uri_tmpl = None
1941 uri_tmpl = None
1929 if 'with_id' in override:
1942 if 'with_id' in override:
1930 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1943 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1931 del override['with_id']
1944 del override['with_id']
1932
1945
1933 if 'uri_tmpl' in override:
1946 if 'uri_tmpl' in override:
1934 uri_tmpl = override['uri_tmpl']
1947 uri_tmpl = override['uri_tmpl']
1935 del override['uri_tmpl']
1948 del override['uri_tmpl']
1936
1949
1937 # we didn't override our tmpl from **overrides
1950 # we didn't override our tmpl from **overrides
1938 if not uri_tmpl:
1951 if not uri_tmpl:
1939 uri_tmpl = self.DEFAULT_CLONE_URI
1952 uri_tmpl = self.DEFAULT_CLONE_URI
1940 try:
1953 try:
1941 from pylons import tmpl_context as c
1954 from pylons import tmpl_context as c
1942 uri_tmpl = c.clone_uri_tmpl
1955 uri_tmpl = c.clone_uri_tmpl
1943 except Exception:
1956 except Exception:
1944 # in any case if we call this outside of request context,
1957 # in any case if we call this outside of request context,
1945 # ie, not having tmpl_context set up
1958 # ie, not having tmpl_context set up
1946 pass
1959 pass
1947
1960
1948 request = get_current_request()
1961 request = get_current_request()
1949 return get_clone_url(request=request,
1962 return get_clone_url(request=request,
1950 uri_tmpl=uri_tmpl,
1963 uri_tmpl=uri_tmpl,
1951 repo_name=self.repo_name,
1964 repo_name=self.repo_name,
1952 repo_id=self.repo_id, **override)
1965 repo_id=self.repo_id, **override)
1953
1966
1954 def set_state(self, state):
1967 def set_state(self, state):
1955 self.repo_state = state
1968 self.repo_state = state
1956 Session().add(self)
1969 Session().add(self)
1957 #==========================================================================
1970 #==========================================================================
1958 # SCM PROPERTIES
1971 # SCM PROPERTIES
1959 #==========================================================================
1972 #==========================================================================
1960
1973
1961 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1974 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1962 return get_commit_safe(
1975 return get_commit_safe(
1963 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1976 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1964
1977
1965 def get_changeset(self, rev=None, pre_load=None):
1978 def get_changeset(self, rev=None, pre_load=None):
1966 warnings.warn("Use get_commit", DeprecationWarning)
1979 warnings.warn("Use get_commit", DeprecationWarning)
1967 commit_id = None
1980 commit_id = None
1968 commit_idx = None
1981 commit_idx = None
1969 if isinstance(rev, basestring):
1982 if isinstance(rev, basestring):
1970 commit_id = rev
1983 commit_id = rev
1971 else:
1984 else:
1972 commit_idx = rev
1985 commit_idx = rev
1973 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1986 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1974 pre_load=pre_load)
1987 pre_load=pre_load)
1975
1988
1976 def get_landing_commit(self):
1989 def get_landing_commit(self):
1977 """
1990 """
1978 Returns landing commit, or if that doesn't exist returns the tip
1991 Returns landing commit, or if that doesn't exist returns the tip
1979 """
1992 """
1980 _rev_type, _rev = self.landing_rev
1993 _rev_type, _rev = self.landing_rev
1981 commit = self.get_commit(_rev)
1994 commit = self.get_commit(_rev)
1982 if isinstance(commit, EmptyCommit):
1995 if isinstance(commit, EmptyCommit):
1983 return self.get_commit()
1996 return self.get_commit()
1984 return commit
1997 return commit
1985
1998
1986 def update_commit_cache(self, cs_cache=None, config=None):
1999 def update_commit_cache(self, cs_cache=None, config=None):
1987 """
2000 """
1988 Update cache of last changeset for repository, keys should be::
2001 Update cache of last changeset for repository, keys should be::
1989
2002
1990 short_id
2003 short_id
1991 raw_id
2004 raw_id
1992 revision
2005 revision
1993 parents
2006 parents
1994 message
2007 message
1995 date
2008 date
1996 author
2009 author
1997
2010
1998 :param cs_cache:
2011 :param cs_cache:
1999 """
2012 """
2000 from rhodecode.lib.vcs.backends.base import BaseChangeset
2013 from rhodecode.lib.vcs.backends.base import BaseChangeset
2001 if cs_cache is None:
2014 if cs_cache is None:
2002 # use no-cache version here
2015 # use no-cache version here
2003 scm_repo = self.scm_instance(cache=False, config=config)
2016 scm_repo = self.scm_instance(cache=False, config=config)
2004 if scm_repo:
2017 if scm_repo:
2005 cs_cache = scm_repo.get_commit(
2018 cs_cache = scm_repo.get_commit(
2006 pre_load=["author", "date", "message", "parents"])
2019 pre_load=["author", "date", "message", "parents"])
2007 else:
2020 else:
2008 cs_cache = EmptyCommit()
2021 cs_cache = EmptyCommit()
2009
2022
2010 if isinstance(cs_cache, BaseChangeset):
2023 if isinstance(cs_cache, BaseChangeset):
2011 cs_cache = cs_cache.__json__()
2024 cs_cache = cs_cache.__json__()
2012
2025
2013 def is_outdated(new_cs_cache):
2026 def is_outdated(new_cs_cache):
2014 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2027 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2015 new_cs_cache['revision'] != self.changeset_cache['revision']):
2028 new_cs_cache['revision'] != self.changeset_cache['revision']):
2016 return True
2029 return True
2017 return False
2030 return False
2018
2031
2019 # check if we have maybe already latest cached revision
2032 # check if we have maybe already latest cached revision
2020 if is_outdated(cs_cache) or not self.changeset_cache:
2033 if is_outdated(cs_cache) or not self.changeset_cache:
2021 _default = datetime.datetime.fromtimestamp(0)
2034 _default = datetime.datetime.fromtimestamp(0)
2022 last_change = cs_cache.get('date') or _default
2035 last_change = cs_cache.get('date') or _default
2023 log.debug('updated repo %s with new cs cache %s',
2036 log.debug('updated repo %s with new cs cache %s',
2024 self.repo_name, cs_cache)
2037 self.repo_name, cs_cache)
2025 self.updated_on = last_change
2038 self.updated_on = last_change
2026 self.changeset_cache = cs_cache
2039 self.changeset_cache = cs_cache
2027 Session().add(self)
2040 Session().add(self)
2028 Session().commit()
2041 Session().commit()
2029 else:
2042 else:
2030 log.debug('Skipping update_commit_cache for repo:`%s` '
2043 log.debug('Skipping update_commit_cache for repo:`%s` '
2031 'commit already with latest changes', self.repo_name)
2044 'commit already with latest changes', self.repo_name)
2032
2045
2033 @property
2046 @property
2034 def tip(self):
2047 def tip(self):
2035 return self.get_commit('tip')
2048 return self.get_commit('tip')
2036
2049
2037 @property
2050 @property
2038 def author(self):
2051 def author(self):
2039 return self.tip.author
2052 return self.tip.author
2040
2053
2041 @property
2054 @property
2042 def last_change(self):
2055 def last_change(self):
2043 return self.scm_instance().last_change
2056 return self.scm_instance().last_change
2044
2057
2045 def get_comments(self, revisions=None):
2058 def get_comments(self, revisions=None):
2046 """
2059 """
2047 Returns comments for this repository grouped by revisions
2060 Returns comments for this repository grouped by revisions
2048
2061
2049 :param revisions: filter query by revisions only
2062 :param revisions: filter query by revisions only
2050 """
2063 """
2051 cmts = ChangesetComment.query()\
2064 cmts = ChangesetComment.query()\
2052 .filter(ChangesetComment.repo == self)
2065 .filter(ChangesetComment.repo == self)
2053 if revisions:
2066 if revisions:
2054 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2067 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2055 grouped = collections.defaultdict(list)
2068 grouped = collections.defaultdict(list)
2056 for cmt in cmts.all():
2069 for cmt in cmts.all():
2057 grouped[cmt.revision].append(cmt)
2070 grouped[cmt.revision].append(cmt)
2058 return grouped
2071 return grouped
2059
2072
2060 def statuses(self, revisions=None):
2073 def statuses(self, revisions=None):
2061 """
2074 """
2062 Returns statuses for this repository
2075 Returns statuses for this repository
2063
2076
2064 :param revisions: list of revisions to get statuses for
2077 :param revisions: list of revisions to get statuses for
2065 """
2078 """
2066 statuses = ChangesetStatus.query()\
2079 statuses = ChangesetStatus.query()\
2067 .filter(ChangesetStatus.repo == self)\
2080 .filter(ChangesetStatus.repo == self)\
2068 .filter(ChangesetStatus.version == 0)
2081 .filter(ChangesetStatus.version == 0)
2069
2082
2070 if revisions:
2083 if revisions:
2071 # Try doing the filtering in chunks to avoid hitting limits
2084 # Try doing the filtering in chunks to avoid hitting limits
2072 size = 500
2085 size = 500
2073 status_results = []
2086 status_results = []
2074 for chunk in xrange(0, len(revisions), size):
2087 for chunk in xrange(0, len(revisions), size):
2075 status_results += statuses.filter(
2088 status_results += statuses.filter(
2076 ChangesetStatus.revision.in_(
2089 ChangesetStatus.revision.in_(
2077 revisions[chunk: chunk+size])
2090 revisions[chunk: chunk+size])
2078 ).all()
2091 ).all()
2079 else:
2092 else:
2080 status_results = statuses.all()
2093 status_results = statuses.all()
2081
2094
2082 grouped = {}
2095 grouped = {}
2083
2096
2084 # maybe we have open new pullrequest without a status?
2097 # maybe we have open new pullrequest without a status?
2085 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2098 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2086 status_lbl = ChangesetStatus.get_status_lbl(stat)
2099 status_lbl = ChangesetStatus.get_status_lbl(stat)
2087 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2100 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2088 for rev in pr.revisions:
2101 for rev in pr.revisions:
2089 pr_id = pr.pull_request_id
2102 pr_id = pr.pull_request_id
2090 pr_repo = pr.target_repo.repo_name
2103 pr_repo = pr.target_repo.repo_name
2091 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2104 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2092
2105
2093 for stat in status_results:
2106 for stat in status_results:
2094 pr_id = pr_repo = None
2107 pr_id = pr_repo = None
2095 if stat.pull_request:
2108 if stat.pull_request:
2096 pr_id = stat.pull_request.pull_request_id
2109 pr_id = stat.pull_request.pull_request_id
2097 pr_repo = stat.pull_request.target_repo.repo_name
2110 pr_repo = stat.pull_request.target_repo.repo_name
2098 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2111 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2099 pr_id, pr_repo]
2112 pr_id, pr_repo]
2100 return grouped
2113 return grouped
2101
2114
2102 # ==========================================================================
2115 # ==========================================================================
2103 # SCM CACHE INSTANCE
2116 # SCM CACHE INSTANCE
2104 # ==========================================================================
2117 # ==========================================================================
2105
2118
2106 def scm_instance(self, **kwargs):
2119 def scm_instance(self, **kwargs):
2107 import rhodecode
2120 import rhodecode
2108
2121
2109 # Passing a config will not hit the cache currently only used
2122 # Passing a config will not hit the cache currently only used
2110 # for repo2dbmapper
2123 # for repo2dbmapper
2111 config = kwargs.pop('config', None)
2124 config = kwargs.pop('config', None)
2112 cache = kwargs.pop('cache', None)
2125 cache = kwargs.pop('cache', None)
2113 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2126 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2114 # if cache is NOT defined use default global, else we have a full
2127 # if cache is NOT defined use default global, else we have a full
2115 # control over cache behaviour
2128 # control over cache behaviour
2116 if cache is None and full_cache and not config:
2129 if cache is None and full_cache and not config:
2117 return self._get_instance_cached()
2130 return self._get_instance_cached()
2118 return self._get_instance(cache=bool(cache), config=config)
2131 return self._get_instance(cache=bool(cache), config=config)
2119
2132
2120 def _get_instance_cached(self):
2133 def _get_instance_cached(self):
2121 @cache_region('long_term')
2134 @cache_region('long_term')
2122 def _get_repo(cache_key):
2135 def _get_repo(cache_key):
2123 return self._get_instance()
2136 return self._get_instance()
2124
2137
2125 invalidator_context = CacheKey.repo_context_cache(
2138 invalidator_context = CacheKey.repo_context_cache(
2126 _get_repo, self.repo_name, None, thread_scoped=True)
2139 _get_repo, self.repo_name, None, thread_scoped=True)
2127
2140
2128 with invalidator_context as context:
2141 with invalidator_context as context:
2129 context.invalidate()
2142 context.invalidate()
2130 repo = context.compute()
2143 repo = context.compute()
2131
2144
2132 return repo
2145 return repo
2133
2146
2134 def _get_instance(self, cache=True, config=None):
2147 def _get_instance(self, cache=True, config=None):
2135 config = config or self._config
2148 config = config or self._config
2136 custom_wire = {
2149 custom_wire = {
2137 'cache': cache # controls the vcs.remote cache
2150 'cache': cache # controls the vcs.remote cache
2138 }
2151 }
2139 repo = get_vcs_instance(
2152 repo = get_vcs_instance(
2140 repo_path=safe_str(self.repo_full_path),
2153 repo_path=safe_str(self.repo_full_path),
2141 config=config,
2154 config=config,
2142 with_wire=custom_wire,
2155 with_wire=custom_wire,
2143 create=False,
2156 create=False,
2144 _vcs_alias=self.repo_type)
2157 _vcs_alias=self.repo_type)
2145
2158
2146 return repo
2159 return repo
2147
2160
2148 def __json__(self):
2161 def __json__(self):
2149 return {'landing_rev': self.landing_rev}
2162 return {'landing_rev': self.landing_rev}
2150
2163
2151 def get_dict(self):
2164 def get_dict(self):
2152
2165
2153 # Since we transformed `repo_name` to a hybrid property, we need to
2166 # Since we transformed `repo_name` to a hybrid property, we need to
2154 # keep compatibility with the code which uses `repo_name` field.
2167 # keep compatibility with the code which uses `repo_name` field.
2155
2168
2156 result = super(Repository, self).get_dict()
2169 result = super(Repository, self).get_dict()
2157 result['repo_name'] = result.pop('_repo_name', None)
2170 result['repo_name'] = result.pop('_repo_name', None)
2158 return result
2171 return result
2159
2172
2160
2173
2161 class RepoGroup(Base, BaseModel):
2174 class RepoGroup(Base, BaseModel):
2162 __tablename__ = 'groups'
2175 __tablename__ = 'groups'
2163 __table_args__ = (
2176 __table_args__ = (
2164 UniqueConstraint('group_name', 'group_parent_id'),
2177 UniqueConstraint('group_name', 'group_parent_id'),
2165 CheckConstraint('group_id != group_parent_id'),
2178 CheckConstraint('group_id != group_parent_id'),
2166 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2167 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2168 )
2181 )
2169 __mapper_args__ = {'order_by': 'group_name'}
2182 __mapper_args__ = {'order_by': 'group_name'}
2170
2183
2171 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2184 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2172
2185
2173 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2186 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2174 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2187 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2175 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2188 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2176 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2189 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2177 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2190 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2178 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2179 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2192 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2180 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2193 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2181
2194
2182 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2195 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2183 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2196 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2184 parent_group = relationship('RepoGroup', remote_side=group_id)
2197 parent_group = relationship('RepoGroup', remote_side=group_id)
2185 user = relationship('User')
2198 user = relationship('User')
2186 integrations = relationship('Integration',
2199 integrations = relationship('Integration',
2187 cascade="all, delete, delete-orphan")
2200 cascade="all, delete, delete-orphan")
2188
2201
2189 def __init__(self, group_name='', parent_group=None):
2202 def __init__(self, group_name='', parent_group=None):
2190 self.group_name = group_name
2203 self.group_name = group_name
2191 self.parent_group = parent_group
2204 self.parent_group = parent_group
2192
2205
2193 def __unicode__(self):
2206 def __unicode__(self):
2194 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2207 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2195 self.group_name)
2208 self.group_name)
2196
2209
2197 @classmethod
2210 @classmethod
2198 def _generate_choice(cls, repo_group):
2211 def _generate_choice(cls, repo_group):
2199 from webhelpers.html import literal as _literal
2212 from webhelpers.html import literal as _literal
2200 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2213 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2201 return repo_group.group_id, _name(repo_group.full_path_splitted)
2214 return repo_group.group_id, _name(repo_group.full_path_splitted)
2202
2215
2203 @classmethod
2216 @classmethod
2204 def groups_choices(cls, groups=None, show_empty_group=True):
2217 def groups_choices(cls, groups=None, show_empty_group=True):
2205 if not groups:
2218 if not groups:
2206 groups = cls.query().all()
2219 groups = cls.query().all()
2207
2220
2208 repo_groups = []
2221 repo_groups = []
2209 if show_empty_group:
2222 if show_empty_group:
2210 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2223 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2211
2224
2212 repo_groups.extend([cls._generate_choice(x) for x in groups])
2225 repo_groups.extend([cls._generate_choice(x) for x in groups])
2213
2226
2214 repo_groups = sorted(
2227 repo_groups = sorted(
2215 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2228 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2216 return repo_groups
2229 return repo_groups
2217
2230
2218 @classmethod
2231 @classmethod
2219 def url_sep(cls):
2232 def url_sep(cls):
2220 return URL_SEP
2233 return URL_SEP
2221
2234
2222 @classmethod
2235 @classmethod
2223 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2236 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2224 if case_insensitive:
2237 if case_insensitive:
2225 gr = cls.query().filter(func.lower(cls.group_name)
2238 gr = cls.query().filter(func.lower(cls.group_name)
2226 == func.lower(group_name))
2239 == func.lower(group_name))
2227 else:
2240 else:
2228 gr = cls.query().filter(cls.group_name == group_name)
2241 gr = cls.query().filter(cls.group_name == group_name)
2229 if cache:
2242 if cache:
2230 name_key = _hash_key(group_name)
2243 name_key = _hash_key(group_name)
2231 gr = gr.options(
2244 gr = gr.options(
2232 FromCache("sql_cache_short", "get_group_%s" % name_key))
2245 FromCache("sql_cache_short", "get_group_%s" % name_key))
2233 return gr.scalar()
2246 return gr.scalar()
2234
2247
2235 @classmethod
2248 @classmethod
2236 def get_user_personal_repo_group(cls, user_id):
2249 def get_user_personal_repo_group(cls, user_id):
2237 user = User.get(user_id)
2250 user = User.get(user_id)
2238 if user.username == User.DEFAULT_USER:
2251 if user.username == User.DEFAULT_USER:
2239 return None
2252 return None
2240
2253
2241 return cls.query()\
2254 return cls.query()\
2242 .filter(cls.personal == true()) \
2255 .filter(cls.personal == true()) \
2243 .filter(cls.user == user).scalar()
2256 .filter(cls.user == user).scalar()
2244
2257
2245 @classmethod
2258 @classmethod
2246 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2259 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2247 case_insensitive=True):
2260 case_insensitive=True):
2248 q = RepoGroup.query()
2261 q = RepoGroup.query()
2249
2262
2250 if not isinstance(user_id, Optional):
2263 if not isinstance(user_id, Optional):
2251 q = q.filter(RepoGroup.user_id == user_id)
2264 q = q.filter(RepoGroup.user_id == user_id)
2252
2265
2253 if not isinstance(group_id, Optional):
2266 if not isinstance(group_id, Optional):
2254 q = q.filter(RepoGroup.group_parent_id == group_id)
2267 q = q.filter(RepoGroup.group_parent_id == group_id)
2255
2268
2256 if case_insensitive:
2269 if case_insensitive:
2257 q = q.order_by(func.lower(RepoGroup.group_name))
2270 q = q.order_by(func.lower(RepoGroup.group_name))
2258 else:
2271 else:
2259 q = q.order_by(RepoGroup.group_name)
2272 q = q.order_by(RepoGroup.group_name)
2260 return q.all()
2273 return q.all()
2261
2274
2262 @property
2275 @property
2263 def parents(self):
2276 def parents(self):
2264 parents_recursion_limit = 10
2277 parents_recursion_limit = 10
2265 groups = []
2278 groups = []
2266 if self.parent_group is None:
2279 if self.parent_group is None:
2267 return groups
2280 return groups
2268 cur_gr = self.parent_group
2281 cur_gr = self.parent_group
2269 groups.insert(0, cur_gr)
2282 groups.insert(0, cur_gr)
2270 cnt = 0
2283 cnt = 0
2271 while 1:
2284 while 1:
2272 cnt += 1
2285 cnt += 1
2273 gr = getattr(cur_gr, 'parent_group', None)
2286 gr = getattr(cur_gr, 'parent_group', None)
2274 cur_gr = cur_gr.parent_group
2287 cur_gr = cur_gr.parent_group
2275 if gr is None:
2288 if gr is None:
2276 break
2289 break
2277 if cnt == parents_recursion_limit:
2290 if cnt == parents_recursion_limit:
2278 # this will prevent accidental infinit loops
2291 # this will prevent accidental infinit loops
2279 log.error(('more than %s parents found for group %s, stopping '
2292 log.error(('more than %s parents found for group %s, stopping '
2280 'recursive parent fetching' % (parents_recursion_limit, self)))
2293 'recursive parent fetching' % (parents_recursion_limit, self)))
2281 break
2294 break
2282
2295
2283 groups.insert(0, gr)
2296 groups.insert(0, gr)
2284 return groups
2297 return groups
2285
2298
2286 @property
2299 @property
2287 def children(self):
2300 def children(self):
2288 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2301 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2289
2302
2290 @property
2303 @property
2291 def name(self):
2304 def name(self):
2292 return self.group_name.split(RepoGroup.url_sep())[-1]
2305 return self.group_name.split(RepoGroup.url_sep())[-1]
2293
2306
2294 @property
2307 @property
2295 def full_path(self):
2308 def full_path(self):
2296 return self.group_name
2309 return self.group_name
2297
2310
2298 @property
2311 @property
2299 def full_path_splitted(self):
2312 def full_path_splitted(self):
2300 return self.group_name.split(RepoGroup.url_sep())
2313 return self.group_name.split(RepoGroup.url_sep())
2301
2314
2302 @property
2315 @property
2303 def repositories(self):
2316 def repositories(self):
2304 return Repository.query()\
2317 return Repository.query()\
2305 .filter(Repository.group == self)\
2318 .filter(Repository.group == self)\
2306 .order_by(Repository.repo_name)
2319 .order_by(Repository.repo_name)
2307
2320
2308 @property
2321 @property
2309 def repositories_recursive_count(self):
2322 def repositories_recursive_count(self):
2310 cnt = self.repositories.count()
2323 cnt = self.repositories.count()
2311
2324
2312 def children_count(group):
2325 def children_count(group):
2313 cnt = 0
2326 cnt = 0
2314 for child in group.children:
2327 for child in group.children:
2315 cnt += child.repositories.count()
2328 cnt += child.repositories.count()
2316 cnt += children_count(child)
2329 cnt += children_count(child)
2317 return cnt
2330 return cnt
2318
2331
2319 return cnt + children_count(self)
2332 return cnt + children_count(self)
2320
2333
2321 def _recursive_objects(self, include_repos=True):
2334 def _recursive_objects(self, include_repos=True):
2322 all_ = []
2335 all_ = []
2323
2336
2324 def _get_members(root_gr):
2337 def _get_members(root_gr):
2325 if include_repos:
2338 if include_repos:
2326 for r in root_gr.repositories:
2339 for r in root_gr.repositories:
2327 all_.append(r)
2340 all_.append(r)
2328 childs = root_gr.children.all()
2341 childs = root_gr.children.all()
2329 if childs:
2342 if childs:
2330 for gr in childs:
2343 for gr in childs:
2331 all_.append(gr)
2344 all_.append(gr)
2332 _get_members(gr)
2345 _get_members(gr)
2333
2346
2334 _get_members(self)
2347 _get_members(self)
2335 return [self] + all_
2348 return [self] + all_
2336
2349
2337 def recursive_groups_and_repos(self):
2350 def recursive_groups_and_repos(self):
2338 """
2351 """
2339 Recursive return all groups, with repositories in those groups
2352 Recursive return all groups, with repositories in those groups
2340 """
2353 """
2341 return self._recursive_objects()
2354 return self._recursive_objects()
2342
2355
2343 def recursive_groups(self):
2356 def recursive_groups(self):
2344 """
2357 """
2345 Returns all children groups for this group including children of children
2358 Returns all children groups for this group including children of children
2346 """
2359 """
2347 return self._recursive_objects(include_repos=False)
2360 return self._recursive_objects(include_repos=False)
2348
2361
2349 def get_new_name(self, group_name):
2362 def get_new_name(self, group_name):
2350 """
2363 """
2351 returns new full group name based on parent and new name
2364 returns new full group name based on parent and new name
2352
2365
2353 :param group_name:
2366 :param group_name:
2354 """
2367 """
2355 path_prefix = (self.parent_group.full_path_splitted if
2368 path_prefix = (self.parent_group.full_path_splitted if
2356 self.parent_group else [])
2369 self.parent_group else [])
2357 return RepoGroup.url_sep().join(path_prefix + [group_name])
2370 return RepoGroup.url_sep().join(path_prefix + [group_name])
2358
2371
2359 def permissions(self, with_admins=True, with_owner=True):
2372 def permissions(self, with_admins=True, with_owner=True):
2360 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2373 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2361 q = q.options(joinedload(UserRepoGroupToPerm.group),
2374 q = q.options(joinedload(UserRepoGroupToPerm.group),
2362 joinedload(UserRepoGroupToPerm.user),
2375 joinedload(UserRepoGroupToPerm.user),
2363 joinedload(UserRepoGroupToPerm.permission),)
2376 joinedload(UserRepoGroupToPerm.permission),)
2364
2377
2365 # get owners and admins and permissions. We do a trick of re-writing
2378 # get owners and admins and permissions. We do a trick of re-writing
2366 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2379 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2367 # has a global reference and changing one object propagates to all
2380 # has a global reference and changing one object propagates to all
2368 # others. This means if admin is also an owner admin_row that change
2381 # others. This means if admin is also an owner admin_row that change
2369 # would propagate to both objects
2382 # would propagate to both objects
2370 perm_rows = []
2383 perm_rows = []
2371 for _usr in q.all():
2384 for _usr in q.all():
2372 usr = AttributeDict(_usr.user.get_dict())
2385 usr = AttributeDict(_usr.user.get_dict())
2373 usr.permission = _usr.permission.permission_name
2386 usr.permission = _usr.permission.permission_name
2374 perm_rows.append(usr)
2387 perm_rows.append(usr)
2375
2388
2376 # filter the perm rows by 'default' first and then sort them by
2389 # filter the perm rows by 'default' first and then sort them by
2377 # admin,write,read,none permissions sorted again alphabetically in
2390 # admin,write,read,none permissions sorted again alphabetically in
2378 # each group
2391 # each group
2379 perm_rows = sorted(perm_rows, key=display_sort)
2392 perm_rows = sorted(perm_rows, key=display_sort)
2380
2393
2381 _admin_perm = 'group.admin'
2394 _admin_perm = 'group.admin'
2382 owner_row = []
2395 owner_row = []
2383 if with_owner:
2396 if with_owner:
2384 usr = AttributeDict(self.user.get_dict())
2397 usr = AttributeDict(self.user.get_dict())
2385 usr.owner_row = True
2398 usr.owner_row = True
2386 usr.permission = _admin_perm
2399 usr.permission = _admin_perm
2387 owner_row.append(usr)
2400 owner_row.append(usr)
2388
2401
2389 super_admin_rows = []
2402 super_admin_rows = []
2390 if with_admins:
2403 if with_admins:
2391 for usr in User.get_all_super_admins():
2404 for usr in User.get_all_super_admins():
2392 # if this admin is also owner, don't double the record
2405 # if this admin is also owner, don't double the record
2393 if usr.user_id == owner_row[0].user_id:
2406 if usr.user_id == owner_row[0].user_id:
2394 owner_row[0].admin_row = True
2407 owner_row[0].admin_row = True
2395 else:
2408 else:
2396 usr = AttributeDict(usr.get_dict())
2409 usr = AttributeDict(usr.get_dict())
2397 usr.admin_row = True
2410 usr.admin_row = True
2398 usr.permission = _admin_perm
2411 usr.permission = _admin_perm
2399 super_admin_rows.append(usr)
2412 super_admin_rows.append(usr)
2400
2413
2401 return super_admin_rows + owner_row + perm_rows
2414 return super_admin_rows + owner_row + perm_rows
2402
2415
2403 def permission_user_groups(self):
2416 def permission_user_groups(self):
2404 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2417 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2405 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2418 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2406 joinedload(UserGroupRepoGroupToPerm.users_group),
2419 joinedload(UserGroupRepoGroupToPerm.users_group),
2407 joinedload(UserGroupRepoGroupToPerm.permission),)
2420 joinedload(UserGroupRepoGroupToPerm.permission),)
2408
2421
2409 perm_rows = []
2422 perm_rows = []
2410 for _user_group in q.all():
2423 for _user_group in q.all():
2411 usr = AttributeDict(_user_group.users_group.get_dict())
2424 usr = AttributeDict(_user_group.users_group.get_dict())
2412 usr.permission = _user_group.permission.permission_name
2425 usr.permission = _user_group.permission.permission_name
2413 perm_rows.append(usr)
2426 perm_rows.append(usr)
2414
2427
2415 return perm_rows
2428 return perm_rows
2416
2429
2417 def get_api_data(self):
2430 def get_api_data(self):
2418 """
2431 """
2419 Common function for generating api data
2432 Common function for generating api data
2420
2433
2421 """
2434 """
2422 group = self
2435 group = self
2423 data = {
2436 data = {
2424 'group_id': group.group_id,
2437 'group_id': group.group_id,
2425 'group_name': group.group_name,
2438 'group_name': group.group_name,
2426 'group_description': group.group_description,
2439 'group_description': group.group_description,
2427 'parent_group': group.parent_group.group_name if group.parent_group else None,
2440 'parent_group': group.parent_group.group_name if group.parent_group else None,
2428 'repositories': [x.repo_name for x in group.repositories],
2441 'repositories': [x.repo_name for x in group.repositories],
2429 'owner': group.user.username,
2442 'owner': group.user.username,
2430 }
2443 }
2431 return data
2444 return data
2432
2445
2433
2446
2434 class Permission(Base, BaseModel):
2447 class Permission(Base, BaseModel):
2435 __tablename__ = 'permissions'
2448 __tablename__ = 'permissions'
2436 __table_args__ = (
2449 __table_args__ = (
2437 Index('p_perm_name_idx', 'permission_name'),
2450 Index('p_perm_name_idx', 'permission_name'),
2438 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2451 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2439 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2452 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2440 )
2453 )
2441 PERMS = [
2454 PERMS = [
2442 ('hg.admin', _('RhodeCode Super Administrator')),
2455 ('hg.admin', _('RhodeCode Super Administrator')),
2443
2456
2444 ('repository.none', _('Repository no access')),
2457 ('repository.none', _('Repository no access')),
2445 ('repository.read', _('Repository read access')),
2458 ('repository.read', _('Repository read access')),
2446 ('repository.write', _('Repository write access')),
2459 ('repository.write', _('Repository write access')),
2447 ('repository.admin', _('Repository admin access')),
2460 ('repository.admin', _('Repository admin access')),
2448
2461
2449 ('group.none', _('Repository group no access')),
2462 ('group.none', _('Repository group no access')),
2450 ('group.read', _('Repository group read access')),
2463 ('group.read', _('Repository group read access')),
2451 ('group.write', _('Repository group write access')),
2464 ('group.write', _('Repository group write access')),
2452 ('group.admin', _('Repository group admin access')),
2465 ('group.admin', _('Repository group admin access')),
2453
2466
2454 ('usergroup.none', _('User group no access')),
2467 ('usergroup.none', _('User group no access')),
2455 ('usergroup.read', _('User group read access')),
2468 ('usergroup.read', _('User group read access')),
2456 ('usergroup.write', _('User group write access')),
2469 ('usergroup.write', _('User group write access')),
2457 ('usergroup.admin', _('User group admin access')),
2470 ('usergroup.admin', _('User group admin access')),
2458
2471
2459 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2472 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2460 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2473 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2461
2474
2462 ('hg.usergroup.create.false', _('User Group creation disabled')),
2475 ('hg.usergroup.create.false', _('User Group creation disabled')),
2463 ('hg.usergroup.create.true', _('User Group creation enabled')),
2476 ('hg.usergroup.create.true', _('User Group creation enabled')),
2464
2477
2465 ('hg.create.none', _('Repository creation disabled')),
2478 ('hg.create.none', _('Repository creation disabled')),
2466 ('hg.create.repository', _('Repository creation enabled')),
2479 ('hg.create.repository', _('Repository creation enabled')),
2467 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2480 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2468 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2481 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2469
2482
2470 ('hg.fork.none', _('Repository forking disabled')),
2483 ('hg.fork.none', _('Repository forking disabled')),
2471 ('hg.fork.repository', _('Repository forking enabled')),
2484 ('hg.fork.repository', _('Repository forking enabled')),
2472
2485
2473 ('hg.register.none', _('Registration disabled')),
2486 ('hg.register.none', _('Registration disabled')),
2474 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2487 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2475 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2488 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2476
2489
2477 ('hg.password_reset.enabled', _('Password reset enabled')),
2490 ('hg.password_reset.enabled', _('Password reset enabled')),
2478 ('hg.password_reset.hidden', _('Password reset hidden')),
2491 ('hg.password_reset.hidden', _('Password reset hidden')),
2479 ('hg.password_reset.disabled', _('Password reset disabled')),
2492 ('hg.password_reset.disabled', _('Password reset disabled')),
2480
2493
2481 ('hg.extern_activate.manual', _('Manual activation of external account')),
2494 ('hg.extern_activate.manual', _('Manual activation of external account')),
2482 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2495 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2483
2496
2484 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2497 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2485 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2498 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2486 ]
2499 ]
2487
2500
2488 # definition of system default permissions for DEFAULT user
2501 # definition of system default permissions for DEFAULT user
2489 DEFAULT_USER_PERMISSIONS = [
2502 DEFAULT_USER_PERMISSIONS = [
2490 'repository.read',
2503 'repository.read',
2491 'group.read',
2504 'group.read',
2492 'usergroup.read',
2505 'usergroup.read',
2493 'hg.create.repository',
2506 'hg.create.repository',
2494 'hg.repogroup.create.false',
2507 'hg.repogroup.create.false',
2495 'hg.usergroup.create.false',
2508 'hg.usergroup.create.false',
2496 'hg.create.write_on_repogroup.true',
2509 'hg.create.write_on_repogroup.true',
2497 'hg.fork.repository',
2510 'hg.fork.repository',
2498 'hg.register.manual_activate',
2511 'hg.register.manual_activate',
2499 'hg.password_reset.enabled',
2512 'hg.password_reset.enabled',
2500 'hg.extern_activate.auto',
2513 'hg.extern_activate.auto',
2501 'hg.inherit_default_perms.true',
2514 'hg.inherit_default_perms.true',
2502 ]
2515 ]
2503
2516
2504 # defines which permissions are more important higher the more important
2517 # defines which permissions are more important higher the more important
2505 # Weight defines which permissions are more important.
2518 # Weight defines which permissions are more important.
2506 # The higher number the more important.
2519 # The higher number the more important.
2507 PERM_WEIGHTS = {
2520 PERM_WEIGHTS = {
2508 'repository.none': 0,
2521 'repository.none': 0,
2509 'repository.read': 1,
2522 'repository.read': 1,
2510 'repository.write': 3,
2523 'repository.write': 3,
2511 'repository.admin': 4,
2524 'repository.admin': 4,
2512
2525
2513 'group.none': 0,
2526 'group.none': 0,
2514 'group.read': 1,
2527 'group.read': 1,
2515 'group.write': 3,
2528 'group.write': 3,
2516 'group.admin': 4,
2529 'group.admin': 4,
2517
2530
2518 'usergroup.none': 0,
2531 'usergroup.none': 0,
2519 'usergroup.read': 1,
2532 'usergroup.read': 1,
2520 'usergroup.write': 3,
2533 'usergroup.write': 3,
2521 'usergroup.admin': 4,
2534 'usergroup.admin': 4,
2522
2535
2523 'hg.repogroup.create.false': 0,
2536 'hg.repogroup.create.false': 0,
2524 'hg.repogroup.create.true': 1,
2537 'hg.repogroup.create.true': 1,
2525
2538
2526 'hg.usergroup.create.false': 0,
2539 'hg.usergroup.create.false': 0,
2527 'hg.usergroup.create.true': 1,
2540 'hg.usergroup.create.true': 1,
2528
2541
2529 'hg.fork.none': 0,
2542 'hg.fork.none': 0,
2530 'hg.fork.repository': 1,
2543 'hg.fork.repository': 1,
2531 'hg.create.none': 0,
2544 'hg.create.none': 0,
2532 'hg.create.repository': 1
2545 'hg.create.repository': 1
2533 }
2546 }
2534
2547
2535 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2548 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2536 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2549 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2537 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2550 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2538
2551
2539 def __unicode__(self):
2552 def __unicode__(self):
2540 return u"<%s('%s:%s')>" % (
2553 return u"<%s('%s:%s')>" % (
2541 self.__class__.__name__, self.permission_id, self.permission_name
2554 self.__class__.__name__, self.permission_id, self.permission_name
2542 )
2555 )
2543
2556
2544 @classmethod
2557 @classmethod
2545 def get_by_key(cls, key):
2558 def get_by_key(cls, key):
2546 return cls.query().filter(cls.permission_name == key).scalar()
2559 return cls.query().filter(cls.permission_name == key).scalar()
2547
2560
2548 @classmethod
2561 @classmethod
2549 def get_default_repo_perms(cls, user_id, repo_id=None):
2562 def get_default_repo_perms(cls, user_id, repo_id=None):
2550 q = Session().query(UserRepoToPerm, Repository, Permission)\
2563 q = Session().query(UserRepoToPerm, Repository, Permission)\
2551 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2564 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2552 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2565 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2553 .filter(UserRepoToPerm.user_id == user_id)
2566 .filter(UserRepoToPerm.user_id == user_id)
2554 if repo_id:
2567 if repo_id:
2555 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2568 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2556 return q.all()
2569 return q.all()
2557
2570
2558 @classmethod
2571 @classmethod
2559 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2572 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2560 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2573 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2561 .join(
2574 .join(
2562 Permission,
2575 Permission,
2563 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2576 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2564 .join(
2577 .join(
2565 Repository,
2578 Repository,
2566 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2579 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2567 .join(
2580 .join(
2568 UserGroup,
2581 UserGroup,
2569 UserGroupRepoToPerm.users_group_id ==
2582 UserGroupRepoToPerm.users_group_id ==
2570 UserGroup.users_group_id)\
2583 UserGroup.users_group_id)\
2571 .join(
2584 .join(
2572 UserGroupMember,
2585 UserGroupMember,
2573 UserGroupRepoToPerm.users_group_id ==
2586 UserGroupRepoToPerm.users_group_id ==
2574 UserGroupMember.users_group_id)\
2587 UserGroupMember.users_group_id)\
2575 .filter(
2588 .filter(
2576 UserGroupMember.user_id == user_id,
2589 UserGroupMember.user_id == user_id,
2577 UserGroup.users_group_active == true())
2590 UserGroup.users_group_active == true())
2578 if repo_id:
2591 if repo_id:
2579 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2592 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2580 return q.all()
2593 return q.all()
2581
2594
2582 @classmethod
2595 @classmethod
2583 def get_default_group_perms(cls, user_id, repo_group_id=None):
2596 def get_default_group_perms(cls, user_id, repo_group_id=None):
2584 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2597 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2585 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2598 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2586 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2599 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2587 .filter(UserRepoGroupToPerm.user_id == user_id)
2600 .filter(UserRepoGroupToPerm.user_id == user_id)
2588 if repo_group_id:
2601 if repo_group_id:
2589 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2602 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2590 return q.all()
2603 return q.all()
2591
2604
2592 @classmethod
2605 @classmethod
2593 def get_default_group_perms_from_user_group(
2606 def get_default_group_perms_from_user_group(
2594 cls, user_id, repo_group_id=None):
2607 cls, user_id, repo_group_id=None):
2595 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2608 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2596 .join(
2609 .join(
2597 Permission,
2610 Permission,
2598 UserGroupRepoGroupToPerm.permission_id ==
2611 UserGroupRepoGroupToPerm.permission_id ==
2599 Permission.permission_id)\
2612 Permission.permission_id)\
2600 .join(
2613 .join(
2601 RepoGroup,
2614 RepoGroup,
2602 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2615 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2603 .join(
2616 .join(
2604 UserGroup,
2617 UserGroup,
2605 UserGroupRepoGroupToPerm.users_group_id ==
2618 UserGroupRepoGroupToPerm.users_group_id ==
2606 UserGroup.users_group_id)\
2619 UserGroup.users_group_id)\
2607 .join(
2620 .join(
2608 UserGroupMember,
2621 UserGroupMember,
2609 UserGroupRepoGroupToPerm.users_group_id ==
2622 UserGroupRepoGroupToPerm.users_group_id ==
2610 UserGroupMember.users_group_id)\
2623 UserGroupMember.users_group_id)\
2611 .filter(
2624 .filter(
2612 UserGroupMember.user_id == user_id,
2625 UserGroupMember.user_id == user_id,
2613 UserGroup.users_group_active == true())
2626 UserGroup.users_group_active == true())
2614 if repo_group_id:
2627 if repo_group_id:
2615 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2628 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2616 return q.all()
2629 return q.all()
2617
2630
2618 @classmethod
2631 @classmethod
2619 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2632 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2620 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2633 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2621 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2634 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2622 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2635 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2623 .filter(UserUserGroupToPerm.user_id == user_id)
2636 .filter(UserUserGroupToPerm.user_id == user_id)
2624 if user_group_id:
2637 if user_group_id:
2625 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2638 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2626 return q.all()
2639 return q.all()
2627
2640
2628 @classmethod
2641 @classmethod
2629 def get_default_user_group_perms_from_user_group(
2642 def get_default_user_group_perms_from_user_group(
2630 cls, user_id, user_group_id=None):
2643 cls, user_id, user_group_id=None):
2631 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2644 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2632 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2645 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2633 .join(
2646 .join(
2634 Permission,
2647 Permission,
2635 UserGroupUserGroupToPerm.permission_id ==
2648 UserGroupUserGroupToPerm.permission_id ==
2636 Permission.permission_id)\
2649 Permission.permission_id)\
2637 .join(
2650 .join(
2638 TargetUserGroup,
2651 TargetUserGroup,
2639 UserGroupUserGroupToPerm.target_user_group_id ==
2652 UserGroupUserGroupToPerm.target_user_group_id ==
2640 TargetUserGroup.users_group_id)\
2653 TargetUserGroup.users_group_id)\
2641 .join(
2654 .join(
2642 UserGroup,
2655 UserGroup,
2643 UserGroupUserGroupToPerm.user_group_id ==
2656 UserGroupUserGroupToPerm.user_group_id ==
2644 UserGroup.users_group_id)\
2657 UserGroup.users_group_id)\
2645 .join(
2658 .join(
2646 UserGroupMember,
2659 UserGroupMember,
2647 UserGroupUserGroupToPerm.user_group_id ==
2660 UserGroupUserGroupToPerm.user_group_id ==
2648 UserGroupMember.users_group_id)\
2661 UserGroupMember.users_group_id)\
2649 .filter(
2662 .filter(
2650 UserGroupMember.user_id == user_id,
2663 UserGroupMember.user_id == user_id,
2651 UserGroup.users_group_active == true())
2664 UserGroup.users_group_active == true())
2652 if user_group_id:
2665 if user_group_id:
2653 q = q.filter(
2666 q = q.filter(
2654 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2667 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2655
2668
2656 return q.all()
2669 return q.all()
2657
2670
2658
2671
2659 class UserRepoToPerm(Base, BaseModel):
2672 class UserRepoToPerm(Base, BaseModel):
2660 __tablename__ = 'repo_to_perm'
2673 __tablename__ = 'repo_to_perm'
2661 __table_args__ = (
2674 __table_args__ = (
2662 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2675 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2663 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2664 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2677 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2665 )
2678 )
2666 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2667 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2668 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2681 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2669 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2682 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2670
2683
2671 user = relationship('User')
2684 user = relationship('User')
2672 repository = relationship('Repository')
2685 repository = relationship('Repository')
2673 permission = relationship('Permission')
2686 permission = relationship('Permission')
2674
2687
2675 @classmethod
2688 @classmethod
2676 def create(cls, user, repository, permission):
2689 def create(cls, user, repository, permission):
2677 n = cls()
2690 n = cls()
2678 n.user = user
2691 n.user = user
2679 n.repository = repository
2692 n.repository = repository
2680 n.permission = permission
2693 n.permission = permission
2681 Session().add(n)
2694 Session().add(n)
2682 return n
2695 return n
2683
2696
2684 def __unicode__(self):
2697 def __unicode__(self):
2685 return u'<%s => %s >' % (self.user, self.repository)
2698 return u'<%s => %s >' % (self.user, self.repository)
2686
2699
2687
2700
2688 class UserUserGroupToPerm(Base, BaseModel):
2701 class UserUserGroupToPerm(Base, BaseModel):
2689 __tablename__ = 'user_user_group_to_perm'
2702 __tablename__ = 'user_user_group_to_perm'
2690 __table_args__ = (
2703 __table_args__ = (
2691 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2704 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2693 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2706 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2694 )
2707 )
2695 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2708 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2696 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2709 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2697 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2710 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2698 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2711 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2699
2712
2700 user = relationship('User')
2713 user = relationship('User')
2701 user_group = relationship('UserGroup')
2714 user_group = relationship('UserGroup')
2702 permission = relationship('Permission')
2715 permission = relationship('Permission')
2703
2716
2704 @classmethod
2717 @classmethod
2705 def create(cls, user, user_group, permission):
2718 def create(cls, user, user_group, permission):
2706 n = cls()
2719 n = cls()
2707 n.user = user
2720 n.user = user
2708 n.user_group = user_group
2721 n.user_group = user_group
2709 n.permission = permission
2722 n.permission = permission
2710 Session().add(n)
2723 Session().add(n)
2711 return n
2724 return n
2712
2725
2713 def __unicode__(self):
2726 def __unicode__(self):
2714 return u'<%s => %s >' % (self.user, self.user_group)
2727 return u'<%s => %s >' % (self.user, self.user_group)
2715
2728
2716
2729
2717 class UserToPerm(Base, BaseModel):
2730 class UserToPerm(Base, BaseModel):
2718 __tablename__ = 'user_to_perm'
2731 __tablename__ = 'user_to_perm'
2719 __table_args__ = (
2732 __table_args__ = (
2720 UniqueConstraint('user_id', 'permission_id'),
2733 UniqueConstraint('user_id', 'permission_id'),
2721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2735 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2723 )
2736 )
2724 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2737 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2725 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2738 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2726 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2739 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2727
2740
2728 user = relationship('User')
2741 user = relationship('User')
2729 permission = relationship('Permission', lazy='joined')
2742 permission = relationship('Permission', lazy='joined')
2730
2743
2731 def __unicode__(self):
2744 def __unicode__(self):
2732 return u'<%s => %s >' % (self.user, self.permission)
2745 return u'<%s => %s >' % (self.user, self.permission)
2733
2746
2734
2747
2735 class UserGroupRepoToPerm(Base, BaseModel):
2748 class UserGroupRepoToPerm(Base, BaseModel):
2736 __tablename__ = 'users_group_repo_to_perm'
2749 __tablename__ = 'users_group_repo_to_perm'
2737 __table_args__ = (
2750 __table_args__ = (
2738 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2751 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2740 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2741 )
2754 )
2742 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2755 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2743 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2756 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2744 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2757 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2745 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2758 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2746
2759
2747 users_group = relationship('UserGroup')
2760 users_group = relationship('UserGroup')
2748 permission = relationship('Permission')
2761 permission = relationship('Permission')
2749 repository = relationship('Repository')
2762 repository = relationship('Repository')
2750
2763
2751 @classmethod
2764 @classmethod
2752 def create(cls, users_group, repository, permission):
2765 def create(cls, users_group, repository, permission):
2753 n = cls()
2766 n = cls()
2754 n.users_group = users_group
2767 n.users_group = users_group
2755 n.repository = repository
2768 n.repository = repository
2756 n.permission = permission
2769 n.permission = permission
2757 Session().add(n)
2770 Session().add(n)
2758 return n
2771 return n
2759
2772
2760 def __unicode__(self):
2773 def __unicode__(self):
2761 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2774 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2762
2775
2763
2776
2764 class UserGroupUserGroupToPerm(Base, BaseModel):
2777 class UserGroupUserGroupToPerm(Base, BaseModel):
2765 __tablename__ = 'user_group_user_group_to_perm'
2778 __tablename__ = 'user_group_user_group_to_perm'
2766 __table_args__ = (
2779 __table_args__ = (
2767 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2780 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2768 CheckConstraint('target_user_group_id != user_group_id'),
2781 CheckConstraint('target_user_group_id != user_group_id'),
2769 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2782 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2770 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2783 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2771 )
2784 )
2772 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)
2785 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)
2773 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2786 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2774 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2787 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2775 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2788 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2776
2789
2777 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2790 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2778 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2791 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2779 permission = relationship('Permission')
2792 permission = relationship('Permission')
2780
2793
2781 @classmethod
2794 @classmethod
2782 def create(cls, target_user_group, user_group, permission):
2795 def create(cls, target_user_group, user_group, permission):
2783 n = cls()
2796 n = cls()
2784 n.target_user_group = target_user_group
2797 n.target_user_group = target_user_group
2785 n.user_group = user_group
2798 n.user_group = user_group
2786 n.permission = permission
2799 n.permission = permission
2787 Session().add(n)
2800 Session().add(n)
2788 return n
2801 return n
2789
2802
2790 def __unicode__(self):
2803 def __unicode__(self):
2791 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2804 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2792
2805
2793
2806
2794 class UserGroupToPerm(Base, BaseModel):
2807 class UserGroupToPerm(Base, BaseModel):
2795 __tablename__ = 'users_group_to_perm'
2808 __tablename__ = 'users_group_to_perm'
2796 __table_args__ = (
2809 __table_args__ = (
2797 UniqueConstraint('users_group_id', 'permission_id',),
2810 UniqueConstraint('users_group_id', 'permission_id',),
2798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2811 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2812 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2800 )
2813 )
2801 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2814 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2802 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2815 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2803 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2804
2817
2805 users_group = relationship('UserGroup')
2818 users_group = relationship('UserGroup')
2806 permission = relationship('Permission')
2819 permission = relationship('Permission')
2807
2820
2808
2821
2809 class UserRepoGroupToPerm(Base, BaseModel):
2822 class UserRepoGroupToPerm(Base, BaseModel):
2810 __tablename__ = 'user_repo_group_to_perm'
2823 __tablename__ = 'user_repo_group_to_perm'
2811 __table_args__ = (
2824 __table_args__ = (
2812 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2825 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2813 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2814 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2827 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2815 )
2828 )
2816
2829
2817 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2830 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2818 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2831 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2819 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2832 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2820 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2833 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2821
2834
2822 user = relationship('User')
2835 user = relationship('User')
2823 group = relationship('RepoGroup')
2836 group = relationship('RepoGroup')
2824 permission = relationship('Permission')
2837 permission = relationship('Permission')
2825
2838
2826 @classmethod
2839 @classmethod
2827 def create(cls, user, repository_group, permission):
2840 def create(cls, user, repository_group, permission):
2828 n = cls()
2841 n = cls()
2829 n.user = user
2842 n.user = user
2830 n.group = repository_group
2843 n.group = repository_group
2831 n.permission = permission
2844 n.permission = permission
2832 Session().add(n)
2845 Session().add(n)
2833 return n
2846 return n
2834
2847
2835
2848
2836 class UserGroupRepoGroupToPerm(Base, BaseModel):
2849 class UserGroupRepoGroupToPerm(Base, BaseModel):
2837 __tablename__ = 'users_group_repo_group_to_perm'
2850 __tablename__ = 'users_group_repo_group_to_perm'
2838 __table_args__ = (
2851 __table_args__ = (
2839 UniqueConstraint('users_group_id', 'group_id'),
2852 UniqueConstraint('users_group_id', 'group_id'),
2840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2853 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2841 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2854 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2842 )
2855 )
2843
2856
2844 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)
2857 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)
2845 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2858 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2846 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2859 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2847 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2860 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2848
2861
2849 users_group = relationship('UserGroup')
2862 users_group = relationship('UserGroup')
2850 permission = relationship('Permission')
2863 permission = relationship('Permission')
2851 group = relationship('RepoGroup')
2864 group = relationship('RepoGroup')
2852
2865
2853 @classmethod
2866 @classmethod
2854 def create(cls, user_group, repository_group, permission):
2867 def create(cls, user_group, repository_group, permission):
2855 n = cls()
2868 n = cls()
2856 n.users_group = user_group
2869 n.users_group = user_group
2857 n.group = repository_group
2870 n.group = repository_group
2858 n.permission = permission
2871 n.permission = permission
2859 Session().add(n)
2872 Session().add(n)
2860 return n
2873 return n
2861
2874
2862 def __unicode__(self):
2875 def __unicode__(self):
2863 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2876 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2864
2877
2865
2878
2866 class Statistics(Base, BaseModel):
2879 class Statistics(Base, BaseModel):
2867 __tablename__ = 'statistics'
2880 __tablename__ = 'statistics'
2868 __table_args__ = (
2881 __table_args__ = (
2869 UniqueConstraint('repository_id'),
2882 UniqueConstraint('repository_id'),
2870 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2871 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2872 )
2885 )
2873 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2886 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2887 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2875 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2888 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2876 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2889 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2877 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2890 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2878 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2891 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2879
2892
2880 repository = relationship('Repository', single_parent=True)
2893 repository = relationship('Repository', single_parent=True)
2881
2894
2882
2895
2883 class UserFollowing(Base, BaseModel):
2896 class UserFollowing(Base, BaseModel):
2884 __tablename__ = 'user_followings'
2897 __tablename__ = 'user_followings'
2885 __table_args__ = (
2898 __table_args__ = (
2886 UniqueConstraint('user_id', 'follows_repository_id'),
2899 UniqueConstraint('user_id', 'follows_repository_id'),
2887 UniqueConstraint('user_id', 'follows_user_id'),
2900 UniqueConstraint('user_id', 'follows_user_id'),
2888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2901 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2889 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2902 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2890 )
2903 )
2891
2904
2892 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2905 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2893 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2906 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2894 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2907 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2895 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2908 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2896 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2909 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2897
2910
2898 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2911 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2899
2912
2900 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2913 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2901 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2914 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2902
2915
2903 @classmethod
2916 @classmethod
2904 def get_repo_followers(cls, repo_id):
2917 def get_repo_followers(cls, repo_id):
2905 return cls.query().filter(cls.follows_repo_id == repo_id)
2918 return cls.query().filter(cls.follows_repo_id == repo_id)
2906
2919
2907
2920
2908 class CacheKey(Base, BaseModel):
2921 class CacheKey(Base, BaseModel):
2909 __tablename__ = 'cache_invalidation'
2922 __tablename__ = 'cache_invalidation'
2910 __table_args__ = (
2923 __table_args__ = (
2911 UniqueConstraint('cache_key'),
2924 UniqueConstraint('cache_key'),
2912 Index('key_idx', 'cache_key'),
2925 Index('key_idx', 'cache_key'),
2913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2926 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2927 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2915 )
2928 )
2916 CACHE_TYPE_ATOM = 'ATOM'
2929 CACHE_TYPE_ATOM = 'ATOM'
2917 CACHE_TYPE_RSS = 'RSS'
2930 CACHE_TYPE_RSS = 'RSS'
2918 CACHE_TYPE_README = 'README'
2931 CACHE_TYPE_README = 'README'
2919
2932
2920 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2933 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2921 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2934 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2922 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2935 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2923 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2936 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2924
2937
2925 def __init__(self, cache_key, cache_args=''):
2938 def __init__(self, cache_key, cache_args=''):
2926 self.cache_key = cache_key
2939 self.cache_key = cache_key
2927 self.cache_args = cache_args
2940 self.cache_args = cache_args
2928 self.cache_active = False
2941 self.cache_active = False
2929
2942
2930 def __unicode__(self):
2943 def __unicode__(self):
2931 return u"<%s('%s:%s[%s]')>" % (
2944 return u"<%s('%s:%s[%s]')>" % (
2932 self.__class__.__name__,
2945 self.__class__.__name__,
2933 self.cache_id, self.cache_key, self.cache_active)
2946 self.cache_id, self.cache_key, self.cache_active)
2934
2947
2935 def _cache_key_partition(self):
2948 def _cache_key_partition(self):
2936 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2949 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2937 return prefix, repo_name, suffix
2950 return prefix, repo_name, suffix
2938
2951
2939 def get_prefix(self):
2952 def get_prefix(self):
2940 """
2953 """
2941 Try to extract prefix from existing cache key. The key could consist
2954 Try to extract prefix from existing cache key. The key could consist
2942 of prefix, repo_name, suffix
2955 of prefix, repo_name, suffix
2943 """
2956 """
2944 # this returns prefix, repo_name, suffix
2957 # this returns prefix, repo_name, suffix
2945 return self._cache_key_partition()[0]
2958 return self._cache_key_partition()[0]
2946
2959
2947 def get_suffix(self):
2960 def get_suffix(self):
2948 """
2961 """
2949 get suffix that might have been used in _get_cache_key to
2962 get suffix that might have been used in _get_cache_key to
2950 generate self.cache_key. Only used for informational purposes
2963 generate self.cache_key. Only used for informational purposes
2951 in repo_edit.mako.
2964 in repo_edit.mako.
2952 """
2965 """
2953 # prefix, repo_name, suffix
2966 # prefix, repo_name, suffix
2954 return self._cache_key_partition()[2]
2967 return self._cache_key_partition()[2]
2955
2968
2956 @classmethod
2969 @classmethod
2957 def delete_all_cache(cls):
2970 def delete_all_cache(cls):
2958 """
2971 """
2959 Delete all cache keys from database.
2972 Delete all cache keys from database.
2960 Should only be run when all instances are down and all entries
2973 Should only be run when all instances are down and all entries
2961 thus stale.
2974 thus stale.
2962 """
2975 """
2963 cls.query().delete()
2976 cls.query().delete()
2964 Session().commit()
2977 Session().commit()
2965
2978
2966 @classmethod
2979 @classmethod
2967 def get_cache_key(cls, repo_name, cache_type):
2980 def get_cache_key(cls, repo_name, cache_type):
2968 """
2981 """
2969
2982
2970 Generate a cache key for this process of RhodeCode instance.
2983 Generate a cache key for this process of RhodeCode instance.
2971 Prefix most likely will be process id or maybe explicitly set
2984 Prefix most likely will be process id or maybe explicitly set
2972 instance_id from .ini file.
2985 instance_id from .ini file.
2973 """
2986 """
2974 import rhodecode
2987 import rhodecode
2975 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2988 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2976
2989
2977 repo_as_unicode = safe_unicode(repo_name)
2990 repo_as_unicode = safe_unicode(repo_name)
2978 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2991 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2979 if cache_type else repo_as_unicode
2992 if cache_type else repo_as_unicode
2980
2993
2981 return u'{}{}'.format(prefix, key)
2994 return u'{}{}'.format(prefix, key)
2982
2995
2983 @classmethod
2996 @classmethod
2984 def set_invalidate(cls, repo_name, delete=False):
2997 def set_invalidate(cls, repo_name, delete=False):
2985 """
2998 """
2986 Mark all caches of a repo as invalid in the database.
2999 Mark all caches of a repo as invalid in the database.
2987 """
3000 """
2988
3001
2989 try:
3002 try:
2990 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3003 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2991 if delete:
3004 if delete:
2992 log.debug('cache objects deleted for repo %s',
3005 log.debug('cache objects deleted for repo %s',
2993 safe_str(repo_name))
3006 safe_str(repo_name))
2994 qry.delete()
3007 qry.delete()
2995 else:
3008 else:
2996 log.debug('cache objects marked as invalid for repo %s',
3009 log.debug('cache objects marked as invalid for repo %s',
2997 safe_str(repo_name))
3010 safe_str(repo_name))
2998 qry.update({"cache_active": False})
3011 qry.update({"cache_active": False})
2999
3012
3000 Session().commit()
3013 Session().commit()
3001 except Exception:
3014 except Exception:
3002 log.exception(
3015 log.exception(
3003 'Cache key invalidation failed for repository %s',
3016 'Cache key invalidation failed for repository %s',
3004 safe_str(repo_name))
3017 safe_str(repo_name))
3005 Session().rollback()
3018 Session().rollback()
3006
3019
3007 @classmethod
3020 @classmethod
3008 def get_active_cache(cls, cache_key):
3021 def get_active_cache(cls, cache_key):
3009 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3022 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3010 if inv_obj:
3023 if inv_obj:
3011 return inv_obj
3024 return inv_obj
3012 return None
3025 return None
3013
3026
3014 @classmethod
3027 @classmethod
3015 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3028 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3016 thread_scoped=False):
3029 thread_scoped=False):
3017 """
3030 """
3018 @cache_region('long_term')
3031 @cache_region('long_term')
3019 def _heavy_calculation(cache_key):
3032 def _heavy_calculation(cache_key):
3020 return 'result'
3033 return 'result'
3021
3034
3022 cache_context = CacheKey.repo_context_cache(
3035 cache_context = CacheKey.repo_context_cache(
3023 _heavy_calculation, repo_name, cache_type)
3036 _heavy_calculation, repo_name, cache_type)
3024
3037
3025 with cache_context as context:
3038 with cache_context as context:
3026 context.invalidate()
3039 context.invalidate()
3027 computed = context.compute()
3040 computed = context.compute()
3028
3041
3029 assert computed == 'result'
3042 assert computed == 'result'
3030 """
3043 """
3031 from rhodecode.lib import caches
3044 from rhodecode.lib import caches
3032 return caches.InvalidationContext(
3045 return caches.InvalidationContext(
3033 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3046 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3034
3047
3035
3048
3036 class ChangesetComment(Base, BaseModel):
3049 class ChangesetComment(Base, BaseModel):
3037 __tablename__ = 'changeset_comments'
3050 __tablename__ = 'changeset_comments'
3038 __table_args__ = (
3051 __table_args__ = (
3039 Index('cc_revision_idx', 'revision'),
3052 Index('cc_revision_idx', 'revision'),
3040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3054 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3042 )
3055 )
3043
3056
3044 COMMENT_OUTDATED = u'comment_outdated'
3057 COMMENT_OUTDATED = u'comment_outdated'
3045 COMMENT_TYPE_NOTE = u'note'
3058 COMMENT_TYPE_NOTE = u'note'
3046 COMMENT_TYPE_TODO = u'todo'
3059 COMMENT_TYPE_TODO = u'todo'
3047 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3060 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3048
3061
3049 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3062 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3050 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3063 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3051 revision = Column('revision', String(40), nullable=True)
3064 revision = Column('revision', String(40), nullable=True)
3052 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3065 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3053 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3066 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3054 line_no = Column('line_no', Unicode(10), nullable=True)
3067 line_no = Column('line_no', Unicode(10), nullable=True)
3055 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3068 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3056 f_path = Column('f_path', Unicode(1000), nullable=True)
3069 f_path = Column('f_path', Unicode(1000), nullable=True)
3057 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3070 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3058 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3071 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3059 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3072 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3060 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3073 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3061 renderer = Column('renderer', Unicode(64), nullable=True)
3074 renderer = Column('renderer', Unicode(64), nullable=True)
3062 display_state = Column('display_state', Unicode(128), nullable=True)
3075 display_state = Column('display_state', Unicode(128), nullable=True)
3063
3076
3064 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3077 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3065 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3078 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3066 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3079 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3067 author = relationship('User', lazy='joined')
3080 author = relationship('User', lazy='joined')
3068 repo = relationship('Repository')
3081 repo = relationship('Repository')
3069 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3082 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3070 pull_request = relationship('PullRequest', lazy='joined')
3083 pull_request = relationship('PullRequest', lazy='joined')
3071 pull_request_version = relationship('PullRequestVersion')
3084 pull_request_version = relationship('PullRequestVersion')
3072
3085
3073 @classmethod
3086 @classmethod
3074 def get_users(cls, revision=None, pull_request_id=None):
3087 def get_users(cls, revision=None, pull_request_id=None):
3075 """
3088 """
3076 Returns user associated with this ChangesetComment. ie those
3089 Returns user associated with this ChangesetComment. ie those
3077 who actually commented
3090 who actually commented
3078
3091
3079 :param cls:
3092 :param cls:
3080 :param revision:
3093 :param revision:
3081 """
3094 """
3082 q = Session().query(User)\
3095 q = Session().query(User)\
3083 .join(ChangesetComment.author)
3096 .join(ChangesetComment.author)
3084 if revision:
3097 if revision:
3085 q = q.filter(cls.revision == revision)
3098 q = q.filter(cls.revision == revision)
3086 elif pull_request_id:
3099 elif pull_request_id:
3087 q = q.filter(cls.pull_request_id == pull_request_id)
3100 q = q.filter(cls.pull_request_id == pull_request_id)
3088 return q.all()
3101 return q.all()
3089
3102
3090 @classmethod
3103 @classmethod
3091 def get_index_from_version(cls, pr_version, versions):
3104 def get_index_from_version(cls, pr_version, versions):
3092 num_versions = [x.pull_request_version_id for x in versions]
3105 num_versions = [x.pull_request_version_id for x in versions]
3093 try:
3106 try:
3094 return num_versions.index(pr_version) +1
3107 return num_versions.index(pr_version) +1
3095 except (IndexError, ValueError):
3108 except (IndexError, ValueError):
3096 return
3109 return
3097
3110
3098 @property
3111 @property
3099 def outdated(self):
3112 def outdated(self):
3100 return self.display_state == self.COMMENT_OUTDATED
3113 return self.display_state == self.COMMENT_OUTDATED
3101
3114
3102 def outdated_at_version(self, version):
3115 def outdated_at_version(self, version):
3103 """
3116 """
3104 Checks if comment is outdated for given pull request version
3117 Checks if comment is outdated for given pull request version
3105 """
3118 """
3106 return self.outdated and self.pull_request_version_id != version
3119 return self.outdated and self.pull_request_version_id != version
3107
3120
3108 def older_than_version(self, version):
3121 def older_than_version(self, version):
3109 """
3122 """
3110 Checks if comment is made from previous version than given
3123 Checks if comment is made from previous version than given
3111 """
3124 """
3112 if version is None:
3125 if version is None:
3113 return self.pull_request_version_id is not None
3126 return self.pull_request_version_id is not None
3114
3127
3115 return self.pull_request_version_id < version
3128 return self.pull_request_version_id < version
3116
3129
3117 @property
3130 @property
3118 def resolved(self):
3131 def resolved(self):
3119 return self.resolved_by[0] if self.resolved_by else None
3132 return self.resolved_by[0] if self.resolved_by else None
3120
3133
3121 @property
3134 @property
3122 def is_todo(self):
3135 def is_todo(self):
3123 return self.comment_type == self.COMMENT_TYPE_TODO
3136 return self.comment_type == self.COMMENT_TYPE_TODO
3124
3137
3125 @property
3138 @property
3126 def is_inline(self):
3139 def is_inline(self):
3127 return self.line_no and self.f_path
3140 return self.line_no and self.f_path
3128
3141
3129 def get_index_version(self, versions):
3142 def get_index_version(self, versions):
3130 return self.get_index_from_version(
3143 return self.get_index_from_version(
3131 self.pull_request_version_id, versions)
3144 self.pull_request_version_id, versions)
3132
3145
3133 def __repr__(self):
3146 def __repr__(self):
3134 if self.comment_id:
3147 if self.comment_id:
3135 return '<DB:Comment #%s>' % self.comment_id
3148 return '<DB:Comment #%s>' % self.comment_id
3136 else:
3149 else:
3137 return '<DB:Comment at %#x>' % id(self)
3150 return '<DB:Comment at %#x>' % id(self)
3138
3151
3139 def get_api_data(self):
3152 def get_api_data(self):
3140 comment = self
3153 comment = self
3141 data = {
3154 data = {
3142 'comment_id': comment.comment_id,
3155 'comment_id': comment.comment_id,
3143 'comment_type': comment.comment_type,
3156 'comment_type': comment.comment_type,
3144 'comment_text': comment.text,
3157 'comment_text': comment.text,
3145 'comment_status': comment.status_change,
3158 'comment_status': comment.status_change,
3146 'comment_f_path': comment.f_path,
3159 'comment_f_path': comment.f_path,
3147 'comment_lineno': comment.line_no,
3160 'comment_lineno': comment.line_no,
3148 'comment_author': comment.author,
3161 'comment_author': comment.author,
3149 'comment_created_on': comment.created_on
3162 'comment_created_on': comment.created_on
3150 }
3163 }
3151 return data
3164 return data
3152
3165
3153 def __json__(self):
3166 def __json__(self):
3154 data = dict()
3167 data = dict()
3155 data.update(self.get_api_data())
3168 data.update(self.get_api_data())
3156 return data
3169 return data
3157
3170
3158
3171
3159 class ChangesetStatus(Base, BaseModel):
3172 class ChangesetStatus(Base, BaseModel):
3160 __tablename__ = 'changeset_statuses'
3173 __tablename__ = 'changeset_statuses'
3161 __table_args__ = (
3174 __table_args__ = (
3162 Index('cs_revision_idx', 'revision'),
3175 Index('cs_revision_idx', 'revision'),
3163 Index('cs_version_idx', 'version'),
3176 Index('cs_version_idx', 'version'),
3164 UniqueConstraint('repo_id', 'revision', 'version'),
3177 UniqueConstraint('repo_id', 'revision', 'version'),
3165 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3178 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3166 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3179 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3167 )
3180 )
3168 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3181 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3169 STATUS_APPROVED = 'approved'
3182 STATUS_APPROVED = 'approved'
3170 STATUS_REJECTED = 'rejected'
3183 STATUS_REJECTED = 'rejected'
3171 STATUS_UNDER_REVIEW = 'under_review'
3184 STATUS_UNDER_REVIEW = 'under_review'
3172
3185
3173 STATUSES = [
3186 STATUSES = [
3174 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3187 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3175 (STATUS_APPROVED, _("Approved")),
3188 (STATUS_APPROVED, _("Approved")),
3176 (STATUS_REJECTED, _("Rejected")),
3189 (STATUS_REJECTED, _("Rejected")),
3177 (STATUS_UNDER_REVIEW, _("Under Review")),
3190 (STATUS_UNDER_REVIEW, _("Under Review")),
3178 ]
3191 ]
3179
3192
3180 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3193 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3181 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3194 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3182 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3195 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3183 revision = Column('revision', String(40), nullable=False)
3196 revision = Column('revision', String(40), nullable=False)
3184 status = Column('status', String(128), nullable=False, default=DEFAULT)
3197 status = Column('status', String(128), nullable=False, default=DEFAULT)
3185 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3198 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3186 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3199 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3187 version = Column('version', Integer(), nullable=False, default=0)
3200 version = Column('version', Integer(), nullable=False, default=0)
3188 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3201 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3189
3202
3190 author = relationship('User', lazy='joined')
3203 author = relationship('User', lazy='joined')
3191 repo = relationship('Repository')
3204 repo = relationship('Repository')
3192 comment = relationship('ChangesetComment', lazy='joined')
3205 comment = relationship('ChangesetComment', lazy='joined')
3193 pull_request = relationship('PullRequest', lazy='joined')
3206 pull_request = relationship('PullRequest', lazy='joined')
3194
3207
3195 def __unicode__(self):
3208 def __unicode__(self):
3196 return u"<%s('%s[v%s]:%s')>" % (
3209 return u"<%s('%s[v%s]:%s')>" % (
3197 self.__class__.__name__,
3210 self.__class__.__name__,
3198 self.status, self.version, self.author
3211 self.status, self.version, self.author
3199 )
3212 )
3200
3213
3201 @classmethod
3214 @classmethod
3202 def get_status_lbl(cls, value):
3215 def get_status_lbl(cls, value):
3203 return dict(cls.STATUSES).get(value)
3216 return dict(cls.STATUSES).get(value)
3204
3217
3205 @property
3218 @property
3206 def status_lbl(self):
3219 def status_lbl(self):
3207 return ChangesetStatus.get_status_lbl(self.status)
3220 return ChangesetStatus.get_status_lbl(self.status)
3208
3221
3209 def get_api_data(self):
3222 def get_api_data(self):
3210 status = self
3223 status = self
3211 data = {
3224 data = {
3212 'status_id': status.changeset_status_id,
3225 'status_id': status.changeset_status_id,
3213 'status': status.status,
3226 'status': status.status,
3214 }
3227 }
3215 return data
3228 return data
3216
3229
3217 def __json__(self):
3230 def __json__(self):
3218 data = dict()
3231 data = dict()
3219 data.update(self.get_api_data())
3232 data.update(self.get_api_data())
3220 return data
3233 return data
3221
3234
3222
3235
3223 class _PullRequestBase(BaseModel):
3236 class _PullRequestBase(BaseModel):
3224 """
3237 """
3225 Common attributes of pull request and version entries.
3238 Common attributes of pull request and version entries.
3226 """
3239 """
3227
3240
3228 # .status values
3241 # .status values
3229 STATUS_NEW = u'new'
3242 STATUS_NEW = u'new'
3230 STATUS_OPEN = u'open'
3243 STATUS_OPEN = u'open'
3231 STATUS_CLOSED = u'closed'
3244 STATUS_CLOSED = u'closed'
3232
3245
3233 title = Column('title', Unicode(255), nullable=True)
3246 title = Column('title', Unicode(255), nullable=True)
3234 description = Column(
3247 description = Column(
3235 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3248 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3236 nullable=True)
3249 nullable=True)
3237 # new/open/closed status of pull request (not approve/reject/etc)
3250 # new/open/closed status of pull request (not approve/reject/etc)
3238 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3251 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3239 created_on = Column(
3252 created_on = Column(
3240 'created_on', DateTime(timezone=False), nullable=False,
3253 'created_on', DateTime(timezone=False), nullable=False,
3241 default=datetime.datetime.now)
3254 default=datetime.datetime.now)
3242 updated_on = Column(
3255 updated_on = Column(
3243 'updated_on', DateTime(timezone=False), nullable=False,
3256 'updated_on', DateTime(timezone=False), nullable=False,
3244 default=datetime.datetime.now)
3257 default=datetime.datetime.now)
3245
3258
3246 @declared_attr
3259 @declared_attr
3247 def user_id(cls):
3260 def user_id(cls):
3248 return Column(
3261 return Column(
3249 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3262 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3250 unique=None)
3263 unique=None)
3251
3264
3252 # 500 revisions max
3265 # 500 revisions max
3253 _revisions = Column(
3266 _revisions = Column(
3254 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3267 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3255
3268
3256 @declared_attr
3269 @declared_attr
3257 def source_repo_id(cls):
3270 def source_repo_id(cls):
3258 # TODO: dan: rename column to source_repo_id
3271 # TODO: dan: rename column to source_repo_id
3259 return Column(
3272 return Column(
3260 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3273 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3261 nullable=False)
3274 nullable=False)
3262
3275
3263 source_ref = Column('org_ref', Unicode(255), nullable=False)
3276 source_ref = Column('org_ref', Unicode(255), nullable=False)
3264
3277
3265 @declared_attr
3278 @declared_attr
3266 def target_repo_id(cls):
3279 def target_repo_id(cls):
3267 # TODO: dan: rename column to target_repo_id
3280 # TODO: dan: rename column to target_repo_id
3268 return Column(
3281 return Column(
3269 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3282 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3270 nullable=False)
3283 nullable=False)
3271
3284
3272 target_ref = Column('other_ref', Unicode(255), nullable=False)
3285 target_ref = Column('other_ref', Unicode(255), nullable=False)
3273 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3286 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3274
3287
3275 # TODO: dan: rename column to last_merge_source_rev
3288 # TODO: dan: rename column to last_merge_source_rev
3276 _last_merge_source_rev = Column(
3289 _last_merge_source_rev = Column(
3277 'last_merge_org_rev', String(40), nullable=True)
3290 'last_merge_org_rev', String(40), nullable=True)
3278 # TODO: dan: rename column to last_merge_target_rev
3291 # TODO: dan: rename column to last_merge_target_rev
3279 _last_merge_target_rev = Column(
3292 _last_merge_target_rev = Column(
3280 'last_merge_other_rev', String(40), nullable=True)
3293 'last_merge_other_rev', String(40), nullable=True)
3281 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3294 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3282 merge_rev = Column('merge_rev', String(40), nullable=True)
3295 merge_rev = Column('merge_rev', String(40), nullable=True)
3283
3296
3284 reviewer_data = Column(
3297 reviewer_data = Column(
3285 'reviewer_data_json', MutationObj.as_mutable(
3298 'reviewer_data_json', MutationObj.as_mutable(
3286 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3299 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3287
3300
3288 @property
3301 @property
3289 def reviewer_data_json(self):
3302 def reviewer_data_json(self):
3290 return json.dumps(self.reviewer_data)
3303 return json.dumps(self.reviewer_data)
3291
3304
3292 @hybrid_property
3305 @hybrid_property
3293 def revisions(self):
3306 def revisions(self):
3294 return self._revisions.split(':') if self._revisions else []
3307 return self._revisions.split(':') if self._revisions else []
3295
3308
3296 @revisions.setter
3309 @revisions.setter
3297 def revisions(self, val):
3310 def revisions(self, val):
3298 self._revisions = ':'.join(val)
3311 self._revisions = ':'.join(val)
3299
3312
3300 @declared_attr
3313 @declared_attr
3301 def author(cls):
3314 def author(cls):
3302 return relationship('User', lazy='joined')
3315 return relationship('User', lazy='joined')
3303
3316
3304 @declared_attr
3317 @declared_attr
3305 def source_repo(cls):
3318 def source_repo(cls):
3306 return relationship(
3319 return relationship(
3307 'Repository',
3320 'Repository',
3308 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3321 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3309
3322
3310 @property
3323 @property
3311 def source_ref_parts(self):
3324 def source_ref_parts(self):
3312 return self.unicode_to_reference(self.source_ref)
3325 return self.unicode_to_reference(self.source_ref)
3313
3326
3314 @declared_attr
3327 @declared_attr
3315 def target_repo(cls):
3328 def target_repo(cls):
3316 return relationship(
3329 return relationship(
3317 'Repository',
3330 'Repository',
3318 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3331 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3319
3332
3320 @property
3333 @property
3321 def target_ref_parts(self):
3334 def target_ref_parts(self):
3322 return self.unicode_to_reference(self.target_ref)
3335 return self.unicode_to_reference(self.target_ref)
3323
3336
3324 @property
3337 @property
3325 def shadow_merge_ref(self):
3338 def shadow_merge_ref(self):
3326 return self.unicode_to_reference(self._shadow_merge_ref)
3339 return self.unicode_to_reference(self._shadow_merge_ref)
3327
3340
3328 @shadow_merge_ref.setter
3341 @shadow_merge_ref.setter
3329 def shadow_merge_ref(self, ref):
3342 def shadow_merge_ref(self, ref):
3330 self._shadow_merge_ref = self.reference_to_unicode(ref)
3343 self._shadow_merge_ref = self.reference_to_unicode(ref)
3331
3344
3332 def unicode_to_reference(self, raw):
3345 def unicode_to_reference(self, raw):
3333 """
3346 """
3334 Convert a unicode (or string) to a reference object.
3347 Convert a unicode (or string) to a reference object.
3335 If unicode evaluates to False it returns None.
3348 If unicode evaluates to False it returns None.
3336 """
3349 """
3337 if raw:
3350 if raw:
3338 refs = raw.split(':')
3351 refs = raw.split(':')
3339 return Reference(*refs)
3352 return Reference(*refs)
3340 else:
3353 else:
3341 return None
3354 return None
3342
3355
3343 def reference_to_unicode(self, ref):
3356 def reference_to_unicode(self, ref):
3344 """
3357 """
3345 Convert a reference object to unicode.
3358 Convert a reference object to unicode.
3346 If reference is None it returns None.
3359 If reference is None it returns None.
3347 """
3360 """
3348 if ref:
3361 if ref:
3349 return u':'.join(ref)
3362 return u':'.join(ref)
3350 else:
3363 else:
3351 return None
3364 return None
3352
3365
3353 def get_api_data(self, with_merge_state=True):
3366 def get_api_data(self, with_merge_state=True):
3354 from rhodecode.model.pull_request import PullRequestModel
3367 from rhodecode.model.pull_request import PullRequestModel
3355
3368
3356 pull_request = self
3369 pull_request = self
3357 if with_merge_state:
3370 if with_merge_state:
3358 merge_status = PullRequestModel().merge_status(pull_request)
3371 merge_status = PullRequestModel().merge_status(pull_request)
3359 merge_state = {
3372 merge_state = {
3360 'status': merge_status[0],
3373 'status': merge_status[0],
3361 'message': safe_unicode(merge_status[1]),
3374 'message': safe_unicode(merge_status[1]),
3362 }
3375 }
3363 else:
3376 else:
3364 merge_state = {'status': 'not_available',
3377 merge_state = {'status': 'not_available',
3365 'message': 'not_available'}
3378 'message': 'not_available'}
3366
3379
3367 merge_data = {
3380 merge_data = {
3368 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3381 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3369 'reference': (
3382 'reference': (
3370 pull_request.shadow_merge_ref._asdict()
3383 pull_request.shadow_merge_ref._asdict()
3371 if pull_request.shadow_merge_ref else None),
3384 if pull_request.shadow_merge_ref else None),
3372 }
3385 }
3373
3386
3374 data = {
3387 data = {
3375 'pull_request_id': pull_request.pull_request_id,
3388 'pull_request_id': pull_request.pull_request_id,
3376 'url': PullRequestModel().get_url(pull_request),
3389 'url': PullRequestModel().get_url(pull_request),
3377 'title': pull_request.title,
3390 'title': pull_request.title,
3378 'description': pull_request.description,
3391 'description': pull_request.description,
3379 'status': pull_request.status,
3392 'status': pull_request.status,
3380 'created_on': pull_request.created_on,
3393 'created_on': pull_request.created_on,
3381 'updated_on': pull_request.updated_on,
3394 'updated_on': pull_request.updated_on,
3382 'commit_ids': pull_request.revisions,
3395 'commit_ids': pull_request.revisions,
3383 'review_status': pull_request.calculated_review_status(),
3396 'review_status': pull_request.calculated_review_status(),
3384 'mergeable': merge_state,
3397 'mergeable': merge_state,
3385 'source': {
3398 'source': {
3386 'clone_url': pull_request.source_repo.clone_url(),
3399 'clone_url': pull_request.source_repo.clone_url(),
3387 'repository': pull_request.source_repo.repo_name,
3400 'repository': pull_request.source_repo.repo_name,
3388 'reference': {
3401 'reference': {
3389 'name': pull_request.source_ref_parts.name,
3402 'name': pull_request.source_ref_parts.name,
3390 'type': pull_request.source_ref_parts.type,
3403 'type': pull_request.source_ref_parts.type,
3391 'commit_id': pull_request.source_ref_parts.commit_id,
3404 'commit_id': pull_request.source_ref_parts.commit_id,
3392 },
3405 },
3393 },
3406 },
3394 'target': {
3407 'target': {
3395 'clone_url': pull_request.target_repo.clone_url(),
3408 'clone_url': pull_request.target_repo.clone_url(),
3396 'repository': pull_request.target_repo.repo_name,
3409 'repository': pull_request.target_repo.repo_name,
3397 'reference': {
3410 'reference': {
3398 'name': pull_request.target_ref_parts.name,
3411 'name': pull_request.target_ref_parts.name,
3399 'type': pull_request.target_ref_parts.type,
3412 'type': pull_request.target_ref_parts.type,
3400 'commit_id': pull_request.target_ref_parts.commit_id,
3413 'commit_id': pull_request.target_ref_parts.commit_id,
3401 },
3414 },
3402 },
3415 },
3403 'merge': merge_data,
3416 'merge': merge_data,
3404 'author': pull_request.author.get_api_data(include_secrets=False,
3417 'author': pull_request.author.get_api_data(include_secrets=False,
3405 details='basic'),
3418 details='basic'),
3406 'reviewers': [
3419 'reviewers': [
3407 {
3420 {
3408 'user': reviewer.get_api_data(include_secrets=False,
3421 'user': reviewer.get_api_data(include_secrets=False,
3409 details='basic'),
3422 details='basic'),
3410 'reasons': reasons,
3423 'reasons': reasons,
3411 'review_status': st[0][1].status if st else 'not_reviewed',
3424 'review_status': st[0][1].status if st else 'not_reviewed',
3412 }
3425 }
3413 for reviewer, reasons, mandatory, st in
3426 for reviewer, reasons, mandatory, st in
3414 pull_request.reviewers_statuses()
3427 pull_request.reviewers_statuses()
3415 ]
3428 ]
3416 }
3429 }
3417
3430
3418 return data
3431 return data
3419
3432
3420
3433
3421 class PullRequest(Base, _PullRequestBase):
3434 class PullRequest(Base, _PullRequestBase):
3422 __tablename__ = 'pull_requests'
3435 __tablename__ = 'pull_requests'
3423 __table_args__ = (
3436 __table_args__ = (
3424 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3425 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3438 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3426 )
3439 )
3427
3440
3428 pull_request_id = Column(
3441 pull_request_id = Column(
3429 'pull_request_id', Integer(), nullable=False, primary_key=True)
3442 'pull_request_id', Integer(), nullable=False, primary_key=True)
3430
3443
3431 def __repr__(self):
3444 def __repr__(self):
3432 if self.pull_request_id:
3445 if self.pull_request_id:
3433 return '<DB:PullRequest #%s>' % self.pull_request_id
3446 return '<DB:PullRequest #%s>' % self.pull_request_id
3434 else:
3447 else:
3435 return '<DB:PullRequest at %#x>' % id(self)
3448 return '<DB:PullRequest at %#x>' % id(self)
3436
3449
3437 reviewers = relationship('PullRequestReviewers',
3450 reviewers = relationship('PullRequestReviewers',
3438 cascade="all, delete, delete-orphan")
3451 cascade="all, delete, delete-orphan")
3439 statuses = relationship('ChangesetStatus',
3452 statuses = relationship('ChangesetStatus',
3440 cascade="all, delete, delete-orphan")
3453 cascade="all, delete, delete-orphan")
3441 comments = relationship('ChangesetComment',
3454 comments = relationship('ChangesetComment',
3442 cascade="all, delete, delete-orphan")
3455 cascade="all, delete, delete-orphan")
3443 versions = relationship('PullRequestVersion',
3456 versions = relationship('PullRequestVersion',
3444 cascade="all, delete, delete-orphan",
3457 cascade="all, delete, delete-orphan",
3445 lazy='dynamic')
3458 lazy='dynamic')
3446
3459
3447 @classmethod
3460 @classmethod
3448 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3461 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3449 internal_methods=None):
3462 internal_methods=None):
3450
3463
3451 class PullRequestDisplay(object):
3464 class PullRequestDisplay(object):
3452 """
3465 """
3453 Special object wrapper for showing PullRequest data via Versions
3466 Special object wrapper for showing PullRequest data via Versions
3454 It mimics PR object as close as possible. This is read only object
3467 It mimics PR object as close as possible. This is read only object
3455 just for display
3468 just for display
3456 """
3469 """
3457
3470
3458 def __init__(self, attrs, internal=None):
3471 def __init__(self, attrs, internal=None):
3459 self.attrs = attrs
3472 self.attrs = attrs
3460 # internal have priority over the given ones via attrs
3473 # internal have priority over the given ones via attrs
3461 self.internal = internal or ['versions']
3474 self.internal = internal or ['versions']
3462
3475
3463 def __getattr__(self, item):
3476 def __getattr__(self, item):
3464 if item in self.internal:
3477 if item in self.internal:
3465 return getattr(self, item)
3478 return getattr(self, item)
3466 try:
3479 try:
3467 return self.attrs[item]
3480 return self.attrs[item]
3468 except KeyError:
3481 except KeyError:
3469 raise AttributeError(
3482 raise AttributeError(
3470 '%s object has no attribute %s' % (self, item))
3483 '%s object has no attribute %s' % (self, item))
3471
3484
3472 def __repr__(self):
3485 def __repr__(self):
3473 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3486 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3474
3487
3475 def versions(self):
3488 def versions(self):
3476 return pull_request_obj.versions.order_by(
3489 return pull_request_obj.versions.order_by(
3477 PullRequestVersion.pull_request_version_id).all()
3490 PullRequestVersion.pull_request_version_id).all()
3478
3491
3479 def is_closed(self):
3492 def is_closed(self):
3480 return pull_request_obj.is_closed()
3493 return pull_request_obj.is_closed()
3481
3494
3482 @property
3495 @property
3483 def pull_request_version_id(self):
3496 def pull_request_version_id(self):
3484 return getattr(pull_request_obj, 'pull_request_version_id', None)
3497 return getattr(pull_request_obj, 'pull_request_version_id', None)
3485
3498
3486 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3499 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3487
3500
3488 attrs.author = StrictAttributeDict(
3501 attrs.author = StrictAttributeDict(
3489 pull_request_obj.author.get_api_data())
3502 pull_request_obj.author.get_api_data())
3490 if pull_request_obj.target_repo:
3503 if pull_request_obj.target_repo:
3491 attrs.target_repo = StrictAttributeDict(
3504 attrs.target_repo = StrictAttributeDict(
3492 pull_request_obj.target_repo.get_api_data())
3505 pull_request_obj.target_repo.get_api_data())
3493 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3506 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3494
3507
3495 if pull_request_obj.source_repo:
3508 if pull_request_obj.source_repo:
3496 attrs.source_repo = StrictAttributeDict(
3509 attrs.source_repo = StrictAttributeDict(
3497 pull_request_obj.source_repo.get_api_data())
3510 pull_request_obj.source_repo.get_api_data())
3498 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3511 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3499
3512
3500 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3513 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3501 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3514 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3502 attrs.revisions = pull_request_obj.revisions
3515 attrs.revisions = pull_request_obj.revisions
3503
3516
3504 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3517 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3505 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3518 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3506 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3519 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3507
3520
3508 return PullRequestDisplay(attrs, internal=internal_methods)
3521 return PullRequestDisplay(attrs, internal=internal_methods)
3509
3522
3510 def is_closed(self):
3523 def is_closed(self):
3511 return self.status == self.STATUS_CLOSED
3524 return self.status == self.STATUS_CLOSED
3512
3525
3513 def __json__(self):
3526 def __json__(self):
3514 return {
3527 return {
3515 'revisions': self.revisions,
3528 'revisions': self.revisions,
3516 }
3529 }
3517
3530
3518 def calculated_review_status(self):
3531 def calculated_review_status(self):
3519 from rhodecode.model.changeset_status import ChangesetStatusModel
3532 from rhodecode.model.changeset_status import ChangesetStatusModel
3520 return ChangesetStatusModel().calculated_review_status(self)
3533 return ChangesetStatusModel().calculated_review_status(self)
3521
3534
3522 def reviewers_statuses(self):
3535 def reviewers_statuses(self):
3523 from rhodecode.model.changeset_status import ChangesetStatusModel
3536 from rhodecode.model.changeset_status import ChangesetStatusModel
3524 return ChangesetStatusModel().reviewers_statuses(self)
3537 return ChangesetStatusModel().reviewers_statuses(self)
3525
3538
3526 @property
3539 @property
3527 def workspace_id(self):
3540 def workspace_id(self):
3528 from rhodecode.model.pull_request import PullRequestModel
3541 from rhodecode.model.pull_request import PullRequestModel
3529 return PullRequestModel()._workspace_id(self)
3542 return PullRequestModel()._workspace_id(self)
3530
3543
3531 def get_shadow_repo(self):
3544 def get_shadow_repo(self):
3532 workspace_id = self.workspace_id
3545 workspace_id = self.workspace_id
3533 vcs_obj = self.target_repo.scm_instance()
3546 vcs_obj = self.target_repo.scm_instance()
3534 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3547 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3535 workspace_id)
3548 workspace_id)
3536 return vcs_obj._get_shadow_instance(shadow_repository_path)
3549 return vcs_obj._get_shadow_instance(shadow_repository_path)
3537
3550
3538
3551
3539 class PullRequestVersion(Base, _PullRequestBase):
3552 class PullRequestVersion(Base, _PullRequestBase):
3540 __tablename__ = 'pull_request_versions'
3553 __tablename__ = 'pull_request_versions'
3541 __table_args__ = (
3554 __table_args__ = (
3542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3555 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3543 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3556 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3544 )
3557 )
3545
3558
3546 pull_request_version_id = Column(
3559 pull_request_version_id = Column(
3547 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3560 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3548 pull_request_id = Column(
3561 pull_request_id = Column(
3549 'pull_request_id', Integer(),
3562 'pull_request_id', Integer(),
3550 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3563 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3551 pull_request = relationship('PullRequest')
3564 pull_request = relationship('PullRequest')
3552
3565
3553 def __repr__(self):
3566 def __repr__(self):
3554 if self.pull_request_version_id:
3567 if self.pull_request_version_id:
3555 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3568 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3556 else:
3569 else:
3557 return '<DB:PullRequestVersion at %#x>' % id(self)
3570 return '<DB:PullRequestVersion at %#x>' % id(self)
3558
3571
3559 @property
3572 @property
3560 def reviewers(self):
3573 def reviewers(self):
3561 return self.pull_request.reviewers
3574 return self.pull_request.reviewers
3562
3575
3563 @property
3576 @property
3564 def versions(self):
3577 def versions(self):
3565 return self.pull_request.versions
3578 return self.pull_request.versions
3566
3579
3567 def is_closed(self):
3580 def is_closed(self):
3568 # calculate from original
3581 # calculate from original
3569 return self.pull_request.status == self.STATUS_CLOSED
3582 return self.pull_request.status == self.STATUS_CLOSED
3570
3583
3571 def calculated_review_status(self):
3584 def calculated_review_status(self):
3572 return self.pull_request.calculated_review_status()
3585 return self.pull_request.calculated_review_status()
3573
3586
3574 def reviewers_statuses(self):
3587 def reviewers_statuses(self):
3575 return self.pull_request.reviewers_statuses()
3588 return self.pull_request.reviewers_statuses()
3576
3589
3577
3590
3578 class PullRequestReviewers(Base, BaseModel):
3591 class PullRequestReviewers(Base, BaseModel):
3579 __tablename__ = 'pull_request_reviewers'
3592 __tablename__ = 'pull_request_reviewers'
3580 __table_args__ = (
3593 __table_args__ = (
3581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3595 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3583 )
3596 )
3584
3597
3585 @hybrid_property
3598 @hybrid_property
3586 def reasons(self):
3599 def reasons(self):
3587 if not self._reasons:
3600 if not self._reasons:
3588 return []
3601 return []
3589 return self._reasons
3602 return self._reasons
3590
3603
3591 @reasons.setter
3604 @reasons.setter
3592 def reasons(self, val):
3605 def reasons(self, val):
3593 val = val or []
3606 val = val or []
3594 if any(not isinstance(x, basestring) for x in val):
3607 if any(not isinstance(x, basestring) for x in val):
3595 raise Exception('invalid reasons type, must be list of strings')
3608 raise Exception('invalid reasons type, must be list of strings')
3596 self._reasons = val
3609 self._reasons = val
3597
3610
3598 pull_requests_reviewers_id = Column(
3611 pull_requests_reviewers_id = Column(
3599 'pull_requests_reviewers_id', Integer(), nullable=False,
3612 'pull_requests_reviewers_id', Integer(), nullable=False,
3600 primary_key=True)
3613 primary_key=True)
3601 pull_request_id = Column(
3614 pull_request_id = Column(
3602 "pull_request_id", Integer(),
3615 "pull_request_id", Integer(),
3603 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3616 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3604 user_id = Column(
3617 user_id = Column(
3605 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3606 _reasons = Column(
3619 _reasons = Column(
3607 'reason', MutationList.as_mutable(
3620 'reason', MutationList.as_mutable(
3608 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3621 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3609 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3622 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3610 user = relationship('User')
3623 user = relationship('User')
3611 pull_request = relationship('PullRequest')
3624 pull_request = relationship('PullRequest')
3612
3625
3613
3626
3614 class Notification(Base, BaseModel):
3627 class Notification(Base, BaseModel):
3615 __tablename__ = 'notifications'
3628 __tablename__ = 'notifications'
3616 __table_args__ = (
3629 __table_args__ = (
3617 Index('notification_type_idx', 'type'),
3630 Index('notification_type_idx', 'type'),
3618 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3619 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3632 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3620 )
3633 )
3621
3634
3622 TYPE_CHANGESET_COMMENT = u'cs_comment'
3635 TYPE_CHANGESET_COMMENT = u'cs_comment'
3623 TYPE_MESSAGE = u'message'
3636 TYPE_MESSAGE = u'message'
3624 TYPE_MENTION = u'mention'
3637 TYPE_MENTION = u'mention'
3625 TYPE_REGISTRATION = u'registration'
3638 TYPE_REGISTRATION = u'registration'
3626 TYPE_PULL_REQUEST = u'pull_request'
3639 TYPE_PULL_REQUEST = u'pull_request'
3627 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3640 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3628
3641
3629 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3642 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3630 subject = Column('subject', Unicode(512), nullable=True)
3643 subject = Column('subject', Unicode(512), nullable=True)
3631 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3644 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3632 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3645 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3633 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3646 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3634 type_ = Column('type', Unicode(255))
3647 type_ = Column('type', Unicode(255))
3635
3648
3636 created_by_user = relationship('User')
3649 created_by_user = relationship('User')
3637 notifications_to_users = relationship('UserNotification', lazy='joined',
3650 notifications_to_users = relationship('UserNotification', lazy='joined',
3638 cascade="all, delete, delete-orphan")
3651 cascade="all, delete, delete-orphan")
3639
3652
3640 @property
3653 @property
3641 def recipients(self):
3654 def recipients(self):
3642 return [x.user for x in UserNotification.query()\
3655 return [x.user for x in UserNotification.query()\
3643 .filter(UserNotification.notification == self)\
3656 .filter(UserNotification.notification == self)\
3644 .order_by(UserNotification.user_id.asc()).all()]
3657 .order_by(UserNotification.user_id.asc()).all()]
3645
3658
3646 @classmethod
3659 @classmethod
3647 def create(cls, created_by, subject, body, recipients, type_=None):
3660 def create(cls, created_by, subject, body, recipients, type_=None):
3648 if type_ is None:
3661 if type_ is None:
3649 type_ = Notification.TYPE_MESSAGE
3662 type_ = Notification.TYPE_MESSAGE
3650
3663
3651 notification = cls()
3664 notification = cls()
3652 notification.created_by_user = created_by
3665 notification.created_by_user = created_by
3653 notification.subject = subject
3666 notification.subject = subject
3654 notification.body = body
3667 notification.body = body
3655 notification.type_ = type_
3668 notification.type_ = type_
3656 notification.created_on = datetime.datetime.now()
3669 notification.created_on = datetime.datetime.now()
3657
3670
3658 for u in recipients:
3671 for u in recipients:
3659 assoc = UserNotification()
3672 assoc = UserNotification()
3660 assoc.notification = notification
3673 assoc.notification = notification
3661
3674
3662 # if created_by is inside recipients mark his notification
3675 # if created_by is inside recipients mark his notification
3663 # as read
3676 # as read
3664 if u.user_id == created_by.user_id:
3677 if u.user_id == created_by.user_id:
3665 assoc.read = True
3678 assoc.read = True
3666
3679
3667 u.notifications.append(assoc)
3680 u.notifications.append(assoc)
3668 Session().add(notification)
3681 Session().add(notification)
3669
3682
3670 return notification
3683 return notification
3671
3684
3672 @property
3685 @property
3673 def description(self):
3686 def description(self):
3674 from rhodecode.model.notification import NotificationModel
3687 from rhodecode.model.notification import NotificationModel
3675 return NotificationModel().make_description(self)
3688 return NotificationModel().make_description(self)
3676
3689
3677
3690
3678 class UserNotification(Base, BaseModel):
3691 class UserNotification(Base, BaseModel):
3679 __tablename__ = 'user_to_notification'
3692 __tablename__ = 'user_to_notification'
3680 __table_args__ = (
3693 __table_args__ = (
3681 UniqueConstraint('user_id', 'notification_id'),
3694 UniqueConstraint('user_id', 'notification_id'),
3682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3695 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3683 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3696 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3684 )
3697 )
3685 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3698 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3686 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3699 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3687 read = Column('read', Boolean, default=False)
3700 read = Column('read', Boolean, default=False)
3688 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3701 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3689
3702
3690 user = relationship('User', lazy="joined")
3703 user = relationship('User', lazy="joined")
3691 notification = relationship('Notification', lazy="joined",
3704 notification = relationship('Notification', lazy="joined",
3692 order_by=lambda: Notification.created_on.desc(),)
3705 order_by=lambda: Notification.created_on.desc(),)
3693
3706
3694 def mark_as_read(self):
3707 def mark_as_read(self):
3695 self.read = True
3708 self.read = True
3696 Session().add(self)
3709 Session().add(self)
3697
3710
3698
3711
3699 class Gist(Base, BaseModel):
3712 class Gist(Base, BaseModel):
3700 __tablename__ = 'gists'
3713 __tablename__ = 'gists'
3701 __table_args__ = (
3714 __table_args__ = (
3702 Index('g_gist_access_id_idx', 'gist_access_id'),
3715 Index('g_gist_access_id_idx', 'gist_access_id'),
3703 Index('g_created_on_idx', 'created_on'),
3716 Index('g_created_on_idx', 'created_on'),
3704 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3717 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3705 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3718 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3706 )
3719 )
3707 GIST_PUBLIC = u'public'
3720 GIST_PUBLIC = u'public'
3708 GIST_PRIVATE = u'private'
3721 GIST_PRIVATE = u'private'
3709 DEFAULT_FILENAME = u'gistfile1.txt'
3722 DEFAULT_FILENAME = u'gistfile1.txt'
3710
3723
3711 ACL_LEVEL_PUBLIC = u'acl_public'
3724 ACL_LEVEL_PUBLIC = u'acl_public'
3712 ACL_LEVEL_PRIVATE = u'acl_private'
3725 ACL_LEVEL_PRIVATE = u'acl_private'
3713
3726
3714 gist_id = Column('gist_id', Integer(), primary_key=True)
3727 gist_id = Column('gist_id', Integer(), primary_key=True)
3715 gist_access_id = Column('gist_access_id', Unicode(250))
3728 gist_access_id = Column('gist_access_id', Unicode(250))
3716 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3729 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3717 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3730 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3718 gist_expires = Column('gist_expires', Float(53), nullable=False)
3731 gist_expires = Column('gist_expires', Float(53), nullable=False)
3719 gist_type = Column('gist_type', Unicode(128), nullable=False)
3732 gist_type = Column('gist_type', Unicode(128), nullable=False)
3720 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3733 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3721 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3734 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3722 acl_level = Column('acl_level', Unicode(128), nullable=True)
3735 acl_level = Column('acl_level', Unicode(128), nullable=True)
3723
3736
3724 owner = relationship('User')
3737 owner = relationship('User')
3725
3738
3726 def __repr__(self):
3739 def __repr__(self):
3727 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3740 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3728
3741
3729 @classmethod
3742 @classmethod
3730 def get_or_404(cls, id_, pyramid_exc=False):
3743 def get_or_404(cls, id_, pyramid_exc=False):
3731
3744
3732 if pyramid_exc:
3745 if pyramid_exc:
3733 from pyramid.httpexceptions import HTTPNotFound
3746 from pyramid.httpexceptions import HTTPNotFound
3734 else:
3747 else:
3735 from webob.exc import HTTPNotFound
3748 from webob.exc import HTTPNotFound
3736
3749
3737 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3750 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3738 if not res:
3751 if not res:
3739 raise HTTPNotFound
3752 raise HTTPNotFound
3740 return res
3753 return res
3741
3754
3742 @classmethod
3755 @classmethod
3743 def get_by_access_id(cls, gist_access_id):
3756 def get_by_access_id(cls, gist_access_id):
3744 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3757 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3745
3758
3746 def gist_url(self):
3759 def gist_url(self):
3747 import rhodecode
3760 import rhodecode
3748 from pylons import url
3761 from pylons import url
3749
3762
3750 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3763 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3751 if alias_url:
3764 if alias_url:
3752 return alias_url.replace('{gistid}', self.gist_access_id)
3765 return alias_url.replace('{gistid}', self.gist_access_id)
3753
3766
3754 return url('gist', gist_id=self.gist_access_id, qualified=True)
3767 return url('gist', gist_id=self.gist_access_id, qualified=True)
3755
3768
3756 @classmethod
3769 @classmethod
3757 def base_path(cls):
3770 def base_path(cls):
3758 """
3771 """
3759 Returns base path when all gists are stored
3772 Returns base path when all gists are stored
3760
3773
3761 :param cls:
3774 :param cls:
3762 """
3775 """
3763 from rhodecode.model.gist import GIST_STORE_LOC
3776 from rhodecode.model.gist import GIST_STORE_LOC
3764 q = Session().query(RhodeCodeUi)\
3777 q = Session().query(RhodeCodeUi)\
3765 .filter(RhodeCodeUi.ui_key == URL_SEP)
3778 .filter(RhodeCodeUi.ui_key == URL_SEP)
3766 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3779 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3767 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3780 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3768
3781
3769 def get_api_data(self):
3782 def get_api_data(self):
3770 """
3783 """
3771 Common function for generating gist related data for API
3784 Common function for generating gist related data for API
3772 """
3785 """
3773 gist = self
3786 gist = self
3774 data = {
3787 data = {
3775 'gist_id': gist.gist_id,
3788 'gist_id': gist.gist_id,
3776 'type': gist.gist_type,
3789 'type': gist.gist_type,
3777 'access_id': gist.gist_access_id,
3790 'access_id': gist.gist_access_id,
3778 'description': gist.gist_description,
3791 'description': gist.gist_description,
3779 'url': gist.gist_url(),
3792 'url': gist.gist_url(),
3780 'expires': gist.gist_expires,
3793 'expires': gist.gist_expires,
3781 'created_on': gist.created_on,
3794 'created_on': gist.created_on,
3782 'modified_at': gist.modified_at,
3795 'modified_at': gist.modified_at,
3783 'content': None,
3796 'content': None,
3784 'acl_level': gist.acl_level,
3797 'acl_level': gist.acl_level,
3785 }
3798 }
3786 return data
3799 return data
3787
3800
3788 def __json__(self):
3801 def __json__(self):
3789 data = dict(
3802 data = dict(
3790 )
3803 )
3791 data.update(self.get_api_data())
3804 data.update(self.get_api_data())
3792 return data
3805 return data
3793 # SCM functions
3806 # SCM functions
3794
3807
3795 def scm_instance(self, **kwargs):
3808 def scm_instance(self, **kwargs):
3796 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3809 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3797 return get_vcs_instance(
3810 return get_vcs_instance(
3798 repo_path=safe_str(full_repo_path), create=False)
3811 repo_path=safe_str(full_repo_path), create=False)
3799
3812
3800
3813
3801 class ExternalIdentity(Base, BaseModel):
3814 class ExternalIdentity(Base, BaseModel):
3802 __tablename__ = 'external_identities'
3815 __tablename__ = 'external_identities'
3803 __table_args__ = (
3816 __table_args__ = (
3804 Index('local_user_id_idx', 'local_user_id'),
3817 Index('local_user_id_idx', 'local_user_id'),
3805 Index('external_id_idx', 'external_id'),
3818 Index('external_id_idx', 'external_id'),
3806 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3819 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3807 'mysql_charset': 'utf8'})
3820 'mysql_charset': 'utf8'})
3808
3821
3809 external_id = Column('external_id', Unicode(255), default=u'',
3822 external_id = Column('external_id', Unicode(255), default=u'',
3810 primary_key=True)
3823 primary_key=True)
3811 external_username = Column('external_username', Unicode(1024), default=u'')
3824 external_username = Column('external_username', Unicode(1024), default=u'')
3812 local_user_id = Column('local_user_id', Integer(),
3825 local_user_id = Column('local_user_id', Integer(),
3813 ForeignKey('users.user_id'), primary_key=True)
3826 ForeignKey('users.user_id'), primary_key=True)
3814 provider_name = Column('provider_name', Unicode(255), default=u'',
3827 provider_name = Column('provider_name', Unicode(255), default=u'',
3815 primary_key=True)
3828 primary_key=True)
3816 access_token = Column('access_token', String(1024), default=u'')
3829 access_token = Column('access_token', String(1024), default=u'')
3817 alt_token = Column('alt_token', String(1024), default=u'')
3830 alt_token = Column('alt_token', String(1024), default=u'')
3818 token_secret = Column('token_secret', String(1024), default=u'')
3831 token_secret = Column('token_secret', String(1024), default=u'')
3819
3832
3820 @classmethod
3833 @classmethod
3821 def by_external_id_and_provider(cls, external_id, provider_name,
3834 def by_external_id_and_provider(cls, external_id, provider_name,
3822 local_user_id=None):
3835 local_user_id=None):
3823 """
3836 """
3824 Returns ExternalIdentity instance based on search params
3837 Returns ExternalIdentity instance based on search params
3825
3838
3826 :param external_id:
3839 :param external_id:
3827 :param provider_name:
3840 :param provider_name:
3828 :return: ExternalIdentity
3841 :return: ExternalIdentity
3829 """
3842 """
3830 query = cls.query()
3843 query = cls.query()
3831 query = query.filter(cls.external_id == external_id)
3844 query = query.filter(cls.external_id == external_id)
3832 query = query.filter(cls.provider_name == provider_name)
3845 query = query.filter(cls.provider_name == provider_name)
3833 if local_user_id:
3846 if local_user_id:
3834 query = query.filter(cls.local_user_id == local_user_id)
3847 query = query.filter(cls.local_user_id == local_user_id)
3835 return query.first()
3848 return query.first()
3836
3849
3837 @classmethod
3850 @classmethod
3838 def user_by_external_id_and_provider(cls, external_id, provider_name):
3851 def user_by_external_id_and_provider(cls, external_id, provider_name):
3839 """
3852 """
3840 Returns User instance based on search params
3853 Returns User instance based on search params
3841
3854
3842 :param external_id:
3855 :param external_id:
3843 :param provider_name:
3856 :param provider_name:
3844 :return: User
3857 :return: User
3845 """
3858 """
3846 query = User.query()
3859 query = User.query()
3847 query = query.filter(cls.external_id == external_id)
3860 query = query.filter(cls.external_id == external_id)
3848 query = query.filter(cls.provider_name == provider_name)
3861 query = query.filter(cls.provider_name == provider_name)
3849 query = query.filter(User.user_id == cls.local_user_id)
3862 query = query.filter(User.user_id == cls.local_user_id)
3850 return query.first()
3863 return query.first()
3851
3864
3852 @classmethod
3865 @classmethod
3853 def by_local_user_id(cls, local_user_id):
3866 def by_local_user_id(cls, local_user_id):
3854 """
3867 """
3855 Returns all tokens for user
3868 Returns all tokens for user
3856
3869
3857 :param local_user_id:
3870 :param local_user_id:
3858 :return: ExternalIdentity
3871 :return: ExternalIdentity
3859 """
3872 """
3860 query = cls.query()
3873 query = cls.query()
3861 query = query.filter(cls.local_user_id == local_user_id)
3874 query = query.filter(cls.local_user_id == local_user_id)
3862 return query
3875 return query
3863
3876
3864
3877
3865 class Integration(Base, BaseModel):
3878 class Integration(Base, BaseModel):
3866 __tablename__ = 'integrations'
3879 __tablename__ = 'integrations'
3867 __table_args__ = (
3880 __table_args__ = (
3868 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3869 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3870 )
3883 )
3871
3884
3872 integration_id = Column('integration_id', Integer(), primary_key=True)
3885 integration_id = Column('integration_id', Integer(), primary_key=True)
3873 integration_type = Column('integration_type', String(255))
3886 integration_type = Column('integration_type', String(255))
3874 enabled = Column('enabled', Boolean(), nullable=False)
3887 enabled = Column('enabled', Boolean(), nullable=False)
3875 name = Column('name', String(255), nullable=False)
3888 name = Column('name', String(255), nullable=False)
3876 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3889 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3877 default=False)
3890 default=False)
3878
3891
3879 settings = Column(
3892 settings = Column(
3880 'settings_json', MutationObj.as_mutable(
3893 'settings_json', MutationObj.as_mutable(
3881 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3894 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3882 repo_id = Column(
3895 repo_id = Column(
3883 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3896 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3884 nullable=True, unique=None, default=None)
3897 nullable=True, unique=None, default=None)
3885 repo = relationship('Repository', lazy='joined')
3898 repo = relationship('Repository', lazy='joined')
3886
3899
3887 repo_group_id = Column(
3900 repo_group_id = Column(
3888 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3901 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3889 nullable=True, unique=None, default=None)
3902 nullable=True, unique=None, default=None)
3890 repo_group = relationship('RepoGroup', lazy='joined')
3903 repo_group = relationship('RepoGroup', lazy='joined')
3891
3904
3892 @property
3905 @property
3893 def scope(self):
3906 def scope(self):
3894 if self.repo:
3907 if self.repo:
3895 return repr(self.repo)
3908 return repr(self.repo)
3896 if self.repo_group:
3909 if self.repo_group:
3897 if self.child_repos_only:
3910 if self.child_repos_only:
3898 return repr(self.repo_group) + ' (child repos only)'
3911 return repr(self.repo_group) + ' (child repos only)'
3899 else:
3912 else:
3900 return repr(self.repo_group) + ' (recursive)'
3913 return repr(self.repo_group) + ' (recursive)'
3901 if self.child_repos_only:
3914 if self.child_repos_only:
3902 return 'root_repos'
3915 return 'root_repos'
3903 return 'global'
3916 return 'global'
3904
3917
3905 def __repr__(self):
3918 def __repr__(self):
3906 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3919 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3907
3920
3908
3921
3909 class RepoReviewRuleUser(Base, BaseModel):
3922 class RepoReviewRuleUser(Base, BaseModel):
3910 __tablename__ = 'repo_review_rules_users'
3923 __tablename__ = 'repo_review_rules_users'
3911 __table_args__ = (
3924 __table_args__ = (
3912 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3913 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3914 )
3927 )
3915 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3928 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3916 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3929 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3917 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3930 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3918 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3931 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3919 user = relationship('User')
3932 user = relationship('User')
3920
3933
3921 def rule_data(self):
3934 def rule_data(self):
3922 return {
3935 return {
3923 'mandatory': self.mandatory
3936 'mandatory': self.mandatory
3924 }
3937 }
3925
3938
3926
3939
3927 class RepoReviewRuleUserGroup(Base, BaseModel):
3940 class RepoReviewRuleUserGroup(Base, BaseModel):
3928 __tablename__ = 'repo_review_rules_users_groups'
3941 __tablename__ = 'repo_review_rules_users_groups'
3929 __table_args__ = (
3942 __table_args__ = (
3930 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3931 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3932 )
3945 )
3933 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3946 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3934 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3947 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3935 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3948 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3936 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3949 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3937 users_group = relationship('UserGroup')
3950 users_group = relationship('UserGroup')
3938
3951
3939 def rule_data(self):
3952 def rule_data(self):
3940 return {
3953 return {
3941 'mandatory': self.mandatory
3954 'mandatory': self.mandatory
3942 }
3955 }
3943
3956
3944
3957
3945 class RepoReviewRule(Base, BaseModel):
3958 class RepoReviewRule(Base, BaseModel):
3946 __tablename__ = 'repo_review_rules'
3959 __tablename__ = 'repo_review_rules'
3947 __table_args__ = (
3960 __table_args__ = (
3948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3950 )
3963 )
3951
3964
3952 repo_review_rule_id = Column(
3965 repo_review_rule_id = Column(
3953 'repo_review_rule_id', Integer(), primary_key=True)
3966 'repo_review_rule_id', Integer(), primary_key=True)
3954 repo_id = Column(
3967 repo_id = Column(
3955 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3968 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3956 repo = relationship('Repository', backref='review_rules')
3969 repo = relationship('Repository', backref='review_rules')
3957
3970
3958 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3971 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3959 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3972 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3960
3973
3961 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
3974 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
3962 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
3975 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
3963 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
3976 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
3964 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
3977 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
3965
3978
3966 rule_users = relationship('RepoReviewRuleUser')
3979 rule_users = relationship('RepoReviewRuleUser')
3967 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3980 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3968
3981
3969 @hybrid_property
3982 @hybrid_property
3970 def branch_pattern(self):
3983 def branch_pattern(self):
3971 return self._branch_pattern or '*'
3984 return self._branch_pattern or '*'
3972
3985
3973 def _validate_glob(self, value):
3986 def _validate_glob(self, value):
3974 re.compile('^' + glob2re(value) + '$')
3987 re.compile('^' + glob2re(value) + '$')
3975
3988
3976 @branch_pattern.setter
3989 @branch_pattern.setter
3977 def branch_pattern(self, value):
3990 def branch_pattern(self, value):
3978 self._validate_glob(value)
3991 self._validate_glob(value)
3979 self._branch_pattern = value or '*'
3992 self._branch_pattern = value or '*'
3980
3993
3981 @hybrid_property
3994 @hybrid_property
3982 def file_pattern(self):
3995 def file_pattern(self):
3983 return self._file_pattern or '*'
3996 return self._file_pattern or '*'
3984
3997
3985 @file_pattern.setter
3998 @file_pattern.setter
3986 def file_pattern(self, value):
3999 def file_pattern(self, value):
3987 self._validate_glob(value)
4000 self._validate_glob(value)
3988 self._file_pattern = value or '*'
4001 self._file_pattern = value or '*'
3989
4002
3990 def matches(self, branch, files_changed):
4003 def matches(self, branch, files_changed):
3991 """
4004 """
3992 Check if this review rule matches a branch/files in a pull request
4005 Check if this review rule matches a branch/files in a pull request
3993
4006
3994 :param branch: branch name for the commit
4007 :param branch: branch name for the commit
3995 :param files_changed: list of file paths changed in the pull request
4008 :param files_changed: list of file paths changed in the pull request
3996 """
4009 """
3997
4010
3998 branch = branch or ''
4011 branch = branch or ''
3999 files_changed = files_changed or []
4012 files_changed = files_changed or []
4000
4013
4001 branch_matches = True
4014 branch_matches = True
4002 if branch:
4015 if branch:
4003 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4016 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4004 branch_matches = bool(branch_regex.search(branch))
4017 branch_matches = bool(branch_regex.search(branch))
4005
4018
4006 files_matches = True
4019 files_matches = True
4007 if self.file_pattern != '*':
4020 if self.file_pattern != '*':
4008 files_matches = False
4021 files_matches = False
4009 file_regex = re.compile(glob2re(self.file_pattern))
4022 file_regex = re.compile(glob2re(self.file_pattern))
4010 for filename in files_changed:
4023 for filename in files_changed:
4011 if file_regex.search(filename):
4024 if file_regex.search(filename):
4012 files_matches = True
4025 files_matches = True
4013 break
4026 break
4014
4027
4015 return branch_matches and files_matches
4028 return branch_matches and files_matches
4016
4029
4017 @property
4030 @property
4018 def review_users(self):
4031 def review_users(self):
4019 """ Returns the users which this rule applies to """
4032 """ Returns the users which this rule applies to """
4020
4033
4021 users = collections.OrderedDict()
4034 users = collections.OrderedDict()
4022
4035
4023 for rule_user in self.rule_users:
4036 for rule_user in self.rule_users:
4024 if rule_user.user.active:
4037 if rule_user.user.active:
4025 if rule_user.user not in users:
4038 if rule_user.user not in users:
4026 users[rule_user.user.username] = {
4039 users[rule_user.user.username] = {
4027 'user': rule_user.user,
4040 'user': rule_user.user,
4028 'source': 'user',
4041 'source': 'user',
4029 'source_data': {},
4042 'source_data': {},
4030 'data': rule_user.rule_data()
4043 'data': rule_user.rule_data()
4031 }
4044 }
4032
4045
4033 for rule_user_group in self.rule_user_groups:
4046 for rule_user_group in self.rule_user_groups:
4034 source_data = {
4047 source_data = {
4035 'name': rule_user_group.users_group.users_group_name,
4048 'name': rule_user_group.users_group.users_group_name,
4036 'members': len(rule_user_group.users_group.members)
4049 'members': len(rule_user_group.users_group.members)
4037 }
4050 }
4038 for member in rule_user_group.users_group.members:
4051 for member in rule_user_group.users_group.members:
4039 if member.user.active:
4052 if member.user.active:
4040 users[member.user.username] = {
4053 users[member.user.username] = {
4041 'user': member.user,
4054 'user': member.user,
4042 'source': 'user_group',
4055 'source': 'user_group',
4043 'source_data': source_data,
4056 'source_data': source_data,
4044 'data': rule_user_group.rule_data()
4057 'data': rule_user_group.rule_data()
4045 }
4058 }
4046
4059
4047 return users
4060 return users
4048
4061
4049 def __repr__(self):
4062 def __repr__(self):
4050 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4063 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4051 self.repo_review_rule_id, self.repo)
4064 self.repo_review_rule_id, self.repo)
4052
4065
4053
4066
4054 class DbMigrateVersion(Base, BaseModel):
4067 class DbMigrateVersion(Base, BaseModel):
4055 __tablename__ = 'db_migrate_version'
4068 __tablename__ = 'db_migrate_version'
4056 __table_args__ = (
4069 __table_args__ = (
4057 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4058 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4059 )
4072 )
4060 repository_id = Column('repository_id', String(250), primary_key=True)
4073 repository_id = Column('repository_id', String(250), primary_key=True)
4061 repository_path = Column('repository_path', Text)
4074 repository_path = Column('repository_path', Text)
4062 version = Column('version', Integer)
4075 version = Column('version', Integer)
4063
4076
4064
4077
4065 class DbSession(Base, BaseModel):
4078 class DbSession(Base, BaseModel):
4066 __tablename__ = 'db_session'
4079 __tablename__ = 'db_session'
4067 __table_args__ = (
4080 __table_args__ = (
4068 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4081 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4069 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4082 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4070 )
4083 )
4071
4084
4072 def __repr__(self):
4085 def __repr__(self):
4073 return '<DB:DbSession({})>'.format(self.id)
4086 return '<DB:DbSession({})>'.format(self.id)
4074
4087
4075 id = Column('id', Integer())
4088 id = Column('id', Integer())
4076 namespace = Column('namespace', String(255), primary_key=True)
4089 namespace = Column('namespace', String(255), primary_key=True)
4077 accessed = Column('accessed', DateTime, nullable=False)
4090 accessed = Column('accessed', DateTime, nullable=False)
4078 created = Column('created', DateTime, nullable=False)
4091 created = Column('created', DateTime, nullable=False)
4079 data = Column('data', PickleType, nullable=False)
4092 data = Column('data', PickleType, nullable=False)
@@ -1,160 +1,160 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 </p>
10 </p>
11 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 <tr>
12 <tr>
13 <th>${_('Token')}</th>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
18 <th>${_('Action')}</th>
19 </tr>
19 </tr>
20 %if c.user_auth_tokens:
20 %if c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
23 <td class="truncate-wrap td-authtoken">
23 <td class="truncate-wrap td-authtoken">
24 <div class="user_auth_tokens truncate autoexpand">
24 <div class="user_auth_tokens truncate autoexpand">
25 <code>${auth_token.api_key}</code>
25 <code>${auth_token.api_key}</code>
26 </div>
26 </div>
27 </td>
27 </td>
28 <td class="td">${auth_token.scope_humanized}</td>
28 <td class="td">${auth_token.scope_humanized}</td>
29 <td class="td-wrap">${auth_token.description}</td>
29 <td class="td-wrap">${auth_token.description}</td>
30 <td class="td-tags">
30 <td class="td-tags">
31 <span class="tag disabled">${auth_token.role_humanized}</span>
31 <span class="tag disabled">${auth_token.role_humanized}</span>
32 </td>
32 </td>
33 <td class="td-exp">
33 <td class="td-exp">
34 %if auth_token.expires == -1:
34 %if auth_token.expires == -1:
35 ${_('never')}
35 ${_('never')}
36 %else:
36 %else:
37 %if auth_token.expired:
37 %if auth_token.expired:
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
39 %else:
39 %else:
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
41 %endif
41 %endif
42 %endif
42 %endif
43 </td>
43 </td>
44 <td class="td-action">
44 <td class="td-action">
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
46 ${h.hidden('del_auth_token',auth_token.api_key)}
46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
47 <button class="btn btn-link btn-danger" type="submit"
47 <button class="btn btn-link btn-danger" type="submit"
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
49 ${_('Delete')}
49 ${_('Delete')}
50 </button>
50 </button>
51 ${h.end_form()}
51 ${h.end_form()}
52 </td>
52 </td>
53 </tr>
53 </tr>
54 %endfor
54 %endfor
55 %else:
55 %else:
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
57 %endif
57 %endif
58 </table>
58 </table>
59 </div>
59 </div>
60
60
61 <div class="user_auth_tokens">
61 <div class="user_auth_tokens">
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
63 <div class="form form-vertical">
63 <div class="form form-vertical">
64 <!-- fields -->
64 <!-- fields -->
65 <div class="fields">
65 <div class="fields">
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="new_email">${_('New authentication token')}:</label>
68 <label for="new_email">${_('New authentication token')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
72 ${h.select('lifetime', '', c.lifetime_options)}
72 ${h.select('lifetime', '', c.lifetime_options)}
73 ${h.select('role', '', c.role_options)}
73 ${h.select('role', '', c.role_options)}
74
74
75 % if c.allow_scoped_tokens:
75 % if c.allow_scoped_tokens:
76 ${h.hidden('scope_repo_id')}
76 ${h.hidden('scope_repo_id')}
77 % else:
77 % else:
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
79 % endif
79 % endif
80 </div>
80 </div>
81 <p class="help-block">
81 <p class="help-block">
82 ${_('Repository scope works only with tokens with VCS type.')}
82 ${_('Repository scope works only with tokens with VCS type.')}
83 </p>
83 </p>
84 </div>
84 </div>
85 <div class="buttons">
85 <div class="buttons">
86 ${h.submit('save',_('Add'),class_="btn")}
86 ${h.submit('save',_('Add'),class_="btn")}
87 ${h.reset('reset',_('Reset'),class_="btn")}
87 ${h.reset('reset',_('Reset'),class_="btn")}
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91 ${h.end_form()}
91 ${h.end_form()}
92 </div>
92 </div>
93 </div>
93 </div>
94 </div>
94 </div>
95 <script>
95 <script>
96 $(document).ready(function(){
96 $(document).ready(function(){
97
97
98 var select2Options = {
98 var select2Options = {
99 'containerCssClass': "drop-menu",
99 'containerCssClass': "drop-menu",
100 'dropdownCssClass': "drop-menu-dropdown",
100 'dropdownCssClass': "drop-menu-dropdown",
101 'dropdownAutoWidth': true
101 'dropdownAutoWidth': true
102 };
102 };
103 $("#lifetime").select2(select2Options);
103 $("#lifetime").select2(select2Options);
104 $("#role").select2(select2Options);
104 $("#role").select2(select2Options);
105
105
106 var repoFilter = function(data) {
106 var repoFilter = function(data) {
107 var results = [];
107 var results = [];
108
108
109 if (!data.results[0]) {
109 if (!data.results[0]) {
110 return data
110 return data
111 }
111 }
112
112
113 $.each(data.results[0].children, function() {
113 $.each(data.results[0].children, function() {
114 // replace name to ID for submision
114 // replace name to ID for submision
115 this.id = this.obj.repo_id;
115 this.id = this.obj.repo_id;
116 results.push(this);
116 results.push(this);
117 });
117 });
118
118
119 data.results[0].children = results;
119 data.results[0].children = results;
120 return data;
120 return data;
121 };
121 };
122
122
123 $("#scope_repo_id_disabled").select2(select2Options);
123 $("#scope_repo_id_disabled").select2(select2Options);
124
124
125 $("#scope_repo_id").select2({
125 $("#scope_repo_id").select2({
126 cachedDataSource: {},
126 cachedDataSource: {},
127 minimumInputLength: 2,
127 minimumInputLength: 2,
128 placeholder: "${_('repository scope')}",
128 placeholder: "${_('repository scope')}",
129 dropdownAutoWidth: true,
129 dropdownAutoWidth: true,
130 containerCssClass: "drop-menu",
130 containerCssClass: "drop-menu",
131 dropdownCssClass: "drop-menu-dropdown",
131 dropdownCssClass: "drop-menu-dropdown",
132 formatResult: formatResult,
132 formatResult: formatResult,
133 query: $.debounce(250, function(query){
133 query: $.debounce(250, function(query){
134 self = this;
134 self = this;
135 var cacheKey = query.term;
135 var cacheKey = query.term;
136 var cachedData = self.cachedDataSource[cacheKey];
136 var cachedData = self.cachedDataSource[cacheKey];
137
137
138 if (cachedData) {
138 if (cachedData) {
139 query.callback({results: cachedData.results});
139 query.callback({results: cachedData.results});
140 } else {
140 } else {
141 $.ajax({
141 $.ajax({
142 url: pyroutes.url('repo_list_data'),
142 url: pyroutes.url('repo_list_data'),
143 data: {'query': query.term},
143 data: {'query': query.term},
144 dataType: 'json',
144 dataType: 'json',
145 type: 'GET',
145 type: 'GET',
146 success: function(data) {
146 success: function(data) {
147 data = repoFilter(data);
147 data = repoFilter(data);
148 self.cachedDataSource[cacheKey] = data;
148 self.cachedDataSource[cacheKey] = data;
149 query.callback({results: data.results});
149 query.callback({results: data.results});
150 },
150 },
151 error: function(data, textStatus, errorThrown) {
151 error: function(data, textStatus, errorThrown) {
152 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
152 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
153 }
153 }
154 })
154 })
155 }
155 }
156 })
156 })
157 });
157 });
158
158
159 });
159 });
160 </script>
160 </script>
General Comments 0
You need to be logged in to leave comments. Login now