##// END OF EJS Templates
users: description edit fixes...
marcink -
r4024:dbba29ef default
parent child Browse files
Show More
@@ -1,206 +1,207 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.model.db import RepoGroup
31 from rhodecode.model.db import RepoGroup
32
32
33
33
34 # TODO: mikhail: remove fixture from here
34 # TODO: mikhail: remove fixture from here
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestCreateUser(object):
39 class TestCreateUser(object):
40 def test_api_create_existing_user(self):
40 def test_api_create_existing_user(self):
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey, 'create_user',
42 self.apikey, 'create_user',
43 username=TEST_USER_ADMIN_LOGIN,
43 username=TEST_USER_ADMIN_LOGIN,
44 email='test@foo.com',
44 email='test@foo.com',
45 password='trololo')
45 password='trololo')
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 assert_error(id_, expected, given=response.body)
49 assert_error(id_, expected, given=response.body)
50
50
51 def test_api_create_user_with_existing_email(self):
51 def test_api_create_user_with_existing_email(self):
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_user',
53 self.apikey, 'create_user',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 email=TEST_USER_REGULAR_EMAIL,
55 email=TEST_USER_REGULAR_EMAIL,
56 password='trololo')
56 password='trololo')
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 assert_error(id_, expected, given=response.body)
60 assert_error(id_, expected, given=response.body)
61
61
62 def test_api_create_user_with_wrong_username(self):
62 def test_api_create_user_with_wrong_username(self):
63 bad_username = '<> HELLO WORLD <>'
63 bad_username = '<> HELLO WORLD <>'
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'create_user',
65 self.apikey, 'create_user',
66 username=bad_username,
66 username=bad_username,
67 email='new@email.com',
67 email='new@email.com',
68 password='trololo')
68 password='trololo')
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70
70
71 expected = {'username':
71 expected = {'username':
72 "Username may only contain alphanumeric characters "
72 "Username may only contain alphanumeric characters "
73 "underscores, periods or dashes and must begin with "
73 "underscores, periods or dashes and must begin with "
74 "alphanumeric character or underscore"}
74 "alphanumeric character or underscore"}
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 def test_api_create_user(self):
77 def test_api_create_user(self):
78 username = 'test_new_api_user'
78 username = 'test_new_api_user'
79 email = username + "@foo.com"
79 email = username + "@foo.com"
80
80
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'create_user',
82 self.apikey, 'create_user',
83 username=username,
83 username=username,
84 email=email,
84 email=email,
85 description='CTO of Things',
85 password='example')
86 password='example')
86 response = api_call(self.app, params)
87 response = api_call(self.app, params)
87
88
88 usr = UserModel().get_by_username(username)
89 usr = UserModel().get_by_username(username)
89 ret = {
90 ret = {
90 'msg': 'created new user `%s`' % (username,),
91 'msg': 'created new user `%s`' % (username,),
91 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 }
93 }
93 try:
94 try:
94 expected = ret
95 expected = ret
95 assert check_password('example', usr.password)
96 assert check_password('example', usr.password)
96 assert_ok(id_, expected, given=response.body)
97 assert_ok(id_, expected, given=response.body)
97 finally:
98 finally:
98 fixture.destroy_user(usr.user_id)
99 fixture.destroy_user(usr.user_id)
99
100
100 def test_api_create_user_without_password(self):
101 def test_api_create_user_without_password(self):
101 username = 'test_new_api_user_passwordless'
102 username = 'test_new_api_user_passwordless'
102 email = username + "@foo.com"
103 email = username + "@foo.com"
103
104
104 id_, params = build_data(
105 id_, params = build_data(
105 self.apikey, 'create_user',
106 self.apikey, 'create_user',
106 username=username,
107 username=username,
107 email=email)
108 email=email)
108 response = api_call(self.app, params)
109 response = api_call(self.app, params)
109
110
110 usr = UserModel().get_by_username(username)
111 usr = UserModel().get_by_username(username)
111 ret = {
112 ret = {
112 'msg': 'created new user `%s`' % (username,),
113 'msg': 'created new user `%s`' % (username,),
113 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 }
115 }
115 try:
116 try:
116 expected = ret
117 expected = ret
117 assert_ok(id_, expected, given=response.body)
118 assert_ok(id_, expected, given=response.body)
118 finally:
119 finally:
119 fixture.destroy_user(usr.user_id)
120 fixture.destroy_user(usr.user_id)
120
121
121 def test_api_create_user_with_extern_name(self):
122 def test_api_create_user_with_extern_name(self):
122 username = 'test_new_api_user_passwordless'
123 username = 'test_new_api_user_passwordless'
123 email = username + "@foo.com"
124 email = username + "@foo.com"
124
125
125 id_, params = build_data(
126 id_, params = build_data(
126 self.apikey, 'create_user',
127 self.apikey, 'create_user',
127 username=username,
128 username=username,
128 email=email, extern_name='rhodecode')
129 email=email, extern_name='rhodecode')
129 response = api_call(self.app, params)
130 response = api_call(self.app, params)
130
131
131 usr = UserModel().get_by_username(username)
132 usr = UserModel().get_by_username(username)
132 ret = {
133 ret = {
133 'msg': 'created new user `%s`' % (username,),
134 'msg': 'created new user `%s`' % (username,),
134 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 }
136 }
136 try:
137 try:
137 expected = ret
138 expected = ret
138 assert_ok(id_, expected, given=response.body)
139 assert_ok(id_, expected, given=response.body)
139 finally:
140 finally:
140 fixture.destroy_user(usr.user_id)
141 fixture.destroy_user(usr.user_id)
141
142
142 def test_api_create_user_with_password_change(self):
143 def test_api_create_user_with_password_change(self):
143 username = 'test_new_api_user_password_change'
144 username = 'test_new_api_user_password_change'
144 email = username + "@foo.com"
145 email = username + "@foo.com"
145
146
146 id_, params = build_data(
147 id_, params = build_data(
147 self.apikey, 'create_user',
148 self.apikey, 'create_user',
148 username=username,
149 username=username,
149 email=email, extern_name='rhodecode',
150 email=email, extern_name='rhodecode',
150 force_password_change=True)
151 force_password_change=True)
151 response = api_call(self.app, params)
152 response = api_call(self.app, params)
152
153
153 usr = UserModel().get_by_username(username)
154 usr = UserModel().get_by_username(username)
154 ret = {
155 ret = {
155 'msg': 'created new user `%s`' % (username,),
156 'msg': 'created new user `%s`' % (username,),
156 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 }
158 }
158 try:
159 try:
159 expected = ret
160 expected = ret
160 assert_ok(id_, expected, given=response.body)
161 assert_ok(id_, expected, given=response.body)
161 finally:
162 finally:
162 fixture.destroy_user(usr.user_id)
163 fixture.destroy_user(usr.user_id)
163
164
164 def test_api_create_user_with_personal_repo_group(self):
165 def test_api_create_user_with_personal_repo_group(self):
165 username = 'test_new_api_user_personal_group'
166 username = 'test_new_api_user_personal_group'
166 email = username + "@foo.com"
167 email = username + "@foo.com"
167
168
168 id_, params = build_data(
169 id_, params = build_data(
169 self.apikey, 'create_user',
170 self.apikey, 'create_user',
170 username=username,
171 username=username,
171 email=email, extern_name='rhodecode',
172 email=email, extern_name='rhodecode',
172 create_personal_repo_group=True)
173 create_personal_repo_group=True)
173 response = api_call(self.app, params)
174 response = api_call(self.app, params)
174
175
175 usr = UserModel().get_by_username(username)
176 usr = UserModel().get_by_username(username)
176 ret = {
177 ret = {
177 'msg': 'created new user `%s`' % (username,),
178 'msg': 'created new user `%s`' % (username,),
178 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 }
180 }
180
181
181 personal_group = RepoGroup.get_by_group_name(username)
182 personal_group = RepoGroup.get_by_group_name(username)
182 assert personal_group
183 assert personal_group
183 assert personal_group.personal == True
184 assert personal_group.personal == True
184 assert personal_group.user.username == username
185 assert personal_group.user.username == username
185
186
186 try:
187 try:
187 expected = ret
188 expected = ret
188 assert_ok(id_, expected, given=response.body)
189 assert_ok(id_, expected, given=response.body)
189 finally:
190 finally:
190 fixture.destroy_repo_group(username)
191 fixture.destroy_repo_group(username)
191 fixture.destroy_user(usr.user_id)
192 fixture.destroy_user(usr.user_id)
192
193
193 @mock.patch.object(UserModel, 'create_or_update', crash)
194 @mock.patch.object(UserModel, 'create_or_update', crash)
194 def test_api_create_user_when_exception_happened(self):
195 def test_api_create_user_when_exception_happened(self):
195
196
196 username = 'test_new_api_user'
197 username = 'test_new_api_user'
197 email = username + "@foo.com"
198 email = username + "@foo.com"
198
199
199 id_, params = build_data(
200 id_, params = build_data(
200 self.apikey, 'create_user',
201 self.apikey, 'create_user',
201 username=username,
202 username=username,
202 email=email,
203 email=email,
203 password='trololo')
204 password='trololo')
204 response = api_call(self.app, params)
205 response = api_call(self.app, params)
205 expected = 'failed to create user `%s`' % (username,)
206 expected = 'failed to create user `%s`' % (username,)
206 assert_error(id_, expected, given=response.body)
207 assert_error(id_, expected, given=response.body)
@@ -1,120 +1,121 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUser(object):
32 class TestUpdateUser(object):
33 @pytest.mark.parametrize("name, expected", [
33 @pytest.mark.parametrize("name, expected", [
34 ('firstname', 'new_username'),
34 ('firstname', 'new_username'),
35 ('lastname', 'new_username'),
35 ('lastname', 'new_username'),
36 ('email', 'new_username'),
36 ('email', 'new_username'),
37 ('admin', True),
37 ('admin', True),
38 ('admin', False),
38 ('admin', False),
39 ('extern_type', 'ldap'),
39 ('extern_type', 'ldap'),
40 ('extern_type', None),
40 ('extern_type', None),
41 ('extern_name', 'test'),
41 ('extern_name', 'test'),
42 ('extern_name', None),
42 ('extern_name', None),
43 ('active', False),
43 ('active', False),
44 ('active', True),
44 ('active', True),
45 ('password', 'newpass')
45 ('password', 'newpass'),
46 ('description', 'CTO 4 Life')
46 ])
47 ])
47 def test_api_update_user(self, name, expected, user_util):
48 def test_api_update_user(self, name, expected, user_util):
48 usr = user_util.create_user()
49 usr = user_util.create_user()
49
50
50 kw = {name: expected, 'userid': usr.user_id}
51 kw = {name: expected, 'userid': usr.user_id}
51 id_, params = build_data(self.apikey, 'update_user', **kw)
52 id_, params = build_data(self.apikey, 'update_user', **kw)
52 response = api_call(self.app, params)
53 response = api_call(self.app, params)
53
54
54 ret = {
55 ret = {
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 'user': jsonify(
57 'user': jsonify(
57 UserModel()
58 UserModel()
58 .get_by_username(usr.username)
59 .get_by_username(usr.username)
59 .get_api_data(include_secrets=True)
60 .get_api_data(include_secrets=True)
60 )
61 )
61 }
62 }
62
63
63 expected = ret
64 expected = ret
64 assert_ok(id_, expected, given=response.body)
65 assert_ok(id_, expected, given=response.body)
65
66
66 def test_api_update_user_no_changed_params(self):
67 def test_api_update_user_no_changed_params(self):
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 ret = jsonify(usr.get_api_data(include_secrets=True))
69 ret = jsonify(usr.get_api_data(include_secrets=True))
69 id_, params = build_data(
70 id_, params = build_data(
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71
72
72 response = api_call(self.app, params)
73 response = api_call(self.app, params)
73 ret = {
74 ret = {
74 'msg': 'updated user ID:%s %s' % (
75 'msg': 'updated user ID:%s %s' % (
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 'user': ret
77 'user': ret
77 }
78 }
78 expected = ret
79 expected = ret
79 expected['user']['last_activity'] = response.json['result']['user'][
80 expected['user']['last_activity'] = response.json['result']['user'][
80 'last_activity']
81 'last_activity']
81 assert_ok(id_, expected, given=response.body)
82 assert_ok(id_, expected, given=response.body)
82
83
83 def test_api_update_user_by_user_id(self):
84 def test_api_update_user_by_user_id(self):
84 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
85 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
85 ret = jsonify(usr.get_api_data(include_secrets=True))
86 ret = jsonify(usr.get_api_data(include_secrets=True))
86 id_, params = build_data(
87 id_, params = build_data(
87 self.apikey, 'update_user', userid=usr.user_id)
88 self.apikey, 'update_user', userid=usr.user_id)
88
89
89 response = api_call(self.app, params)
90 response = api_call(self.app, params)
90 ret = {
91 ret = {
91 'msg': 'updated user ID:%s %s' % (
92 'msg': 'updated user ID:%s %s' % (
92 usr.user_id, TEST_USER_ADMIN_LOGIN),
93 usr.user_id, TEST_USER_ADMIN_LOGIN),
93 'user': ret
94 'user': ret
94 }
95 }
95 expected = ret
96 expected = ret
96 expected['user']['last_activity'] = response.json['result']['user'][
97 expected['user']['last_activity'] = response.json['result']['user'][
97 'last_activity']
98 'last_activity']
98 assert_ok(id_, expected, given=response.body)
99 assert_ok(id_, expected, given=response.body)
99
100
100 def test_api_update_user_default_user(self):
101 def test_api_update_user_default_user(self):
101 usr = User.get_default_user()
102 usr = User.get_default_user()
102 id_, params = build_data(
103 id_, params = build_data(
103 self.apikey, 'update_user', userid=usr.user_id)
104 self.apikey, 'update_user', userid=usr.user_id)
104
105
105 response = api_call(self.app, params)
106 response = api_call(self.app, params)
106 expected = 'editing default user is forbidden'
107 expected = 'editing default user is forbidden'
107 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
108
109
109 @mock.patch.object(UserModel, 'update_user', crash)
110 @mock.patch.object(UserModel, 'update_user', crash)
110 def test_api_update_user_when_exception_happens(self):
111 def test_api_update_user_when_exception_happens(self):
111 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
112 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
112 ret = jsonify(usr.get_api_data(include_secrets=True))
113 ret = jsonify(usr.get_api_data(include_secrets=True))
113 id_, params = build_data(
114 id_, params = build_data(
114 self.apikey, 'update_user', userid=usr.user_id)
115 self.apikey, 'update_user', userid=usr.user_id)
115
116
116 response = api_call(self.app, params)
117 response = api_call(self.app, params)
117 ret = 'failed to update user `%s`' % (usr.user_id,)
118 ret = 'failed to update user `%s`' % (usr.user_id,)
118
119
119 expected = ret
120 expected = ret
120 assert_error(id_, expected, given=response.body)
121 assert_error(id_, expected, given=response.body)
@@ -1,564 +1,573 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 from pyramid import compat
22 from pyramid import compat
23
23
24 from rhodecode.api import (
24 from rhodecode.api import (
25 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
25 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
27 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import AuthUser, PasswordGenerator
29 from rhodecode.lib.auth import AuthUser, PasswordGenerator
30 from rhodecode.lib.exceptions import DefaultUserException
30 from rhodecode.lib.exceptions import DefaultUserException
31 from rhodecode.lib.utils2 import safe_int, str2bool
31 from rhodecode.lib.utils2 import safe_int, str2bool
32 from rhodecode.model.db import Session, User, Repository
32 from rhodecode.model.db import Session, User, Repository
33 from rhodecode.model.user import UserModel
33 from rhodecode.model.user import UserModel
34 from rhodecode.model import validation_schema
34 from rhodecode.model import validation_schema
35 from rhodecode.model.validation_schema.schemas import user_schema
35 from rhodecode.model.validation_schema.schemas import user_schema
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 @jsonrpc_method()
40 @jsonrpc_method()
41 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
41 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
42 """
42 """
43 Returns the information associated with a username or userid.
43 Returns the information associated with a username or userid.
44
44
45 * If the ``userid`` is not set, this command returns the information
45 * If the ``userid`` is not set, this command returns the information
46 for the ``userid`` calling the method.
46 for the ``userid`` calling the method.
47
47
48 .. note::
48 .. note::
49
49
50 Normal users may only run this command against their ``userid``. For
50 Normal users may only run this command against their ``userid``. For
51 full privileges you must run this command using an |authtoken| with
51 full privileges you must run this command using an |authtoken| with
52 admin rights.
52 admin rights.
53
53
54 :param apiuser: This is filled automatically from the |authtoken|.
54 :param apiuser: This is filled automatically from the |authtoken|.
55 :type apiuser: AuthUser
55 :type apiuser: AuthUser
56 :param userid: Sets the userid for which data will be returned.
56 :param userid: Sets the userid for which data will be returned.
57 :type userid: Optional(str or int)
57 :type userid: Optional(str or int)
58
58
59 Example output:
59 Example output:
60
60
61 .. code-block:: bash
61 .. code-block:: bash
62
62
63 {
63 {
64 "error": null,
64 "error": null,
65 "id": <id>,
65 "id": <id>,
66 "result": {
66 "result": {
67 "active": true,
67 "active": true,
68 "admin": false,
68 "admin": false,
69 "api_keys": [ list of keys ],
69 "api_keys": [ list of keys ],
70 "auth_tokens": [ list of tokens with details ],
70 "auth_tokens": [ list of tokens with details ],
71 "email": "user@example.com",
71 "email": "user@example.com",
72 "emails": [
72 "emails": [
73 "user@example.com"
73 "user@example.com"
74 ],
74 ],
75 "extern_name": "rhodecode",
75 "extern_name": "rhodecode",
76 "extern_type": "rhodecode",
76 "extern_type": "rhodecode",
77 "firstname": "username",
77 "firstname": "username",
78 "description": "user description",
78 "ip_addresses": [],
79 "ip_addresses": [],
79 "language": null,
80 "language": null,
80 "last_login": "Timestamp",
81 "last_login": "Timestamp",
81 "last_activity": "Timestamp",
82 "last_activity": "Timestamp",
82 "lastname": "surnae",
83 "lastname": "surnae",
83 "permissions": <deprecated>,
84 "permissions": <deprecated>,
84 "permissions_summary": {
85 "permissions_summary": {
85 "global": [
86 "global": [
86 "hg.inherit_default_perms.true",
87 "hg.inherit_default_perms.true",
87 "usergroup.read",
88 "usergroup.read",
88 "hg.repogroup.create.false",
89 "hg.repogroup.create.false",
89 "hg.create.none",
90 "hg.create.none",
90 "hg.password_reset.enabled",
91 "hg.password_reset.enabled",
91 "hg.extern_activate.manual",
92 "hg.extern_activate.manual",
92 "hg.create.write_on_repogroup.false",
93 "hg.create.write_on_repogroup.false",
93 "hg.usergroup.create.false",
94 "hg.usergroup.create.false",
94 "group.none",
95 "group.none",
95 "repository.none",
96 "repository.none",
96 "hg.register.none",
97 "hg.register.none",
97 "hg.fork.repository"
98 "hg.fork.repository"
98 ],
99 ],
99 "repositories": { "username/example": "repository.write"},
100 "repositories": { "username/example": "repository.write"},
100 "repositories_groups": { "user-group/repo": "group.none" },
101 "repositories_groups": { "user-group/repo": "group.none" },
101 "user_groups": { "user_group_name": "usergroup.read" }
102 "user_groups": { "user_group_name": "usergroup.read" }
102 }
103 }
103 "user_id": 32,
104 "user_id": 32,
104 "username": "username"
105 "username": "username"
105 }
106 }
106 }
107 }
107 """
108 """
108
109
109 if not has_superadmin_permission(apiuser):
110 if not has_superadmin_permission(apiuser):
110 # make sure normal user does not pass someone else userid,
111 # make sure normal user does not pass someone else userid,
111 # he is not allowed to do that
112 # he is not allowed to do that
112 if not isinstance(userid, Optional) and userid != apiuser.user_id:
113 if not isinstance(userid, Optional) and userid != apiuser.user_id:
113 raise JSONRPCError('userid is not the same as your user')
114 raise JSONRPCError('userid is not the same as your user')
114
115
115 userid = Optional.extract(userid, evaluate_locals=locals())
116 userid = Optional.extract(userid, evaluate_locals=locals())
116 userid = getattr(userid, 'user_id', userid)
117 userid = getattr(userid, 'user_id', userid)
117
118
118 user = get_user_or_error(userid)
119 user = get_user_or_error(userid)
119 data = user.get_api_data(include_secrets=True)
120 data = user.get_api_data(include_secrets=True)
120 permissions = AuthUser(user_id=user.user_id).permissions
121 permissions = AuthUser(user_id=user.user_id).permissions
121 data['permissions'] = permissions # TODO(marcink): should be deprecated
122 data['permissions'] = permissions # TODO(marcink): should be deprecated
122 data['permissions_summary'] = permissions
123 data['permissions_summary'] = permissions
123 return data
124 return data
124
125
125
126
126 @jsonrpc_method()
127 @jsonrpc_method()
127 def get_users(request, apiuser):
128 def get_users(request, apiuser):
128 """
129 """
129 Lists all users in the |RCE| user database.
130 Lists all users in the |RCE| user database.
130
131
131 This command can only be run using an |authtoken| with admin rights to
132 This command can only be run using an |authtoken| with admin rights to
132 the specified repository.
133 the specified repository.
133
134
134 This command takes the following options:
135 This command takes the following options:
135
136
136 :param apiuser: This is filled automatically from the |authtoken|.
137 :param apiuser: This is filled automatically from the |authtoken|.
137 :type apiuser: AuthUser
138 :type apiuser: AuthUser
138
139
139 Example output:
140 Example output:
140
141
141 .. code-block:: bash
142 .. code-block:: bash
142
143
143 id : <id_given_in_input>
144 id : <id_given_in_input>
144 result: [<user_object>, ...]
145 result: [<user_object>, ...]
145 error: null
146 error: null
146 """
147 """
147
148
148 if not has_superadmin_permission(apiuser):
149 if not has_superadmin_permission(apiuser):
149 raise JSONRPCForbidden()
150 raise JSONRPCForbidden()
150
151
151 result = []
152 result = []
152 users_list = User.query().order_by(User.username) \
153 users_list = User.query().order_by(User.username) \
153 .filter(User.username != User.DEFAULT_USER) \
154 .filter(User.username != User.DEFAULT_USER) \
154 .all()
155 .all()
155 for user in users_list:
156 for user in users_list:
156 result.append(user.get_api_data(include_secrets=True))
157 result.append(user.get_api_data(include_secrets=True))
157 return result
158 return result
158
159
159
160
160 @jsonrpc_method()
161 @jsonrpc_method()
161 def create_user(request, apiuser, username, email, password=Optional(''),
162 def create_user(request, apiuser, username, email, password=Optional(''),
162 firstname=Optional(''), lastname=Optional(''),
163 firstname=Optional(''), lastname=Optional(''), description=Optional(''),
163 active=Optional(True), admin=Optional(False),
164 active=Optional(True), admin=Optional(False),
164 extern_name=Optional('rhodecode'),
165 extern_name=Optional('rhodecode'),
165 extern_type=Optional('rhodecode'),
166 extern_type=Optional('rhodecode'),
166 force_password_change=Optional(False),
167 force_password_change=Optional(False),
167 create_personal_repo_group=Optional(None)):
168 create_personal_repo_group=Optional(None)):
168 """
169 """
169 Creates a new user and returns the new user object.
170 Creates a new user and returns the new user object.
170
171
171 This command can only be run using an |authtoken| with admin rights to
172 This command can only be run using an |authtoken| with admin rights to
172 the specified repository.
173 the specified repository.
173
174
174 This command takes the following options:
175 This command takes the following options:
175
176
176 :param apiuser: This is filled automatically from the |authtoken|.
177 :param apiuser: This is filled automatically from the |authtoken|.
177 :type apiuser: AuthUser
178 :type apiuser: AuthUser
178 :param username: Set the new username.
179 :param username: Set the new username.
179 :type username: str or int
180 :type username: str or int
180 :param email: Set the user email address.
181 :param email: Set the user email address.
181 :type email: str
182 :type email: str
182 :param password: Set the new user password.
183 :param password: Set the new user password.
183 :type password: Optional(str)
184 :type password: Optional(str)
184 :param firstname: Set the new user firstname.
185 :param firstname: Set the new user firstname.
185 :type firstname: Optional(str)
186 :type firstname: Optional(str)
186 :param lastname: Set the new user surname.
187 :param lastname: Set the new user surname.
187 :type lastname: Optional(str)
188 :type lastname: Optional(str)
189 :param description: Set user description, or short bio. Metatags are allowed.
190 :type description: Optional(str)
188 :param active: Set the user as active.
191 :param active: Set the user as active.
189 :type active: Optional(``True`` | ``False``)
192 :type active: Optional(``True`` | ``False``)
190 :param admin: Give the new user admin rights.
193 :param admin: Give the new user admin rights.
191 :type admin: Optional(``True`` | ``False``)
194 :type admin: Optional(``True`` | ``False``)
192 :param extern_name: Set the authentication plugin name.
195 :param extern_name: Set the authentication plugin name.
193 Using LDAP this is filled with LDAP UID.
196 Using LDAP this is filled with LDAP UID.
194 :type extern_name: Optional(str)
197 :type extern_name: Optional(str)
195 :param extern_type: Set the new user authentication plugin.
198 :param extern_type: Set the new user authentication plugin.
196 :type extern_type: Optional(str)
199 :type extern_type: Optional(str)
197 :param force_password_change: Force the new user to change password
200 :param force_password_change: Force the new user to change password
198 on next login.
201 on next login.
199 :type force_password_change: Optional(``True`` | ``False``)
202 :type force_password_change: Optional(``True`` | ``False``)
200 :param create_personal_repo_group: Create personal repo group for this user
203 :param create_personal_repo_group: Create personal repo group for this user
201 :type create_personal_repo_group: Optional(``True`` | ``False``)
204 :type create_personal_repo_group: Optional(``True`` | ``False``)
202
205
203 Example output:
206 Example output:
204
207
205 .. code-block:: bash
208 .. code-block:: bash
206
209
207 id : <id_given_in_input>
210 id : <id_given_in_input>
208 result: {
211 result: {
209 "msg" : "created new user `<username>`",
212 "msg" : "created new user `<username>`",
210 "user": <user_obj>
213 "user": <user_obj>
211 }
214 }
212 error: null
215 error: null
213
216
214 Example error output:
217 Example error output:
215
218
216 .. code-block:: bash
219 .. code-block:: bash
217
220
218 id : <id_given_in_input>
221 id : <id_given_in_input>
219 result : null
222 result : null
220 error : {
223 error : {
221 "user `<username>` already exist"
224 "user `<username>` already exist"
222 or
225 or
223 "email `<email>` already exist"
226 "email `<email>` already exist"
224 or
227 or
225 "failed to create user `<username>`"
228 "failed to create user `<username>`"
226 }
229 }
227
230
228 """
231 """
229 if not has_superadmin_permission(apiuser):
232 if not has_superadmin_permission(apiuser):
230 raise JSONRPCForbidden()
233 raise JSONRPCForbidden()
231
234
232 if UserModel().get_by_username(username):
235 if UserModel().get_by_username(username):
233 raise JSONRPCError("user `%s` already exist" % (username,))
236 raise JSONRPCError("user `%s` already exist" % (username,))
234
237
235 if UserModel().get_by_email(email, case_insensitive=True):
238 if UserModel().get_by_email(email, case_insensitive=True):
236 raise JSONRPCError("email `%s` already exist" % (email,))
239 raise JSONRPCError("email `%s` already exist" % (email,))
237
240
238 # generate random password if we actually given the
241 # generate random password if we actually given the
239 # extern_name and it's not rhodecode
242 # extern_name and it's not rhodecode
240 if (not isinstance(extern_name, Optional) and
243 if (not isinstance(extern_name, Optional) and
241 Optional.extract(extern_name) != 'rhodecode'):
244 Optional.extract(extern_name) != 'rhodecode'):
242 # generate temporary password if user is external
245 # generate temporary password if user is external
243 password = PasswordGenerator().gen_password(length=16)
246 password = PasswordGenerator().gen_password(length=16)
244 create_repo_group = Optional.extract(create_personal_repo_group)
247 create_repo_group = Optional.extract(create_personal_repo_group)
245 if isinstance(create_repo_group, compat.string_types):
248 if isinstance(create_repo_group, compat.string_types):
246 create_repo_group = str2bool(create_repo_group)
249 create_repo_group = str2bool(create_repo_group)
247
250
248 username = Optional.extract(username)
251 username = Optional.extract(username)
249 password = Optional.extract(password)
252 password = Optional.extract(password)
250 email = Optional.extract(email)
253 email = Optional.extract(email)
251 first_name = Optional.extract(firstname)
254 first_name = Optional.extract(firstname)
252 last_name = Optional.extract(lastname)
255 last_name = Optional.extract(lastname)
256 description = Optional.extract(description)
253 active = Optional.extract(active)
257 active = Optional.extract(active)
254 admin = Optional.extract(admin)
258 admin = Optional.extract(admin)
255 extern_type = Optional.extract(extern_type)
259 extern_type = Optional.extract(extern_type)
256 extern_name = Optional.extract(extern_name)
260 extern_name = Optional.extract(extern_name)
257
261
258 schema = user_schema.UserSchema().bind(
262 schema = user_schema.UserSchema().bind(
259 # user caller
263 # user caller
260 user=apiuser)
264 user=apiuser)
261 try:
265 try:
262 schema_data = schema.deserialize(dict(
266 schema_data = schema.deserialize(dict(
263 username=username,
267 username=username,
264 email=email,
268 email=email,
265 password=password,
269 password=password,
266 first_name=first_name,
270 first_name=first_name,
267 last_name=last_name,
271 last_name=last_name,
268 active=active,
272 active=active,
269 admin=admin,
273 admin=admin,
274 description=description,
270 extern_type=extern_type,
275 extern_type=extern_type,
271 extern_name=extern_name,
276 extern_name=extern_name,
272 ))
277 ))
273 except validation_schema.Invalid as err:
278 except validation_schema.Invalid as err:
274 raise JSONRPCValidationError(colander_exc=err)
279 raise JSONRPCValidationError(colander_exc=err)
275
280
276 try:
281 try:
277 user = UserModel().create_or_update(
282 user = UserModel().create_or_update(
278 username=schema_data['username'],
283 username=schema_data['username'],
279 password=schema_data['password'],
284 password=schema_data['password'],
280 email=schema_data['email'],
285 email=schema_data['email'],
281 firstname=schema_data['first_name'],
286 firstname=schema_data['first_name'],
282 lastname=schema_data['last_name'],
287 lastname=schema_data['last_name'],
288 description=schema_data['description'],
283 active=schema_data['active'],
289 active=schema_data['active'],
284 admin=schema_data['admin'],
290 admin=schema_data['admin'],
285 extern_type=schema_data['extern_type'],
291 extern_type=schema_data['extern_type'],
286 extern_name=schema_data['extern_name'],
292 extern_name=schema_data['extern_name'],
287 force_password_change=Optional.extract(force_password_change),
293 force_password_change=Optional.extract(force_password_change),
288 create_repo_group=create_repo_group
294 create_repo_group=create_repo_group
289 )
295 )
290 Session().flush()
296 Session().flush()
291 creation_data = user.get_api_data()
297 creation_data = user.get_api_data()
292 audit_logger.store_api(
298 audit_logger.store_api(
293 'user.create', action_data={'data': creation_data},
299 'user.create', action_data={'data': creation_data},
294 user=apiuser)
300 user=apiuser)
295
301
296 Session().commit()
302 Session().commit()
297 return {
303 return {
298 'msg': 'created new user `%s`' % username,
304 'msg': 'created new user `%s`' % username,
299 'user': user.get_api_data(include_secrets=True)
305 'user': user.get_api_data(include_secrets=True)
300 }
306 }
301 except Exception:
307 except Exception:
302 log.exception('Error occurred during creation of user')
308 log.exception('Error occurred during creation of user')
303 raise JSONRPCError('failed to create user `%s`' % (username,))
309 raise JSONRPCError('failed to create user `%s`' % (username,))
304
310
305
311
306 @jsonrpc_method()
312 @jsonrpc_method()
307 def update_user(request, apiuser, userid, username=Optional(None),
313 def update_user(request, apiuser, userid, username=Optional(None),
308 email=Optional(None), password=Optional(None),
314 email=Optional(None), password=Optional(None),
309 firstname=Optional(None), lastname=Optional(None),
315 firstname=Optional(None), lastname=Optional(None),
310 active=Optional(None), admin=Optional(None),
316 description=Optional(None), active=Optional(None), admin=Optional(None),
311 extern_type=Optional(None), extern_name=Optional(None), ):
317 extern_type=Optional(None), extern_name=Optional(None), ):
312 """
318 """
313 Updates the details for the specified user, if that user exists.
319 Updates the details for the specified user, if that user exists.
314
320
315 This command can only be run using an |authtoken| with admin rights to
321 This command can only be run using an |authtoken| with admin rights to
316 the specified repository.
322 the specified repository.
317
323
318 This command takes the following options:
324 This command takes the following options:
319
325
320 :param apiuser: This is filled automatically from |authtoken|.
326 :param apiuser: This is filled automatically from |authtoken|.
321 :type apiuser: AuthUser
327 :type apiuser: AuthUser
322 :param userid: Set the ``userid`` to update.
328 :param userid: Set the ``userid`` to update.
323 :type userid: str or int
329 :type userid: str or int
324 :param username: Set the new username.
330 :param username: Set the new username.
325 :type username: str or int
331 :type username: str or int
326 :param email: Set the new email.
332 :param email: Set the new email.
327 :type email: str
333 :type email: str
328 :param password: Set the new password.
334 :param password: Set the new password.
329 :type password: Optional(str)
335 :type password: Optional(str)
330 :param firstname: Set the new first name.
336 :param firstname: Set the new first name.
331 :type firstname: Optional(str)
337 :type firstname: Optional(str)
332 :param lastname: Set the new surname.
338 :param lastname: Set the new surname.
333 :type lastname: Optional(str)
339 :type lastname: Optional(str)
340 :param description: Set user description, or short bio. Metatags are allowed.
341 :type description: Optional(str)
334 :param active: Set the new user as active.
342 :param active: Set the new user as active.
335 :type active: Optional(``True`` | ``False``)
343 :type active: Optional(``True`` | ``False``)
336 :param admin: Give the user admin rights.
344 :param admin: Give the user admin rights.
337 :type admin: Optional(``True`` | ``False``)
345 :type admin: Optional(``True`` | ``False``)
338 :param extern_name: Set the authentication plugin user name.
346 :param extern_name: Set the authentication plugin user name.
339 Using LDAP this is filled with LDAP UID.
347 Using LDAP this is filled with LDAP UID.
340 :type extern_name: Optional(str)
348 :type extern_name: Optional(str)
341 :param extern_type: Set the authentication plugin type.
349 :param extern_type: Set the authentication plugin type.
342 :type extern_type: Optional(str)
350 :type extern_type: Optional(str)
343
351
344
352
345 Example output:
353 Example output:
346
354
347 .. code-block:: bash
355 .. code-block:: bash
348
356
349 id : <id_given_in_input>
357 id : <id_given_in_input>
350 result: {
358 result: {
351 "msg" : "updated user ID:<userid> <username>",
359 "msg" : "updated user ID:<userid> <username>",
352 "user": <user_object>,
360 "user": <user_object>,
353 }
361 }
354 error: null
362 error: null
355
363
356 Example error output:
364 Example error output:
357
365
358 .. code-block:: bash
366 .. code-block:: bash
359
367
360 id : <id_given_in_input>
368 id : <id_given_in_input>
361 result : null
369 result : null
362 error : {
370 error : {
363 "failed to update user `<username>`"
371 "failed to update user `<username>`"
364 }
372 }
365
373
366 """
374 """
367 if not has_superadmin_permission(apiuser):
375 if not has_superadmin_permission(apiuser):
368 raise JSONRPCForbidden()
376 raise JSONRPCForbidden()
369
377
370 user = get_user_or_error(userid)
378 user = get_user_or_error(userid)
371 old_data = user.get_api_data()
379 old_data = user.get_api_data()
372 # only non optional arguments will be stored in updates
380 # only non optional arguments will be stored in updates
373 updates = {}
381 updates = {}
374
382
375 try:
383 try:
376
384
377 store_update(updates, username, 'username')
385 store_update(updates, username, 'username')
378 store_update(updates, password, 'password')
386 store_update(updates, password, 'password')
379 store_update(updates, email, 'email')
387 store_update(updates, email, 'email')
380 store_update(updates, firstname, 'name')
388 store_update(updates, firstname, 'name')
381 store_update(updates, lastname, 'lastname')
389 store_update(updates, lastname, 'lastname')
390 store_update(updates, description, 'description')
382 store_update(updates, active, 'active')
391 store_update(updates, active, 'active')
383 store_update(updates, admin, 'admin')
392 store_update(updates, admin, 'admin')
384 store_update(updates, extern_name, 'extern_name')
393 store_update(updates, extern_name, 'extern_name')
385 store_update(updates, extern_type, 'extern_type')
394 store_update(updates, extern_type, 'extern_type')
386
395
387 user = UserModel().update_user(user, **updates)
396 user = UserModel().update_user(user, **updates)
388 audit_logger.store_api(
397 audit_logger.store_api(
389 'user.edit', action_data={'old_data': old_data},
398 'user.edit', action_data={'old_data': old_data},
390 user=apiuser)
399 user=apiuser)
391 Session().commit()
400 Session().commit()
392 return {
401 return {
393 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
402 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
394 'user': user.get_api_data(include_secrets=True)
403 'user': user.get_api_data(include_secrets=True)
395 }
404 }
396 except DefaultUserException:
405 except DefaultUserException:
397 log.exception("Default user edit exception")
406 log.exception("Default user edit exception")
398 raise JSONRPCError('editing default user is forbidden')
407 raise JSONRPCError('editing default user is forbidden')
399 except Exception:
408 except Exception:
400 log.exception("Error occurred during update of user")
409 log.exception("Error occurred during update of user")
401 raise JSONRPCError('failed to update user `%s`' % (userid,))
410 raise JSONRPCError('failed to update user `%s`' % (userid,))
402
411
403
412
404 @jsonrpc_method()
413 @jsonrpc_method()
405 def delete_user(request, apiuser, userid):
414 def delete_user(request, apiuser, userid):
406 """
415 """
407 Deletes the specified user from the |RCE| user database.
416 Deletes the specified user from the |RCE| user database.
408
417
409 This command can only be run using an |authtoken| with admin rights to
418 This command can only be run using an |authtoken| with admin rights to
410 the specified repository.
419 the specified repository.
411
420
412 .. important::
421 .. important::
413
422
414 Ensure all open pull requests and open code review
423 Ensure all open pull requests and open code review
415 requests to this user are close.
424 requests to this user are close.
416
425
417 Also ensure all repositories, or repository groups owned by this
426 Also ensure all repositories, or repository groups owned by this
418 user are reassigned before deletion.
427 user are reassigned before deletion.
419
428
420 This command takes the following options:
429 This command takes the following options:
421
430
422 :param apiuser: This is filled automatically from the |authtoken|.
431 :param apiuser: This is filled automatically from the |authtoken|.
423 :type apiuser: AuthUser
432 :type apiuser: AuthUser
424 :param userid: Set the user to delete.
433 :param userid: Set the user to delete.
425 :type userid: str or int
434 :type userid: str or int
426
435
427 Example output:
436 Example output:
428
437
429 .. code-block:: bash
438 .. code-block:: bash
430
439
431 id : <id_given_in_input>
440 id : <id_given_in_input>
432 result: {
441 result: {
433 "msg" : "deleted user ID:<userid> <username>",
442 "msg" : "deleted user ID:<userid> <username>",
434 "user": null
443 "user": null
435 }
444 }
436 error: null
445 error: null
437
446
438 Example error output:
447 Example error output:
439
448
440 .. code-block:: bash
449 .. code-block:: bash
441
450
442 id : <id_given_in_input>
451 id : <id_given_in_input>
443 result : null
452 result : null
444 error : {
453 error : {
445 "failed to delete user ID:<userid> <username>"
454 "failed to delete user ID:<userid> <username>"
446 }
455 }
447
456
448 """
457 """
449 if not has_superadmin_permission(apiuser):
458 if not has_superadmin_permission(apiuser):
450 raise JSONRPCForbidden()
459 raise JSONRPCForbidden()
451
460
452 user = get_user_or_error(userid)
461 user = get_user_or_error(userid)
453 old_data = user.get_api_data()
462 old_data = user.get_api_data()
454 try:
463 try:
455 UserModel().delete(userid)
464 UserModel().delete(userid)
456 audit_logger.store_api(
465 audit_logger.store_api(
457 'user.delete', action_data={'old_data': old_data},
466 'user.delete', action_data={'old_data': old_data},
458 user=apiuser)
467 user=apiuser)
459
468
460 Session().commit()
469 Session().commit()
461 return {
470 return {
462 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
471 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
463 'user': None
472 'user': None
464 }
473 }
465 except Exception:
474 except Exception:
466 log.exception("Error occurred during deleting of user")
475 log.exception("Error occurred during deleting of user")
467 raise JSONRPCError(
476 raise JSONRPCError(
468 'failed to delete user ID:%s %s' % (user.user_id, user.username))
477 'failed to delete user ID:%s %s' % (user.user_id, user.username))
469
478
470
479
471 @jsonrpc_method()
480 @jsonrpc_method()
472 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
481 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
473 """
482 """
474 Displays all repositories locked by the specified user.
483 Displays all repositories locked by the specified user.
475
484
476 * If this command is run by a non-admin user, it returns
485 * If this command is run by a non-admin user, it returns
477 a list of |repos| locked by that user.
486 a list of |repos| locked by that user.
478
487
479 This command takes the following options:
488 This command takes the following options:
480
489
481 :param apiuser: This is filled automatically from the |authtoken|.
490 :param apiuser: This is filled automatically from the |authtoken|.
482 :type apiuser: AuthUser
491 :type apiuser: AuthUser
483 :param userid: Sets the userid whose list of locked |repos| will be
492 :param userid: Sets the userid whose list of locked |repos| will be
484 displayed.
493 displayed.
485 :type userid: Optional(str or int)
494 :type userid: Optional(str or int)
486
495
487 Example output:
496 Example output:
488
497
489 .. code-block:: bash
498 .. code-block:: bash
490
499
491 id : <id_given_in_input>
500 id : <id_given_in_input>
492 result : {
501 result : {
493 [repo_object, repo_object,...]
502 [repo_object, repo_object,...]
494 }
503 }
495 error : null
504 error : null
496 """
505 """
497
506
498 include_secrets = False
507 include_secrets = False
499 if not has_superadmin_permission(apiuser):
508 if not has_superadmin_permission(apiuser):
500 # make sure normal user does not pass someone else userid,
509 # make sure normal user does not pass someone else userid,
501 # he is not allowed to do that
510 # he is not allowed to do that
502 if not isinstance(userid, Optional) and userid != apiuser.user_id:
511 if not isinstance(userid, Optional) and userid != apiuser.user_id:
503 raise JSONRPCError('userid is not the same as your user')
512 raise JSONRPCError('userid is not the same as your user')
504 else:
513 else:
505 include_secrets = True
514 include_secrets = True
506
515
507 userid = Optional.extract(userid, evaluate_locals=locals())
516 userid = Optional.extract(userid, evaluate_locals=locals())
508 userid = getattr(userid, 'user_id', userid)
517 userid = getattr(userid, 'user_id', userid)
509 user = get_user_or_error(userid)
518 user = get_user_or_error(userid)
510
519
511 ret = []
520 ret = []
512
521
513 # show all locks
522 # show all locks
514 for r in Repository.getAll():
523 for r in Repository.getAll():
515 _user_id, _time, _reason = r.locked
524 _user_id, _time, _reason = r.locked
516 if _user_id and _time:
525 if _user_id and _time:
517 _api_data = r.get_api_data(include_secrets=include_secrets)
526 _api_data = r.get_api_data(include_secrets=include_secrets)
518 # if we use user filter just show the locks for this user
527 # if we use user filter just show the locks for this user
519 if safe_int(_user_id) == user.user_id:
528 if safe_int(_user_id) == user.user_id:
520 ret.append(_api_data)
529 ret.append(_api_data)
521
530
522 return ret
531 return ret
523
532
524
533
525 @jsonrpc_method()
534 @jsonrpc_method()
526 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
535 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
527 """
536 """
528 Fetches all action logs made by the specified user.
537 Fetches all action logs made by the specified user.
529
538
530 This command takes the following options:
539 This command takes the following options:
531
540
532 :param apiuser: This is filled automatically from the |authtoken|.
541 :param apiuser: This is filled automatically from the |authtoken|.
533 :type apiuser: AuthUser
542 :type apiuser: AuthUser
534 :param userid: Sets the userid whose list of locked |repos| will be
543 :param userid: Sets the userid whose list of locked |repos| will be
535 displayed.
544 displayed.
536 :type userid: Optional(str or int)
545 :type userid: Optional(str or int)
537
546
538 Example output:
547 Example output:
539
548
540 .. code-block:: bash
549 .. code-block:: bash
541
550
542 id : <id_given_in_input>
551 id : <id_given_in_input>
543 result : {
552 result : {
544 [action, action,...]
553 [action, action,...]
545 }
554 }
546 error : null
555 error : null
547 """
556 """
548
557
549 if not has_superadmin_permission(apiuser):
558 if not has_superadmin_permission(apiuser):
550 # make sure normal user does not pass someone else userid,
559 # make sure normal user does not pass someone else userid,
551 # he is not allowed to do that
560 # he is not allowed to do that
552 if not isinstance(userid, Optional) and userid != apiuser.user_id:
561 if not isinstance(userid, Optional) and userid != apiuser.user_id:
553 raise JSONRPCError('userid is not the same as your user')
562 raise JSONRPCError('userid is not the same as your user')
554
563
555 userid = Optional.extract(userid, evaluate_locals=locals())
564 userid = Optional.extract(userid, evaluate_locals=locals())
556 userid = getattr(userid, 'user_id', userid)
565 userid = getattr(userid, 'user_id', userid)
557 user = get_user_or_error(userid)
566 user = get_user_or_error(userid)
558
567
559 ret = []
568 ret = []
560
569
561 # show all user actions
570 # show all user actions
562 for entry in UserModel().get_user_log(user, filter_term=None):
571 for entry in UserModel().get_user_log(user, filter_term=None):
563 ret.append(entry)
572 ret.append(entry)
564 return ret
573 return ret
@@ -1,790 +1,793 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib import auth
24 from rhodecode.lib import auth
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
29
29
30 from rhodecode.tests import (
30 from rhodecode.tests import (
31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 from rhodecode.tests.fixture import Fixture
32 from rhodecode.tests.fixture import Fixture
33
33
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39 from rhodecode.apps._base import ADMIN_PREFIX
39 from rhodecode.apps._base import ADMIN_PREFIX
40
40
41 base_url = {
41 base_url = {
42 'users':
42 'users':
43 ADMIN_PREFIX + '/users',
43 ADMIN_PREFIX + '/users',
44 'users_data':
44 'users_data':
45 ADMIN_PREFIX + '/users_data',
45 ADMIN_PREFIX + '/users_data',
46 'users_create':
46 'users_create':
47 ADMIN_PREFIX + '/users/create',
47 ADMIN_PREFIX + '/users/create',
48 'users_new':
48 'users_new':
49 ADMIN_PREFIX + '/users/new',
49 ADMIN_PREFIX + '/users/new',
50 'user_edit':
50 'user_edit':
51 ADMIN_PREFIX + '/users/{user_id}/edit',
51 ADMIN_PREFIX + '/users/{user_id}/edit',
52 'user_edit_advanced':
52 'user_edit_advanced':
53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
54 'user_edit_global_perms':
54 'user_edit_global_perms':
55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
56 'user_edit_global_perms_update':
56 'user_edit_global_perms_update':
57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
58 'user_update':
58 'user_update':
59 ADMIN_PREFIX + '/users/{user_id}/update',
59 ADMIN_PREFIX + '/users/{user_id}/update',
60 'user_delete':
60 'user_delete':
61 ADMIN_PREFIX + '/users/{user_id}/delete',
61 ADMIN_PREFIX + '/users/{user_id}/delete',
62 'user_create_personal_repo_group':
62 'user_create_personal_repo_group':
63 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
63 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
64
64
65 'edit_user_auth_tokens':
65 'edit_user_auth_tokens':
66 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
66 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
67 'edit_user_auth_tokens_add':
67 'edit_user_auth_tokens_add':
68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
69 'edit_user_auth_tokens_delete':
69 'edit_user_auth_tokens_delete':
70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
71
71
72 'edit_user_emails':
72 'edit_user_emails':
73 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
73 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
74 'edit_user_emails_add':
74 'edit_user_emails_add':
75 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
75 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
76 'edit_user_emails_delete':
76 'edit_user_emails_delete':
77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
78
78
79 'edit_user_ips':
79 'edit_user_ips':
80 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
80 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
81 'edit_user_ips_add':
81 'edit_user_ips_add':
82 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
82 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
83 'edit_user_ips_delete':
83 'edit_user_ips_delete':
84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
85
85
86 'edit_user_perms_summary':
86 'edit_user_perms_summary':
87 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
87 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
88 'edit_user_perms_summary_json':
88 'edit_user_perms_summary_json':
89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
90
90
91 'edit_user_audit_logs':
91 'edit_user_audit_logs':
92 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
92 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
93
93
94 'edit_user_audit_logs_download':
94 'edit_user_audit_logs_download':
95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
96
96
97 }[name].format(**kwargs)
97 }[name].format(**kwargs)
98
98
99 if params:
99 if params:
100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
101 return base_url
101 return base_url
102
102
103
103
104 class TestAdminUsersView(TestController):
104 class TestAdminUsersView(TestController):
105
105
106 def test_show_users(self):
106 def test_show_users(self):
107 self.log_user()
107 self.log_user()
108 self.app.get(route_path('users'))
108 self.app.get(route_path('users'))
109
109
110 def test_show_users_data(self, xhr_header):
110 def test_show_users_data(self, xhr_header):
111 self.log_user()
111 self.log_user()
112 response = self.app.get(route_path(
112 response = self.app.get(route_path(
113 'users_data'), extra_environ=xhr_header)
113 'users_data'), extra_environ=xhr_header)
114
114
115 all_users = User.query().filter(
115 all_users = User.query().filter(
116 User.username != User.DEFAULT_USER).count()
116 User.username != User.DEFAULT_USER).count()
117 assert response.json['recordsTotal'] == all_users
117 assert response.json['recordsTotal'] == all_users
118
118
119 def test_show_users_data_filtered(self, xhr_header):
119 def test_show_users_data_filtered(self, xhr_header):
120 self.log_user()
120 self.log_user()
121 response = self.app.get(route_path(
121 response = self.app.get(route_path(
122 'users_data', params={'search[value]': 'empty_search'}),
122 'users_data', params={'search[value]': 'empty_search'}),
123 extra_environ=xhr_header)
123 extra_environ=xhr_header)
124
124
125 all_users = User.query().filter(
125 all_users = User.query().filter(
126 User.username != User.DEFAULT_USER).count()
126 User.username != User.DEFAULT_USER).count()
127 assert response.json['recordsTotal'] == all_users
127 assert response.json['recordsTotal'] == all_users
128 assert response.json['recordsFiltered'] == 0
128 assert response.json['recordsFiltered'] == 0
129
129
130 def test_auth_tokens_default_user(self):
130 def test_auth_tokens_default_user(self):
131 self.log_user()
131 self.log_user()
132 user = User.get_default_user()
132 user = User.get_default_user()
133 response = self.app.get(
133 response = self.app.get(
134 route_path('edit_user_auth_tokens', user_id=user.user_id),
134 route_path('edit_user_auth_tokens', user_id=user.user_id),
135 status=302)
135 status=302)
136
136
137 def test_auth_tokens(self):
137 def test_auth_tokens(self):
138 self.log_user()
138 self.log_user()
139
139
140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
141 user_id = user.user_id
141 user_id = user.user_id
142 auth_tokens = user.auth_tokens
142 auth_tokens = user.auth_tokens
143 response = self.app.get(
143 response = self.app.get(
144 route_path('edit_user_auth_tokens', user_id=user_id))
144 route_path('edit_user_auth_tokens', user_id=user_id))
145 for token in auth_tokens:
145 for token in auth_tokens:
146 response.mustcontain(token)
146 response.mustcontain(token)
147 response.mustcontain('never')
147 response.mustcontain('never')
148
148
149 @pytest.mark.parametrize("desc, lifetime", [
149 @pytest.mark.parametrize("desc, lifetime", [
150 ('forever', -1),
150 ('forever', -1),
151 ('5mins', 60*5),
151 ('5mins', 60*5),
152 ('30days', 60*60*24*30),
152 ('30days', 60*60*24*30),
153 ])
153 ])
154 def test_add_auth_token(self, desc, lifetime, user_util):
154 def test_add_auth_token(self, desc, lifetime, user_util):
155 self.log_user()
155 self.log_user()
156 user = user_util.create_user()
156 user = user_util.create_user()
157 user_id = user.user_id
157 user_id = user.user_id
158
158
159 response = self.app.post(
159 response = self.app.post(
160 route_path('edit_user_auth_tokens_add', user_id=user_id),
160 route_path('edit_user_auth_tokens_add', user_id=user_id),
161 {'description': desc, 'lifetime': lifetime,
161 {'description': desc, 'lifetime': lifetime,
162 'csrf_token': self.csrf_token})
162 'csrf_token': self.csrf_token})
163 assert_session_flash(response, 'Auth token successfully created')
163 assert_session_flash(response, 'Auth token successfully created')
164
164
165 response = response.follow()
165 response = response.follow()
166 user = User.get(user_id)
166 user = User.get(user_id)
167 for auth_token in user.auth_tokens:
167 for auth_token in user.auth_tokens:
168 response.mustcontain(auth_token)
168 response.mustcontain(auth_token)
169
169
170 def test_delete_auth_token(self, user_util):
170 def test_delete_auth_token(self, user_util):
171 self.log_user()
171 self.log_user()
172 user = user_util.create_user()
172 user = user_util.create_user()
173 user_id = user.user_id
173 user_id = user.user_id
174 keys = user.auth_tokens
174 keys = user.auth_tokens
175 assert 2 == len(keys)
175 assert 2 == len(keys)
176
176
177 response = self.app.post(
177 response = self.app.post(
178 route_path('edit_user_auth_tokens_add', user_id=user_id),
178 route_path('edit_user_auth_tokens_add', user_id=user_id),
179 {'description': 'desc', 'lifetime': -1,
179 {'description': 'desc', 'lifetime': -1,
180 'csrf_token': self.csrf_token})
180 'csrf_token': self.csrf_token})
181 assert_session_flash(response, 'Auth token successfully created')
181 assert_session_flash(response, 'Auth token successfully created')
182 response.follow()
182 response.follow()
183
183
184 # now delete our key
184 # now delete our key
185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
186 assert 3 == len(keys)
186 assert 3 == len(keys)
187
187
188 response = self.app.post(
188 response = self.app.post(
189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
190 {'del_auth_token': keys[0].user_api_key_id,
190 {'del_auth_token': keys[0].user_api_key_id,
191 'csrf_token': self.csrf_token})
191 'csrf_token': self.csrf_token})
192
192
193 assert_session_flash(response, 'Auth token successfully deleted')
193 assert_session_flash(response, 'Auth token successfully deleted')
194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
195 assert 2 == len(keys)
195 assert 2 == len(keys)
196
196
197 def test_ips(self):
197 def test_ips(self):
198 self.log_user()
198 self.log_user()
199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
201 response.mustcontain('All IP addresses are allowed')
201 response.mustcontain('All IP addresses are allowed')
202
202
203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
209 ('127_bad_ip', 'foobar', 'foobar', True),
209 ('127_bad_ip', 'foobar', 'foobar', True),
210 ])
210 ])
211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
212 self.log_user()
212 self.log_user()
213 user = user_util.create_user(username=test_name)
213 user = user_util.create_user(username=test_name)
214 user_id = user.user_id
214 user_id = user.user_id
215
215
216 response = self.app.post(
216 response = self.app.post(
217 route_path('edit_user_ips_add', user_id=user_id),
217 route_path('edit_user_ips_add', user_id=user_id),
218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
219
219
220 if failure:
220 if failure:
221 assert_session_flash(
221 assert_session_flash(
222 response, 'Please enter a valid IPv4 or IpV6 address')
222 response, 'Please enter a valid IPv4 or IpV6 address')
223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
224
224
225 response.mustcontain(no=[ip])
225 response.mustcontain(no=[ip])
226 response.mustcontain(no=[ip_range])
226 response.mustcontain(no=[ip_range])
227
227
228 else:
228 else:
229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
230 response.mustcontain(ip)
230 response.mustcontain(ip)
231 response.mustcontain(ip_range)
231 response.mustcontain(ip_range)
232
232
233 def test_ips_delete(self, user_util):
233 def test_ips_delete(self, user_util):
234 self.log_user()
234 self.log_user()
235 user = user_util.create_user()
235 user = user_util.create_user()
236 user_id = user.user_id
236 user_id = user.user_id
237 ip = '127.0.0.1/32'
237 ip = '127.0.0.1/32'
238 ip_range = '127.0.0.1 - 127.0.0.1'
238 ip_range = '127.0.0.1 - 127.0.0.1'
239 new_ip = UserModel().add_extra_ip(user_id, ip)
239 new_ip = UserModel().add_extra_ip(user_id, ip)
240 Session().commit()
240 Session().commit()
241 new_ip_id = new_ip.ip_id
241 new_ip_id = new_ip.ip_id
242
242
243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
244 response.mustcontain(ip)
244 response.mustcontain(ip)
245 response.mustcontain(ip_range)
245 response.mustcontain(ip_range)
246
246
247 self.app.post(
247 self.app.post(
248 route_path('edit_user_ips_delete', user_id=user_id),
248 route_path('edit_user_ips_delete', user_id=user_id),
249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
250
250
251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
252 response.mustcontain('All IP addresses are allowed')
252 response.mustcontain('All IP addresses are allowed')
253 response.mustcontain(no=[ip])
253 response.mustcontain(no=[ip])
254 response.mustcontain(no=[ip_range])
254 response.mustcontain(no=[ip_range])
255
255
256 def test_emails(self):
256 def test_emails(self):
257 self.log_user()
257 self.log_user()
258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
259 response = self.app.get(
259 response = self.app.get(
260 route_path('edit_user_emails', user_id=user.user_id))
260 route_path('edit_user_emails', user_id=user.user_id))
261 response.mustcontain('No additional emails specified')
261 response.mustcontain('No additional emails specified')
262
262
263 def test_emails_add(self, user_util):
263 def test_emails_add(self, user_util):
264 self.log_user()
264 self.log_user()
265 user = user_util.create_user()
265 user = user_util.create_user()
266 user_id = user.user_id
266 user_id = user.user_id
267
267
268 self.app.post(
268 self.app.post(
269 route_path('edit_user_emails_add', user_id=user_id),
269 route_path('edit_user_emails_add', user_id=user_id),
270 params={'new_email': 'example@rhodecode.com',
270 params={'new_email': 'example@rhodecode.com',
271 'csrf_token': self.csrf_token})
271 'csrf_token': self.csrf_token})
272
272
273 response = self.app.get(
273 response = self.app.get(
274 route_path('edit_user_emails', user_id=user_id))
274 route_path('edit_user_emails', user_id=user_id))
275 response.mustcontain('example@rhodecode.com')
275 response.mustcontain('example@rhodecode.com')
276
276
277 def test_emails_add_existing_email(self, user_util, user_regular):
277 def test_emails_add_existing_email(self, user_util, user_regular):
278 existing_email = user_regular.email
278 existing_email = user_regular.email
279
279
280 self.log_user()
280 self.log_user()
281 user = user_util.create_user()
281 user = user_util.create_user()
282 user_id = user.user_id
282 user_id = user.user_id
283
283
284 response = self.app.post(
284 response = self.app.post(
285 route_path('edit_user_emails_add', user_id=user_id),
285 route_path('edit_user_emails_add', user_id=user_id),
286 params={'new_email': existing_email,
286 params={'new_email': existing_email,
287 'csrf_token': self.csrf_token})
287 'csrf_token': self.csrf_token})
288 assert_session_flash(
288 assert_session_flash(
289 response, 'This e-mail address is already taken')
289 response, 'This e-mail address is already taken')
290
290
291 response = self.app.get(
291 response = self.app.get(
292 route_path('edit_user_emails', user_id=user_id))
292 route_path('edit_user_emails', user_id=user_id))
293 response.mustcontain(no=[existing_email])
293 response.mustcontain(no=[existing_email])
294
294
295 def test_emails_delete(self, user_util):
295 def test_emails_delete(self, user_util):
296 self.log_user()
296 self.log_user()
297 user = user_util.create_user()
297 user = user_util.create_user()
298 user_id = user.user_id
298 user_id = user.user_id
299
299
300 self.app.post(
300 self.app.post(
301 route_path('edit_user_emails_add', user_id=user_id),
301 route_path('edit_user_emails_add', user_id=user_id),
302 params={'new_email': 'example@rhodecode.com',
302 params={'new_email': 'example@rhodecode.com',
303 'csrf_token': self.csrf_token})
303 'csrf_token': self.csrf_token})
304
304
305 response = self.app.get(
305 response = self.app.get(
306 route_path('edit_user_emails', user_id=user_id))
306 route_path('edit_user_emails', user_id=user_id))
307 response.mustcontain('example@rhodecode.com')
307 response.mustcontain('example@rhodecode.com')
308
308
309 user_email = UserEmailMap.query()\
309 user_email = UserEmailMap.query()\
310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
311 .filter(UserEmailMap.user_id == user_id)\
311 .filter(UserEmailMap.user_id == user_id)\
312 .one()
312 .one()
313
313
314 del_email_id = user_email.email_id
314 del_email_id = user_email.email_id
315 self.app.post(
315 self.app.post(
316 route_path('edit_user_emails_delete', user_id=user_id),
316 route_path('edit_user_emails_delete', user_id=user_id),
317 params={'del_email_id': del_email_id,
317 params={'del_email_id': del_email_id,
318 'csrf_token': self.csrf_token})
318 'csrf_token': self.csrf_token})
319
319
320 response = self.app.get(
320 response = self.app.get(
321 route_path('edit_user_emails', user_id=user_id))
321 route_path('edit_user_emails', user_id=user_id))
322 response.mustcontain(no=['example@rhodecode.com'])
322 response.mustcontain(no=['example@rhodecode.com'])
323
323
324 def test_create(self, request, xhr_header):
324 def test_create(self, request, xhr_header):
325 self.log_user()
325 self.log_user()
326 username = 'newtestuser'
326 username = 'newtestuser'
327 password = 'test12'
327 password = 'test12'
328 password_confirmation = password
328 password_confirmation = password
329 name = 'name'
329 name = 'name'
330 lastname = 'lastname'
330 lastname = 'lastname'
331 email = 'mail@mail.com'
331 email = 'mail@mail.com'
332
332
333 self.app.get(route_path('users_new'))
333 self.app.get(route_path('users_new'))
334
334
335 response = self.app.post(route_path('users_create'), params={
335 response = self.app.post(route_path('users_create'), params={
336 'username': username,
336 'username': username,
337 'password': password,
337 'password': password,
338 'description': 'mr CTO',
338 'password_confirmation': password_confirmation,
339 'password_confirmation': password_confirmation,
339 'firstname': name,
340 'firstname': name,
340 'active': True,
341 'active': True,
341 'lastname': lastname,
342 'lastname': lastname,
342 'extern_name': 'rhodecode',
343 'extern_name': 'rhodecode',
343 'extern_type': 'rhodecode',
344 'extern_type': 'rhodecode',
344 'email': email,
345 'email': email,
345 'csrf_token': self.csrf_token,
346 'csrf_token': self.csrf_token,
346 })
347 })
347 user_link = h.link_to(
348 user_link = h.link_to(
348 username,
349 username,
349 route_path(
350 route_path(
350 'user_edit', user_id=User.get_by_username(username).user_id))
351 'user_edit', user_id=User.get_by_username(username).user_id))
351 assert_session_flash(response, 'Created user %s' % (user_link,))
352 assert_session_flash(response, 'Created user %s' % (user_link,))
352
353
353 @request.addfinalizer
354 @request.addfinalizer
354 def cleanup():
355 def cleanup():
355 fixture.destroy_user(username)
356 fixture.destroy_user(username)
356 Session().commit()
357 Session().commit()
357
358
358 new_user = User.query().filter(User.username == username).one()
359 new_user = User.query().filter(User.username == username).one()
359
360
360 assert new_user.username == username
361 assert new_user.username == username
361 assert auth.check_password(password, new_user.password)
362 assert auth.check_password(password, new_user.password)
362 assert new_user.name == name
363 assert new_user.name == name
363 assert new_user.lastname == lastname
364 assert new_user.lastname == lastname
364 assert new_user.email == email
365 assert new_user.email == email
365
366
366 response = self.app.get(route_path('users_data'),
367 response = self.app.get(route_path('users_data'),
367 extra_environ=xhr_header)
368 extra_environ=xhr_header)
368 response.mustcontain(username)
369 response.mustcontain(username)
369
370
370 def test_create_err(self):
371 def test_create_err(self):
371 self.log_user()
372 self.log_user()
372 username = 'new_user'
373 username = 'new_user'
373 password = ''
374 password = ''
374 name = 'name'
375 name = 'name'
375 lastname = 'lastname'
376 lastname = 'lastname'
376 email = 'errmail.com'
377 email = 'errmail.com'
377
378
378 self.app.get(route_path('users_new'))
379 self.app.get(route_path('users_new'))
379
380
380 response = self.app.post(route_path('users_create'), params={
381 response = self.app.post(route_path('users_create'), params={
381 'username': username,
382 'username': username,
382 'password': password,
383 'password': password,
383 'name': name,
384 'name': name,
384 'active': False,
385 'active': False,
385 'lastname': lastname,
386 'lastname': lastname,
387 'description': 'mr CTO',
386 'email': email,
388 'email': email,
387 'csrf_token': self.csrf_token,
389 'csrf_token': self.csrf_token,
388 })
390 })
389
391
390 msg = u'Username "%(username)s" is forbidden'
392 msg = u'Username "%(username)s" is forbidden'
391 msg = h.html_escape(msg % {'username': 'new_user'})
393 msg = h.html_escape(msg % {'username': 'new_user'})
392 response.mustcontain('<span class="error-message">%s</span>' % msg)
394 response.mustcontain('<span class="error-message">%s</span>' % msg)
393 response.mustcontain(
395 response.mustcontain(
394 '<span class="error-message">Please enter a value</span>')
396 '<span class="error-message">Please enter a value</span>')
395 response.mustcontain(
397 response.mustcontain(
396 '<span class="error-message">An email address must contain a'
398 '<span class="error-message">An email address must contain a'
397 ' single @</span>')
399 ' single @</span>')
398
400
399 def get_user():
401 def get_user():
400 Session().query(User).filter(User.username == username).one()
402 Session().query(User).filter(User.username == username).one()
401
403
402 with pytest.raises(NoResultFound):
404 with pytest.raises(NoResultFound):
403 get_user()
405 get_user()
404
406
405 def test_new(self):
407 def test_new(self):
406 self.log_user()
408 self.log_user()
407 self.app.get(route_path('users_new'))
409 self.app.get(route_path('users_new'))
408
410
409 @pytest.mark.parametrize("name, attrs", [
411 @pytest.mark.parametrize("name, attrs", [
410 ('firstname', {'firstname': 'new_username'}),
412 ('firstname', {'firstname': 'new_username'}),
411 ('lastname', {'lastname': 'new_username'}),
413 ('lastname', {'lastname': 'new_username'}),
412 ('admin', {'admin': True}),
414 ('admin', {'admin': True}),
413 ('admin', {'admin': False}),
415 ('admin', {'admin': False}),
414 ('extern_type', {'extern_type': 'ldap'}),
416 ('extern_type', {'extern_type': 'ldap'}),
415 ('extern_type', {'extern_type': None}),
417 ('extern_type', {'extern_type': None}),
416 ('extern_name', {'extern_name': 'test'}),
418 ('extern_name', {'extern_name': 'test'}),
417 ('extern_name', {'extern_name': None}),
419 ('extern_name', {'extern_name': None}),
418 ('active', {'active': False}),
420 ('active', {'active': False}),
419 ('active', {'active': True}),
421 ('active', {'active': True}),
420 ('email', {'email': 'some@email.com'}),
422 ('email', {'email': 'some@email.com'}),
421 ('language', {'language': 'de'}),
423 ('language', {'language': 'de'}),
422 ('language', {'language': 'en'}),
424 ('language', {'language': 'en'}),
425 ('description', {'description': 'hello CTO'}),
423 # ('new_password', {'new_password': 'foobar123',
426 # ('new_password', {'new_password': 'foobar123',
424 # 'password_confirmation': 'foobar123'})
427 # 'password_confirmation': 'foobar123'})
425 ])
428 ])
426 def test_update(self, name, attrs, user_util):
429 def test_update(self, name, attrs, user_util):
427 self.log_user()
430 self.log_user()
428 usr = user_util.create_user(
431 usr = user_util.create_user(
429 password='qweqwe',
432 password='qweqwe',
430 email='testme@rhodecode.org',
433 email='testme@rhodecode.org',
431 extern_type='rhodecode',
434 extern_type='rhodecode',
432 extern_name='xxx',
435 extern_name='xxx',
433 )
436 )
434 user_id = usr.user_id
437 user_id = usr.user_id
435 Session().commit()
438 Session().commit()
436
439
437 params = usr.get_api_data()
440 params = usr.get_api_data()
438 cur_lang = params['language'] or 'en'
441 cur_lang = params['language'] or 'en'
439 params.update({
442 params.update({
440 'password_confirmation': '',
443 'password_confirmation': '',
441 'new_password': '',
444 'new_password': '',
442 'language': cur_lang,
445 'language': cur_lang,
443 'csrf_token': self.csrf_token,
446 'csrf_token': self.csrf_token,
444 })
447 })
445 params.update({'new_password': ''})
448 params.update({'new_password': ''})
446 params.update(attrs)
449 params.update(attrs)
447 if name == 'email':
450 if name == 'email':
448 params['emails'] = [attrs['email']]
451 params['emails'] = [attrs['email']]
449 elif name == 'extern_type':
452 elif name == 'extern_type':
450 # cannot update this via form, expected value is original one
453 # cannot update this via form, expected value is original one
451 params['extern_type'] = "rhodecode"
454 params['extern_type'] = "rhodecode"
452 elif name == 'extern_name':
455 elif name == 'extern_name':
453 # cannot update this via form, expected value is original one
456 # cannot update this via form, expected value is original one
454 params['extern_name'] = 'xxx'
457 params['extern_name'] = 'xxx'
455 # special case since this user is not
458 # special case since this user is not
456 # logged in yet his data is not filled
459 # logged in yet his data is not filled
457 # so we use creation data
460 # so we use creation data
458
461
459 response = self.app.post(
462 response = self.app.post(
460 route_path('user_update', user_id=usr.user_id), params)
463 route_path('user_update', user_id=usr.user_id), params)
461 assert response.status_int == 302
464 assert response.status_int == 302
462 assert_session_flash(response, 'User updated successfully')
465 assert_session_flash(response, 'User updated successfully')
463
466
464 updated_user = User.get(user_id)
467 updated_user = User.get(user_id)
465 updated_params = updated_user.get_api_data()
468 updated_params = updated_user.get_api_data()
466 updated_params.update({'password_confirmation': ''})
469 updated_params.update({'password_confirmation': ''})
467 updated_params.update({'new_password': ''})
470 updated_params.update({'new_password': ''})
468
471
469 del params['csrf_token']
472 del params['csrf_token']
470 assert params == updated_params
473 assert params == updated_params
471
474
472 def test_update_and_migrate_password(
475 def test_update_and_migrate_password(
473 self, autologin_user, real_crypto_backend, user_util):
476 self, autologin_user, real_crypto_backend, user_util):
474
477
475 user = user_util.create_user()
478 user = user_util.create_user()
476 temp_user = user.username
479 temp_user = user.username
477 user.password = auth._RhodeCodeCryptoSha256().hash_create(
480 user.password = auth._RhodeCodeCryptoSha256().hash_create(
478 b'test123')
481 b'test123')
479 Session().add(user)
482 Session().add(user)
480 Session().commit()
483 Session().commit()
481
484
482 params = user.get_api_data()
485 params = user.get_api_data()
483
486
484 params.update({
487 params.update({
485 'password_confirmation': 'qweqwe123',
488 'password_confirmation': 'qweqwe123',
486 'new_password': 'qweqwe123',
489 'new_password': 'qweqwe123',
487 'language': 'en',
490 'language': 'en',
488 'csrf_token': autologin_user.csrf_token,
491 'csrf_token': autologin_user.csrf_token,
489 })
492 })
490
493
491 response = self.app.post(
494 response = self.app.post(
492 route_path('user_update', user_id=user.user_id), params)
495 route_path('user_update', user_id=user.user_id), params)
493 assert response.status_int == 302
496 assert response.status_int == 302
494 assert_session_flash(response, 'User updated successfully')
497 assert_session_flash(response, 'User updated successfully')
495
498
496 # new password should be bcrypted, after log-in and transfer
499 # new password should be bcrypted, after log-in and transfer
497 user = User.get_by_username(temp_user)
500 user = User.get_by_username(temp_user)
498 assert user.password.startswith('$')
501 assert user.password.startswith('$')
499
502
500 updated_user = User.get_by_username(temp_user)
503 updated_user = User.get_by_username(temp_user)
501 updated_params = updated_user.get_api_data()
504 updated_params = updated_user.get_api_data()
502 updated_params.update({'password_confirmation': 'qweqwe123'})
505 updated_params.update({'password_confirmation': 'qweqwe123'})
503 updated_params.update({'new_password': 'qweqwe123'})
506 updated_params.update({'new_password': 'qweqwe123'})
504
507
505 del params['csrf_token']
508 del params['csrf_token']
506 assert params == updated_params
509 assert params == updated_params
507
510
508 def test_delete(self):
511 def test_delete(self):
509 self.log_user()
512 self.log_user()
510 username = 'newtestuserdeleteme'
513 username = 'newtestuserdeleteme'
511
514
512 fixture.create_user(name=username)
515 fixture.create_user(name=username)
513
516
514 new_user = Session().query(User)\
517 new_user = Session().query(User)\
515 .filter(User.username == username).one()
518 .filter(User.username == username).one()
516 response = self.app.post(
519 response = self.app.post(
517 route_path('user_delete', user_id=new_user.user_id),
520 route_path('user_delete', user_id=new_user.user_id),
518 params={'csrf_token': self.csrf_token})
521 params={'csrf_token': self.csrf_token})
519
522
520 assert_session_flash(response, 'Successfully deleted user `{}`'.format(username))
523 assert_session_flash(response, 'Successfully deleted user `{}`'.format(username))
521
524
522 def test_delete_owner_of_repository(self, request, user_util):
525 def test_delete_owner_of_repository(self, request, user_util):
523 self.log_user()
526 self.log_user()
524 obj_name = 'test_repo'
527 obj_name = 'test_repo'
525 usr = user_util.create_user()
528 usr = user_util.create_user()
526 username = usr.username
529 username = usr.username
527 fixture.create_repo(obj_name, cur_user=usr.username)
530 fixture.create_repo(obj_name, cur_user=usr.username)
528
531
529 new_user = Session().query(User)\
532 new_user = Session().query(User)\
530 .filter(User.username == username).one()
533 .filter(User.username == username).one()
531 response = self.app.post(
534 response = self.app.post(
532 route_path('user_delete', user_id=new_user.user_id),
535 route_path('user_delete', user_id=new_user.user_id),
533 params={'csrf_token': self.csrf_token})
536 params={'csrf_token': self.csrf_token})
534
537
535 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
538 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
536 'Switch owners or remove those repositories:%s' % (username, obj_name)
539 'Switch owners or remove those repositories:%s' % (username, obj_name)
537 assert_session_flash(response, msg)
540 assert_session_flash(response, msg)
538 fixture.destroy_repo(obj_name)
541 fixture.destroy_repo(obj_name)
539
542
540 def test_delete_owner_of_repository_detaching(self, request, user_util):
543 def test_delete_owner_of_repository_detaching(self, request, user_util):
541 self.log_user()
544 self.log_user()
542 obj_name = 'test_repo'
545 obj_name = 'test_repo'
543 usr = user_util.create_user(auto_cleanup=False)
546 usr = user_util.create_user(auto_cleanup=False)
544 username = usr.username
547 username = usr.username
545 fixture.create_repo(obj_name, cur_user=usr.username)
548 fixture.create_repo(obj_name, cur_user=usr.username)
546
549
547 new_user = Session().query(User)\
550 new_user = Session().query(User)\
548 .filter(User.username == username).one()
551 .filter(User.username == username).one()
549 response = self.app.post(
552 response = self.app.post(
550 route_path('user_delete', user_id=new_user.user_id),
553 route_path('user_delete', user_id=new_user.user_id),
551 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
554 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
552
555
553 msg = 'Detached 1 repositories'
556 msg = 'Detached 1 repositories'
554 assert_session_flash(response, msg)
557 assert_session_flash(response, msg)
555 fixture.destroy_repo(obj_name)
558 fixture.destroy_repo(obj_name)
556
559
557 def test_delete_owner_of_repository_deleting(self, request, user_util):
560 def test_delete_owner_of_repository_deleting(self, request, user_util):
558 self.log_user()
561 self.log_user()
559 obj_name = 'test_repo'
562 obj_name = 'test_repo'
560 usr = user_util.create_user(auto_cleanup=False)
563 usr = user_util.create_user(auto_cleanup=False)
561 username = usr.username
564 username = usr.username
562 fixture.create_repo(obj_name, cur_user=usr.username)
565 fixture.create_repo(obj_name, cur_user=usr.username)
563
566
564 new_user = Session().query(User)\
567 new_user = Session().query(User)\
565 .filter(User.username == username).one()
568 .filter(User.username == username).one()
566 response = self.app.post(
569 response = self.app.post(
567 route_path('user_delete', user_id=new_user.user_id),
570 route_path('user_delete', user_id=new_user.user_id),
568 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
571 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
569
572
570 msg = 'Deleted 1 repositories'
573 msg = 'Deleted 1 repositories'
571 assert_session_flash(response, msg)
574 assert_session_flash(response, msg)
572
575
573 def test_delete_owner_of_repository_group(self, request, user_util):
576 def test_delete_owner_of_repository_group(self, request, user_util):
574 self.log_user()
577 self.log_user()
575 obj_name = 'test_group'
578 obj_name = 'test_group'
576 usr = user_util.create_user()
579 usr = user_util.create_user()
577 username = usr.username
580 username = usr.username
578 fixture.create_repo_group(obj_name, cur_user=usr.username)
581 fixture.create_repo_group(obj_name, cur_user=usr.username)
579
582
580 new_user = Session().query(User)\
583 new_user = Session().query(User)\
581 .filter(User.username == username).one()
584 .filter(User.username == username).one()
582 response = self.app.post(
585 response = self.app.post(
583 route_path('user_delete', user_id=new_user.user_id),
586 route_path('user_delete', user_id=new_user.user_id),
584 params={'csrf_token': self.csrf_token})
587 params={'csrf_token': self.csrf_token})
585
588
586 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
589 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
587 'Switch owners or remove those repository groups:%s' % (username, obj_name)
590 'Switch owners or remove those repository groups:%s' % (username, obj_name)
588 assert_session_flash(response, msg)
591 assert_session_flash(response, msg)
589 fixture.destroy_repo_group(obj_name)
592 fixture.destroy_repo_group(obj_name)
590
593
591 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
594 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
592 self.log_user()
595 self.log_user()
593 obj_name = 'test_group'
596 obj_name = 'test_group'
594 usr = user_util.create_user(auto_cleanup=False)
597 usr = user_util.create_user(auto_cleanup=False)
595 username = usr.username
598 username = usr.username
596 fixture.create_repo_group(obj_name, cur_user=usr.username)
599 fixture.create_repo_group(obj_name, cur_user=usr.username)
597
600
598 new_user = Session().query(User)\
601 new_user = Session().query(User)\
599 .filter(User.username == username).one()
602 .filter(User.username == username).one()
600 response = self.app.post(
603 response = self.app.post(
601 route_path('user_delete', user_id=new_user.user_id),
604 route_path('user_delete', user_id=new_user.user_id),
602 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
605 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
603
606
604 msg = 'Deleted 1 repository groups'
607 msg = 'Deleted 1 repository groups'
605 assert_session_flash(response, msg)
608 assert_session_flash(response, msg)
606
609
607 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
610 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
608 self.log_user()
611 self.log_user()
609 obj_name = 'test_group'
612 obj_name = 'test_group'
610 usr = user_util.create_user(auto_cleanup=False)
613 usr = user_util.create_user(auto_cleanup=False)
611 username = usr.username
614 username = usr.username
612 fixture.create_repo_group(obj_name, cur_user=usr.username)
615 fixture.create_repo_group(obj_name, cur_user=usr.username)
613
616
614 new_user = Session().query(User)\
617 new_user = Session().query(User)\
615 .filter(User.username == username).one()
618 .filter(User.username == username).one()
616 response = self.app.post(
619 response = self.app.post(
617 route_path('user_delete', user_id=new_user.user_id),
620 route_path('user_delete', user_id=new_user.user_id),
618 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
621 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
619
622
620 msg = 'Detached 1 repository groups'
623 msg = 'Detached 1 repository groups'
621 assert_session_flash(response, msg)
624 assert_session_flash(response, msg)
622 fixture.destroy_repo_group(obj_name)
625 fixture.destroy_repo_group(obj_name)
623
626
624 def test_delete_owner_of_user_group(self, request, user_util):
627 def test_delete_owner_of_user_group(self, request, user_util):
625 self.log_user()
628 self.log_user()
626 obj_name = 'test_user_group'
629 obj_name = 'test_user_group'
627 usr = user_util.create_user()
630 usr = user_util.create_user()
628 username = usr.username
631 username = usr.username
629 fixture.create_user_group(obj_name, cur_user=usr.username)
632 fixture.create_user_group(obj_name, cur_user=usr.username)
630
633
631 new_user = Session().query(User)\
634 new_user = Session().query(User)\
632 .filter(User.username == username).one()
635 .filter(User.username == username).one()
633 response = self.app.post(
636 response = self.app.post(
634 route_path('user_delete', user_id=new_user.user_id),
637 route_path('user_delete', user_id=new_user.user_id),
635 params={'csrf_token': self.csrf_token})
638 params={'csrf_token': self.csrf_token})
636
639
637 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
640 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
638 'Switch owners or remove those user groups:%s' % (username, obj_name)
641 'Switch owners or remove those user groups:%s' % (username, obj_name)
639 assert_session_flash(response, msg)
642 assert_session_flash(response, msg)
640 fixture.destroy_user_group(obj_name)
643 fixture.destroy_user_group(obj_name)
641
644
642 def test_delete_owner_of_user_group_detaching(self, request, user_util):
645 def test_delete_owner_of_user_group_detaching(self, request, user_util):
643 self.log_user()
646 self.log_user()
644 obj_name = 'test_user_group'
647 obj_name = 'test_user_group'
645 usr = user_util.create_user(auto_cleanup=False)
648 usr = user_util.create_user(auto_cleanup=False)
646 username = usr.username
649 username = usr.username
647 fixture.create_user_group(obj_name, cur_user=usr.username)
650 fixture.create_user_group(obj_name, cur_user=usr.username)
648
651
649 new_user = Session().query(User)\
652 new_user = Session().query(User)\
650 .filter(User.username == username).one()
653 .filter(User.username == username).one()
651 try:
654 try:
652 response = self.app.post(
655 response = self.app.post(
653 route_path('user_delete', user_id=new_user.user_id),
656 route_path('user_delete', user_id=new_user.user_id),
654 params={'user_user_groups': 'detach',
657 params={'user_user_groups': 'detach',
655 'csrf_token': self.csrf_token})
658 'csrf_token': self.csrf_token})
656
659
657 msg = 'Detached 1 user groups'
660 msg = 'Detached 1 user groups'
658 assert_session_flash(response, msg)
661 assert_session_flash(response, msg)
659 finally:
662 finally:
660 fixture.destroy_user_group(obj_name)
663 fixture.destroy_user_group(obj_name)
661
664
662 def test_delete_owner_of_user_group_deleting(self, request, user_util):
665 def test_delete_owner_of_user_group_deleting(self, request, user_util):
663 self.log_user()
666 self.log_user()
664 obj_name = 'test_user_group'
667 obj_name = 'test_user_group'
665 usr = user_util.create_user(auto_cleanup=False)
668 usr = user_util.create_user(auto_cleanup=False)
666 username = usr.username
669 username = usr.username
667 fixture.create_user_group(obj_name, cur_user=usr.username)
670 fixture.create_user_group(obj_name, cur_user=usr.username)
668
671
669 new_user = Session().query(User)\
672 new_user = Session().query(User)\
670 .filter(User.username == username).one()
673 .filter(User.username == username).one()
671 response = self.app.post(
674 response = self.app.post(
672 route_path('user_delete', user_id=new_user.user_id),
675 route_path('user_delete', user_id=new_user.user_id),
673 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
676 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
674
677
675 msg = 'Deleted 1 user groups'
678 msg = 'Deleted 1 user groups'
676 assert_session_flash(response, msg)
679 assert_session_flash(response, msg)
677
680
678 def test_edit(self, user_util):
681 def test_edit(self, user_util):
679 self.log_user()
682 self.log_user()
680 user = user_util.create_user()
683 user = user_util.create_user()
681 self.app.get(route_path('user_edit', user_id=user.user_id))
684 self.app.get(route_path('user_edit', user_id=user.user_id))
682
685
683 def test_edit_default_user_redirect(self):
686 def test_edit_default_user_redirect(self):
684 self.log_user()
687 self.log_user()
685 user = User.get_default_user()
688 user = User.get_default_user()
686 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
689 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
687
690
688 @pytest.mark.parametrize(
691 @pytest.mark.parametrize(
689 'repo_create, repo_create_write, user_group_create, repo_group_create,'
692 'repo_create, repo_create_write, user_group_create, repo_group_create,'
690 'fork_create, inherit_default_permissions, expect_error,'
693 'fork_create, inherit_default_permissions, expect_error,'
691 'expect_form_error', [
694 'expect_form_error', [
692 ('hg.create.none', 'hg.create.write_on_repogroup.false',
695 ('hg.create.none', 'hg.create.write_on_repogroup.false',
693 'hg.usergroup.create.false', 'hg.repogroup.create.false',
696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
694 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
695 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
698 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
699 'hg.usergroup.create.false', 'hg.repogroup.create.false',
697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
700 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
698 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
701 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
699 'hg.usergroup.create.true', 'hg.repogroup.create.true',
702 'hg.usergroup.create.true', 'hg.repogroup.create.true',
700 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
703 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
701 False),
704 False),
702 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
705 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
703 'hg.usergroup.create.true', 'hg.repogroup.create.true',
706 'hg.usergroup.create.true', 'hg.repogroup.create.true',
704 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
707 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
705 True),
708 True),
706 ('', '', '', '', '', '', True, False),
709 ('', '', '', '', '', '', True, False),
707 ])
710 ])
708 def test_global_perms_on_user(
711 def test_global_perms_on_user(
709 self, repo_create, repo_create_write, user_group_create,
712 self, repo_create, repo_create_write, user_group_create,
710 repo_group_create, fork_create, expect_error, expect_form_error,
713 repo_group_create, fork_create, expect_error, expect_form_error,
711 inherit_default_permissions, user_util):
714 inherit_default_permissions, user_util):
712 self.log_user()
715 self.log_user()
713 user = user_util.create_user()
716 user = user_util.create_user()
714 uid = user.user_id
717 uid = user.user_id
715
718
716 # ENABLE REPO CREATE ON A GROUP
719 # ENABLE REPO CREATE ON A GROUP
717 perm_params = {
720 perm_params = {
718 'inherit_default_permissions': False,
721 'inherit_default_permissions': False,
719 'default_repo_create': repo_create,
722 'default_repo_create': repo_create,
720 'default_repo_create_on_write': repo_create_write,
723 'default_repo_create_on_write': repo_create_write,
721 'default_user_group_create': user_group_create,
724 'default_user_group_create': user_group_create,
722 'default_repo_group_create': repo_group_create,
725 'default_repo_group_create': repo_group_create,
723 'default_fork_create': fork_create,
726 'default_fork_create': fork_create,
724 'default_inherit_default_permissions': inherit_default_permissions,
727 'default_inherit_default_permissions': inherit_default_permissions,
725 'csrf_token': self.csrf_token,
728 'csrf_token': self.csrf_token,
726 }
729 }
727 response = self.app.post(
730 response = self.app.post(
728 route_path('user_edit_global_perms_update', user_id=uid),
731 route_path('user_edit_global_perms_update', user_id=uid),
729 params=perm_params)
732 params=perm_params)
730
733
731 if expect_form_error:
734 if expect_form_error:
732 assert response.status_int == 200
735 assert response.status_int == 200
733 response.mustcontain('Value must be one of')
736 response.mustcontain('Value must be one of')
734 else:
737 else:
735 if expect_error:
738 if expect_error:
736 msg = 'An error occurred during permissions saving'
739 msg = 'An error occurred during permissions saving'
737 else:
740 else:
738 msg = 'User global permissions updated successfully'
741 msg = 'User global permissions updated successfully'
739 ug = User.get(uid)
742 ug = User.get(uid)
740 del perm_params['inherit_default_permissions']
743 del perm_params['inherit_default_permissions']
741 del perm_params['csrf_token']
744 del perm_params['csrf_token']
742 assert perm_params == ug.get_default_perms()
745 assert perm_params == ug.get_default_perms()
743 assert_session_flash(response, msg)
746 assert_session_flash(response, msg)
744
747
745 def test_global_permissions_initial_values(self, user_util):
748 def test_global_permissions_initial_values(self, user_util):
746 self.log_user()
749 self.log_user()
747 user = user_util.create_user()
750 user = user_util.create_user()
748 uid = user.user_id
751 uid = user.user_id
749 response = self.app.get(
752 response = self.app.get(
750 route_path('user_edit_global_perms', user_id=uid))
753 route_path('user_edit_global_perms', user_id=uid))
751 default_user = User.get_default_user()
754 default_user = User.get_default_user()
752 default_permissions = default_user.get_default_perms()
755 default_permissions = default_user.get_default_perms()
753 assert_response = response.assert_response()
756 assert_response = response.assert_response()
754 expected_permissions = (
757 expected_permissions = (
755 'default_repo_create', 'default_repo_create_on_write',
758 'default_repo_create', 'default_repo_create_on_write',
756 'default_fork_create', 'default_repo_group_create',
759 'default_fork_create', 'default_repo_group_create',
757 'default_user_group_create', 'default_inherit_default_permissions')
760 'default_user_group_create', 'default_inherit_default_permissions')
758 for permission in expected_permissions:
761 for permission in expected_permissions:
759 css_selector = '[name={}][checked=checked]'.format(permission)
762 css_selector = '[name={}][checked=checked]'.format(permission)
760 element = assert_response.get_element(css_selector)
763 element = assert_response.get_element(css_selector)
761 assert element.value == default_permissions[permission]
764 assert element.value == default_permissions[permission]
762
765
763 def test_perms_summary_page(self):
766 def test_perms_summary_page(self):
764 user = self.log_user()
767 user = self.log_user()
765 response = self.app.get(
768 response = self.app.get(
766 route_path('edit_user_perms_summary', user_id=user['user_id']))
769 route_path('edit_user_perms_summary', user_id=user['user_id']))
767 for repo in Repository.query().all():
770 for repo in Repository.query().all():
768 response.mustcontain(repo.repo_name)
771 response.mustcontain(repo.repo_name)
769
772
770 def test_perms_summary_page_json(self):
773 def test_perms_summary_page_json(self):
771 user = self.log_user()
774 user = self.log_user()
772 response = self.app.get(
775 response = self.app.get(
773 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
776 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
774 for repo in Repository.query().all():
777 for repo in Repository.query().all():
775 response.mustcontain(repo.repo_name)
778 response.mustcontain(repo.repo_name)
776
779
777 def test_audit_log_page(self):
780 def test_audit_log_page(self):
778 user = self.log_user()
781 user = self.log_user()
779 self.app.get(
782 self.app.get(
780 route_path('edit_user_audit_logs', user_id=user['user_id']))
783 route_path('edit_user_audit_logs', user_id=user['user_id']))
781
784
782 def test_audit_log_page_download(self):
785 def test_audit_log_page_download(self):
783 user = self.log_user()
786 user = self.log_user()
784 user_id = user['user_id']
787 user_id = user['user_id']
785 response = self.app.get(
788 response = self.app.get(
786 route_path('edit_user_audit_logs_download', user_id=user_id))
789 route_path('edit_user_audit_logs_download', user_id=user_id))
787
790
788 assert response.content_disposition == \
791 assert response.content_disposition == \
789 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
792 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
790 assert response.content_type == "application/json"
793 assert response.content_type == "application/json"
@@ -1,5445 +1,5446 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, TypeDecorator, event,
40 or_, and_, not_, func, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers.text import collapse, remove_formatting
55 from webhelpers.text import collapse, remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
588
589 user_log = relationship('UserLog')
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
591
592 repositories = relationship('Repository')
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
594 user_groups = relationship('UserGroup')
595
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
602
603 group_member = relationship('UserGroupMember', cascade='all')
603 group_member = relationship('UserGroupMember', cascade='all')
604
604
605 notifications = relationship('UserNotification', cascade='all')
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
615
616 # gists
616 # gists
617 user_gists = relationship('Gist', cascade='all')
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
619 user_pull_requests = relationship('PullRequest', cascade='all')
620 # external identities
620 # external identities
621 external_identities = relationship(
621 external_identities = relationship(
622 'ExternalIdentity',
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
624 cascade='all')
625 # review rules
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
627
628 # artifacts owned
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
630
631 # no cascade, set NULL
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
633
634 def __unicode__(self):
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
636 self.user_id, self.username)
637
637
638 @hybrid_property
638 @hybrid_property
639 def email(self):
639 def email(self):
640 return self._email
640 return self._email
641
641
642 @email.setter
642 @email.setter
643 def email(self, val):
643 def email(self, val):
644 self._email = val.lower() if val else None
644 self._email = val.lower() if val else None
645
645
646 @hybrid_property
646 @hybrid_property
647 def first_name(self):
647 def first_name(self):
648 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
649 if self.name:
649 if self.name:
650 return h.escape(self.name)
650 return h.escape(self.name)
651 return self.name
651 return self.name
652
652
653 @hybrid_property
653 @hybrid_property
654 def last_name(self):
654 def last_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.lastname:
656 if self.lastname:
657 return h.escape(self.lastname)
657 return h.escape(self.lastname)
658 return self.lastname
658 return self.lastname
659
659
660 @hybrid_property
660 @hybrid_property
661 def api_key(self):
661 def api_key(self):
662 """
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
664 """
665 user_auth_token = UserApiKeys.query()\
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
671 user_auth_token = user_auth_token.api_key
672
672
673 return user_auth_token
673 return user_auth_token
674
674
675 @api_key.setter
675 @api_key.setter
676 def api_key(self, val):
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
678 self._api_key = None
679
679
680 @property
680 @property
681 def reviewer_pull_requests(self):
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
685 .all()
686
686
687 @property
687 @property
688 def firstname(self):
688 def firstname(self):
689 # alias for future
689 # alias for future
690 return self.name
690 return self.name
691
691
692 @property
692 @property
693 def emails(self):
693 def emails(self):
694 other = UserEmailMap.query()\
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
697 .all()
698 return [self.email] + [x.email for x in other]
698 return [self.email] + [x.email for x in other]
699
699
700 def emails_cached(self):
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
703 .order_by(UserEmailMap.email_id.asc())
704
704
705 emails = emails.options(
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
707 )
708
708
709 return [self.email] + [x.email for x in emails]
709 return [self.email] + [x.email for x in emails]
710
710
711 @property
711 @property
712 def auth_tokens(self):
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
714 return [x.api_key for x in auth_tokens]
715
715
716 def get_auth_tokens(self):
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
720 .all()
721
721
722 @LazyProperty
722 @LazyProperty
723 def feed_token(self):
723 def feed_token(self):
724 return self.get_feed_token()
724 return self.get_feed_token()
725
725
726 def get_feed_token(self, cache=True):
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
730 if cache:
731 feed_tokens = feed_tokens.options(
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
733
734 feed_tokens = feed_tokens.all()
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
735 if feed_tokens:
736 return feed_tokens[0].api_key
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
738
739 @LazyProperty
739 @LazyProperty
740 def artifact_token(self):
740 def artifact_token(self):
741 return self.get_artifact_token()
741 return self.get_artifact_token()
742
742
743 def get_artifact_token(self, cache=True):
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
750
751 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
755
756 @classmethod
756 @classmethod
757 def get(cls, user_id, cache=False):
757 def get(cls, user_id, cache=False):
758 if not user_id:
758 if not user_id:
759 return
759 return
760
760
761 user = cls.query()
761 user = cls.query()
762 if cache:
762 if cache:
763 user = user.options(
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
765 return user.get(user_id)
766
766
767 @classmethod
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
771 UserApiKeys.expires >= time.time()))
772 if role:
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
775 return tokens.all()
776
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
778 from rhodecode.lib import auth
779
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
781 'and roles: %s', self, roles)
782
782
783 if not auth_token:
783 if not auth_token:
784 return False
784 return False
785
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
793
794 crypto_backend = auth.crypto_backend()
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
795 enc_token_map = {}
796 plain_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
799 enc_token_map[token.api_key] = token
800 else:
800 else:
801 plain_token_map[token.api_key] = token
801 plain_token_map[token.api_key] = token
802 log.debug(
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
804 len(plain_token_map), len(enc_token_map))
805
805
806 # plain token match comes first
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
807 match = plain_token_map.get(auth_token)
808
808
809 # check encrypted tokens now
809 # check encrypted tokens now
810 if not match:
810 if not match:
811 for token_hash, token in enc_token_map.items():
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
814 match = token
815 break
815 break
816
816
817 if match:
817 if match:
818 log.debug('Found matching token %s', match)
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
821 if match.repo_id == scope_repo_id:
822 return True
822 return True
823 else:
823 else:
824 log.debug(
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
827 match.repo, scope_repo_id)
828 return False
828 return False
829 else:
829 else:
830 return True
830 return True
831
831
832 return False
832 return False
833
833
834 @property
834 @property
835 def ip_addresses(self):
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
837 return [x.ip_addr for x in ret]
838
838
839 @property
839 @property
840 def username_and_name(self):
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
842
843 @property
843 @property
844 def username_or_name_or_email(self):
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
846 return self.username or full_name or self.email
847
847
848 @property
848 @property
849 def full_name(self):
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
850 return '%s %s' % (self.first_name, self.last_name)
851
851
852 @property
852 @property
853 def full_name_or_username(self):
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
855 if (self.first_name and self.last_name) else self.username)
856
856
857 @property
857 @property
858 def full_contact(self):
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
860
861 @property
861 @property
862 def short_contact(self):
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
863 return '%s %s' % (self.first_name, self.last_name)
864
864
865 @property
865 @property
866 def is_admin(self):
866 def is_admin(self):
867 return self.admin
867 return self.admin
868
868
869 @property
869 @property
870 def language(self):
870 def language(self):
871 return self.user_data.get('language')
871 return self.user_data.get('language')
872
872
873 def AuthUser(self, **kwargs):
873 def AuthUser(self, **kwargs):
874 """
874 """
875 Returns instance of AuthUser for this user
875 Returns instance of AuthUser for this user
876 """
876 """
877 from rhodecode.lib.auth import AuthUser
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
879
880 @hybrid_property
880 @hybrid_property
881 def user_data(self):
881 def user_data(self):
882 if not self._user_data:
882 if not self._user_data:
883 return {}
883 return {}
884
884
885 try:
885 try:
886 return json.loads(self._user_data)
886 return json.loads(self._user_data)
887 except TypeError:
887 except TypeError:
888 return {}
888 return {}
889
889
890 @user_data.setter
890 @user_data.setter
891 def user_data(self, val):
891 def user_data(self, val):
892 if not isinstance(val, dict):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
894 try:
895 self._user_data = json.dumps(val)
895 self._user_data = json.dumps(val)
896 except Exception:
896 except Exception:
897 log.error(traceback.format_exc())
897 log.error(traceback.format_exc())
898
898
899 @classmethod
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
901 cache=False, identity_cache=False):
902 session = Session()
902 session = Session()
903
903
904 if case_insensitive:
904 if case_insensitive:
905 q = cls.query().filter(
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
906 func.lower(cls.username) == func.lower(username))
907 else:
907 else:
908 q = cls.query().filter(cls.username == username)
908 q = cls.query().filter(cls.username == username)
909
909
910 if cache:
910 if cache:
911 if identity_cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
912 val = cls.identity_cache(session, 'username', username)
913 if val:
913 if val:
914 return val
914 return val
915 else:
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
918 FromCache("sql_cache_short", cache_key))
919
919
920 return q.scalar()
920 return q.scalar()
921
921
922 @classmethod
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
927 UserApiKeys.expires >= time.time()))
928 if cache:
928 if cache:
929 q = q.options(
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
931
932 match = q.first()
932 match = q.first()
933 if match:
933 if match:
934 return match.user
934 return match.user
935
935
936 @classmethod
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
938
939 if case_insensitive:
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
941
942 else:
942 else:
943 q = cls.query().filter(cls.email == email)
943 q = cls.query().filter(cls.email == email)
944
944
945 email_key = _hash_key(email)
945 email_key = _hash_key(email)
946 if cache:
946 if cache:
947 q = q.options(
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
949
950 ret = q.scalar()
950 ret = q.scalar()
951 if ret is None:
951 if ret is None:
952 q = UserEmailMap.query()
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
953 # try fetching in alternate email map
954 if case_insensitive:
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
956 else:
957 q = q.filter(UserEmailMap.email == email)
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
959 if cache:
960 q = q.options(
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
962 ret = getattr(q.scalar(), 'user', None)
963
963
964 return ret
964 return ret
965
965
966 @classmethod
966 @classmethod
967 def get_from_cs_author(cls, author):
967 def get_from_cs_author(cls, author):
968 """
968 """
969 Tries to get User objects out of commit author string
969 Tries to get User objects out of commit author string
970
970
971 :param author:
971 :param author:
972 """
972 """
973 from rhodecode.lib.helpers import email, author_name
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
975 _email = email(author)
976 if _email:
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
978 if user:
979 return user
979 return user
980 # Maybe we can match by username?
980 # Maybe we can match by username?
981 _author = author_name(author)
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
983 if user:
984 return user
984 return user
985
985
986 def update_userdata(self, **kwargs):
986 def update_userdata(self, **kwargs):
987 usr = self
987 usr = self
988 old = usr.user_data
988 old = usr.user_data
989 old.update(**kwargs)
989 old.update(**kwargs)
990 usr.user_data = old
990 usr.user_data = old
991 Session().add(usr)
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
992 log.debug('updated userdata with %s', kwargs)
993
993
994 def update_lastlogin(self):
994 def update_lastlogin(self):
995 """Update user lastlogin"""
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
998 log.debug('updated user %s lastlogin', self.username)
999
999
1000 def update_password(self, new_password):
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1001 from rhodecode.lib.auth import get_crypt_password
1002
1002
1003 self.password = get_crypt_password(new_password)
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1004 Session().add(self)
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_first_super_admin(cls):
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1011 .first()
1012
1012
1013 if user is None:
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1015 return user
1016
1016
1017 @classmethod
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1019 """
1020 Returns all admin accounts sorted by username
1020 Returns all admin accounts sorted by username
1021 """
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1025 return qry.all()
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_default_user(cls, cache=False, refresh=False):
1028 def get_default_user(cls, cache=False, refresh=False):
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1030 if user is None:
1030 if user is None:
1031 raise Exception('FATAL: Missing default account!')
1031 raise Exception('FATAL: Missing default account!')
1032 if refresh:
1032 if refresh:
1033 # The default user might be based on outdated state which
1033 # The default user might be based on outdated state which
1034 # has been loaded from the cache.
1034 # has been loaded from the cache.
1035 # A call to refresh() ensures that the
1035 # A call to refresh() ensures that the
1036 # latest state from the database is used.
1036 # latest state from the database is used.
1037 Session().refresh(user)
1037 Session().refresh(user)
1038 return user
1038 return user
1039
1039
1040 def _get_default_perms(self, user, suffix=''):
1040 def _get_default_perms(self, user, suffix=''):
1041 from rhodecode.model.permission import PermissionModel
1041 from rhodecode.model.permission import PermissionModel
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1043
1043
1044 def get_default_perms(self, suffix=''):
1044 def get_default_perms(self, suffix=''):
1045 return self._get_default_perms(self, suffix)
1045 return self._get_default_perms(self, suffix)
1046
1046
1047 def get_api_data(self, include_secrets=False, details='full'):
1047 def get_api_data(self, include_secrets=False, details='full'):
1048 """
1048 """
1049 Common function for generating user related data for API
1049 Common function for generating user related data for API
1050
1050
1051 :param include_secrets: By default secrets in the API data will be replaced
1051 :param include_secrets: By default secrets in the API data will be replaced
1052 by a placeholder value to prevent exposing this data by accident. In case
1052 by a placeholder value to prevent exposing this data by accident. In case
1053 this data shall be exposed, set this flag to ``True``.
1053 this data shall be exposed, set this flag to ``True``.
1054
1054
1055 :param details: details can be 'basic|full' basic gives only a subset of
1055 :param details: details can be 'basic|full' basic gives only a subset of
1056 the available user information that includes user_id, name and emails.
1056 the available user information that includes user_id, name and emails.
1057 """
1057 """
1058 user = self
1058 user = self
1059 user_data = self.user_data
1059 user_data = self.user_data
1060 data = {
1060 data = {
1061 'user_id': user.user_id,
1061 'user_id': user.user_id,
1062 'username': user.username,
1062 'username': user.username,
1063 'firstname': user.name,
1063 'firstname': user.name,
1064 'lastname': user.lastname,
1064 'lastname': user.lastname,
1065 'description': user.description,
1065 'email': user.email,
1066 'email': user.email,
1066 'emails': user.emails,
1067 'emails': user.emails,
1067 }
1068 }
1068 if details == 'basic':
1069 if details == 'basic':
1069 return data
1070 return data
1070
1071
1071 auth_token_length = 40
1072 auth_token_length = 40
1072 auth_token_replacement = '*' * auth_token_length
1073 auth_token_replacement = '*' * auth_token_length
1073
1074
1074 extras = {
1075 extras = {
1075 'auth_tokens': [auth_token_replacement],
1076 'auth_tokens': [auth_token_replacement],
1076 'active': user.active,
1077 'active': user.active,
1077 'admin': user.admin,
1078 'admin': user.admin,
1078 'extern_type': user.extern_type,
1079 'extern_type': user.extern_type,
1079 'extern_name': user.extern_name,
1080 'extern_name': user.extern_name,
1080 'last_login': user.last_login,
1081 'last_login': user.last_login,
1081 'last_activity': user.last_activity,
1082 'last_activity': user.last_activity,
1082 'ip_addresses': user.ip_addresses,
1083 'ip_addresses': user.ip_addresses,
1083 'language': user_data.get('language')
1084 'language': user_data.get('language')
1084 }
1085 }
1085 data.update(extras)
1086 data.update(extras)
1086
1087
1087 if include_secrets:
1088 if include_secrets:
1088 data['auth_tokens'] = user.auth_tokens
1089 data['auth_tokens'] = user.auth_tokens
1089 return data
1090 return data
1090
1091
1091 def __json__(self):
1092 def __json__(self):
1092 data = {
1093 data = {
1093 'full_name': self.full_name,
1094 'full_name': self.full_name,
1094 'full_name_or_username': self.full_name_or_username,
1095 'full_name_or_username': self.full_name_or_username,
1095 'short_contact': self.short_contact,
1096 'short_contact': self.short_contact,
1096 'full_contact': self.full_contact,
1097 'full_contact': self.full_contact,
1097 }
1098 }
1098 data.update(self.get_api_data())
1099 data.update(self.get_api_data())
1099 return data
1100 return data
1100
1101
1101
1102
1102 class UserApiKeys(Base, BaseModel):
1103 class UserApiKeys(Base, BaseModel):
1103 __tablename__ = 'user_api_keys'
1104 __tablename__ = 'user_api_keys'
1104 __table_args__ = (
1105 __table_args__ = (
1105 Index('uak_api_key_idx', 'api_key'),
1106 Index('uak_api_key_idx', 'api_key'),
1106 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1107 base_table_args
1108 base_table_args
1108 )
1109 )
1109 __mapper_args__ = {}
1110 __mapper_args__ = {}
1110
1111
1111 # ApiKey role
1112 # ApiKey role
1112 ROLE_ALL = 'token_role_all'
1113 ROLE_ALL = 'token_role_all'
1113 ROLE_HTTP = 'token_role_http'
1114 ROLE_HTTP = 'token_role_http'
1114 ROLE_VCS = 'token_role_vcs'
1115 ROLE_VCS = 'token_role_vcs'
1115 ROLE_API = 'token_role_api'
1116 ROLE_API = 'token_role_api'
1116 ROLE_FEED = 'token_role_feed'
1117 ROLE_FEED = 'token_role_feed'
1117 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1118 ROLE_PASSWORD_RESET = 'token_password_reset'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1119
1120
1120 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1121
1122
1122 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1123 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1124 api_key = Column("api_key", String(255), nullable=False, unique=True)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1125 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1126 expires = Column('expires', Float(53), nullable=False)
1127 expires = Column('expires', Float(53), nullable=False)
1127 role = Column('role', String(255), nullable=True)
1128 role = Column('role', String(255), nullable=True)
1128 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1129
1130
1130 # scope columns
1131 # scope columns
1131 repo_id = Column(
1132 repo_id = Column(
1132 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1133 nullable=True, unique=None, default=None)
1134 nullable=True, unique=None, default=None)
1134 repo = relationship('Repository', lazy='joined')
1135 repo = relationship('Repository', lazy='joined')
1135
1136
1136 repo_group_id = Column(
1137 repo_group_id = Column(
1137 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1138 nullable=True, unique=None, default=None)
1139 nullable=True, unique=None, default=None)
1139 repo_group = relationship('RepoGroup', lazy='joined')
1140 repo_group = relationship('RepoGroup', lazy='joined')
1140
1141
1141 user = relationship('User', lazy='joined')
1142 user = relationship('User', lazy='joined')
1142
1143
1143 def __unicode__(self):
1144 def __unicode__(self):
1144 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1145
1146
1146 def __json__(self):
1147 def __json__(self):
1147 data = {
1148 data = {
1148 'auth_token': self.api_key,
1149 'auth_token': self.api_key,
1149 'role': self.role,
1150 'role': self.role,
1150 'scope': self.scope_humanized,
1151 'scope': self.scope_humanized,
1151 'expired': self.expired
1152 'expired': self.expired
1152 }
1153 }
1153 return data
1154 return data
1154
1155
1155 def get_api_data(self, include_secrets=False):
1156 def get_api_data(self, include_secrets=False):
1156 data = self.__json__()
1157 data = self.__json__()
1157 if include_secrets:
1158 if include_secrets:
1158 return data
1159 return data
1159 else:
1160 else:
1160 data['auth_token'] = self.token_obfuscated
1161 data['auth_token'] = self.token_obfuscated
1161 return data
1162 return data
1162
1163
1163 @hybrid_property
1164 @hybrid_property
1164 def description_safe(self):
1165 def description_safe(self):
1165 from rhodecode.lib import helpers as h
1166 from rhodecode.lib import helpers as h
1166 return h.escape(self.description)
1167 return h.escape(self.description)
1167
1168
1168 @property
1169 @property
1169 def expired(self):
1170 def expired(self):
1170 if self.expires == -1:
1171 if self.expires == -1:
1171 return False
1172 return False
1172 return time.time() > self.expires
1173 return time.time() > self.expires
1173
1174
1174 @classmethod
1175 @classmethod
1175 def _get_role_name(cls, role):
1176 def _get_role_name(cls, role):
1176 return {
1177 return {
1177 cls.ROLE_ALL: _('all'),
1178 cls.ROLE_ALL: _('all'),
1178 cls.ROLE_HTTP: _('http/web interface'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1179 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1180 cls.ROLE_API: _('api calls'),
1181 cls.ROLE_API: _('api calls'),
1181 cls.ROLE_FEED: _('feed access'),
1182 cls.ROLE_FEED: _('feed access'),
1182 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1183 }.get(role, role)
1184 }.get(role, role)
1184
1185
1185 @property
1186 @property
1186 def role_humanized(self):
1187 def role_humanized(self):
1187 return self._get_role_name(self.role)
1188 return self._get_role_name(self.role)
1188
1189
1189 def _get_scope(self):
1190 def _get_scope(self):
1190 if self.repo:
1191 if self.repo:
1191 return 'Repository: {}'.format(self.repo.repo_name)
1192 return 'Repository: {}'.format(self.repo.repo_name)
1192 if self.repo_group:
1193 if self.repo_group:
1193 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1194 return 'Global'
1195 return 'Global'
1195
1196
1196 @property
1197 @property
1197 def scope_humanized(self):
1198 def scope_humanized(self):
1198 return self._get_scope()
1199 return self._get_scope()
1199
1200
1200 @property
1201 @property
1201 def token_obfuscated(self):
1202 def token_obfuscated(self):
1202 if self.api_key:
1203 if self.api_key:
1203 return self.api_key[:4] + "****"
1204 return self.api_key[:4] + "****"
1204
1205
1205
1206
1206 class UserEmailMap(Base, BaseModel):
1207 class UserEmailMap(Base, BaseModel):
1207 __tablename__ = 'user_email_map'
1208 __tablename__ = 'user_email_map'
1208 __table_args__ = (
1209 __table_args__ = (
1209 Index('uem_email_idx', 'email'),
1210 Index('uem_email_idx', 'email'),
1210 UniqueConstraint('email'),
1211 UniqueConstraint('email'),
1211 base_table_args
1212 base_table_args
1212 )
1213 )
1213 __mapper_args__ = {}
1214 __mapper_args__ = {}
1214
1215
1215 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1217 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1218 user = relationship('User', lazy='joined')
1219 user = relationship('User', lazy='joined')
1219
1220
1220 @validates('_email')
1221 @validates('_email')
1221 def validate_email(self, key, email):
1222 def validate_email(self, key, email):
1222 # check if this email is not main one
1223 # check if this email is not main one
1223 main_email = Session().query(User).filter(User.email == email).scalar()
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1224 if main_email is not None:
1225 if main_email is not None:
1225 raise AttributeError('email %s is present is user table' % email)
1226 raise AttributeError('email %s is present is user table' % email)
1226 return email
1227 return email
1227
1228
1228 @hybrid_property
1229 @hybrid_property
1229 def email(self):
1230 def email(self):
1230 return self._email
1231 return self._email
1231
1232
1232 @email.setter
1233 @email.setter
1233 def email(self, val):
1234 def email(self, val):
1234 self._email = val.lower() if val else None
1235 self._email = val.lower() if val else None
1235
1236
1236
1237
1237 class UserIpMap(Base, BaseModel):
1238 class UserIpMap(Base, BaseModel):
1238 __tablename__ = 'user_ip_map'
1239 __tablename__ = 'user_ip_map'
1239 __table_args__ = (
1240 __table_args__ = (
1240 UniqueConstraint('user_id', 'ip_addr'),
1241 UniqueConstraint('user_id', 'ip_addr'),
1241 base_table_args
1242 base_table_args
1242 )
1243 )
1243 __mapper_args__ = {}
1244 __mapper_args__ = {}
1244
1245
1245 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1247 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1248 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1249 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1250 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1251
1252
1252 @hybrid_property
1253 @hybrid_property
1253 def description_safe(self):
1254 def description_safe(self):
1254 from rhodecode.lib import helpers as h
1255 from rhodecode.lib import helpers as h
1255 return h.escape(self.description)
1256 return h.escape(self.description)
1256
1257
1257 @classmethod
1258 @classmethod
1258 def _get_ip_range(cls, ip_addr):
1259 def _get_ip_range(cls, ip_addr):
1259 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1260 return [str(net.network_address), str(net.broadcast_address)]
1261 return [str(net.network_address), str(net.broadcast_address)]
1261
1262
1262 def __json__(self):
1263 def __json__(self):
1263 return {
1264 return {
1264 'ip_addr': self.ip_addr,
1265 'ip_addr': self.ip_addr,
1265 'ip_range': self._get_ip_range(self.ip_addr),
1266 'ip_range': self._get_ip_range(self.ip_addr),
1266 }
1267 }
1267
1268
1268 def __unicode__(self):
1269 def __unicode__(self):
1269 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1270 self.user_id, self.ip_addr)
1271 self.user_id, self.ip_addr)
1271
1272
1272
1273
1273 class UserSshKeys(Base, BaseModel):
1274 class UserSshKeys(Base, BaseModel):
1274 __tablename__ = 'user_ssh_keys'
1275 __tablename__ = 'user_ssh_keys'
1275 __table_args__ = (
1276 __table_args__ = (
1276 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1277
1278
1278 UniqueConstraint('ssh_key_fingerprint'),
1279 UniqueConstraint('ssh_key_fingerprint'),
1279
1280
1280 base_table_args
1281 base_table_args
1281 )
1282 )
1282 __mapper_args__ = {}
1283 __mapper_args__ = {}
1283
1284
1284 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1286 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1287
1288
1288 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1289
1290
1290 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1291 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1292 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1293
1294
1294 user = relationship('User', lazy='joined')
1295 user = relationship('User', lazy='joined')
1295
1296
1296 def __json__(self):
1297 def __json__(self):
1297 data = {
1298 data = {
1298 'ssh_fingerprint': self.ssh_key_fingerprint,
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1299 'description': self.description,
1300 'description': self.description,
1300 'created_on': self.created_on
1301 'created_on': self.created_on
1301 }
1302 }
1302 return data
1303 return data
1303
1304
1304 def get_api_data(self):
1305 def get_api_data(self):
1305 data = self.__json__()
1306 data = self.__json__()
1306 return data
1307 return data
1307
1308
1308
1309
1309 class UserLog(Base, BaseModel):
1310 class UserLog(Base, BaseModel):
1310 __tablename__ = 'user_logs'
1311 __tablename__ = 'user_logs'
1311 __table_args__ = (
1312 __table_args__ = (
1312 base_table_args,
1313 base_table_args,
1313 )
1314 )
1314
1315
1315 VERSION_1 = 'v1'
1316 VERSION_1 = 'v1'
1316 VERSION_2 = 'v2'
1317 VERSION_2 = 'v2'
1317 VERSIONS = [VERSION_1, VERSION_2]
1318 VERSIONS = [VERSION_1, VERSION_2]
1318
1319
1319 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1320 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1321 username = Column("username", String(255), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1322 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1323 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1324 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1325 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1326 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1327
1328
1328 version = Column("version", String(255), nullable=True, default=VERSION_1)
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1329 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1330 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331
1332
1332 def __unicode__(self):
1333 def __unicode__(self):
1333 return u"<%s('id:%s:%s')>" % (
1334 return u"<%s('id:%s:%s')>" % (
1334 self.__class__.__name__, self.repository_name, self.action)
1335 self.__class__.__name__, self.repository_name, self.action)
1335
1336
1336 def __json__(self):
1337 def __json__(self):
1337 return {
1338 return {
1338 'user_id': self.user_id,
1339 'user_id': self.user_id,
1339 'username': self.username,
1340 'username': self.username,
1340 'repository_id': self.repository_id,
1341 'repository_id': self.repository_id,
1341 'repository_name': self.repository_name,
1342 'repository_name': self.repository_name,
1342 'user_ip': self.user_ip,
1343 'user_ip': self.user_ip,
1343 'action_date': self.action_date,
1344 'action_date': self.action_date,
1344 'action': self.action,
1345 'action': self.action,
1345 }
1346 }
1346
1347
1347 @hybrid_property
1348 @hybrid_property
1348 def entry_id(self):
1349 def entry_id(self):
1349 return self.user_log_id
1350 return self.user_log_id
1350
1351
1351 @property
1352 @property
1352 def action_as_day(self):
1353 def action_as_day(self):
1353 return datetime.date(*self.action_date.timetuple()[:3])
1354 return datetime.date(*self.action_date.timetuple()[:3])
1354
1355
1355 user = relationship('User')
1356 user = relationship('User')
1356 repository = relationship('Repository', cascade='')
1357 repository = relationship('Repository', cascade='')
1357
1358
1358
1359
1359 class UserGroup(Base, BaseModel):
1360 class UserGroup(Base, BaseModel):
1360 __tablename__ = 'users_groups'
1361 __tablename__ = 'users_groups'
1361 __table_args__ = (
1362 __table_args__ = (
1362 base_table_args,
1363 base_table_args,
1363 )
1364 )
1364
1365
1365 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1367 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1368 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1369 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1370 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1371 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1372 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1373
1374
1374 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1375 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1376 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1377 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1378 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1379 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1380
1381
1381 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1382 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1383
1384
1384 @classmethod
1385 @classmethod
1385 def _load_group_data(cls, column):
1386 def _load_group_data(cls, column):
1386 if not column:
1387 if not column:
1387 return {}
1388 return {}
1388
1389
1389 try:
1390 try:
1390 return json.loads(column) or {}
1391 return json.loads(column) or {}
1391 except TypeError:
1392 except TypeError:
1392 return {}
1393 return {}
1393
1394
1394 @hybrid_property
1395 @hybrid_property
1395 def description_safe(self):
1396 def description_safe(self):
1396 from rhodecode.lib import helpers as h
1397 from rhodecode.lib import helpers as h
1397 return h.escape(self.user_group_description)
1398 return h.escape(self.user_group_description)
1398
1399
1399 @hybrid_property
1400 @hybrid_property
1400 def group_data(self):
1401 def group_data(self):
1401 return self._load_group_data(self._group_data)
1402 return self._load_group_data(self._group_data)
1402
1403
1403 @group_data.expression
1404 @group_data.expression
1404 def group_data(self, **kwargs):
1405 def group_data(self, **kwargs):
1405 return self._group_data
1406 return self._group_data
1406
1407
1407 @group_data.setter
1408 @group_data.setter
1408 def group_data(self, val):
1409 def group_data(self, val):
1409 try:
1410 try:
1410 self._group_data = json.dumps(val)
1411 self._group_data = json.dumps(val)
1411 except Exception:
1412 except Exception:
1412 log.error(traceback.format_exc())
1413 log.error(traceback.format_exc())
1413
1414
1414 @classmethod
1415 @classmethod
1415 def _load_sync(cls, group_data):
1416 def _load_sync(cls, group_data):
1416 if group_data:
1417 if group_data:
1417 return group_data.get('extern_type')
1418 return group_data.get('extern_type')
1418
1419
1419 @property
1420 @property
1420 def sync(self):
1421 def sync(self):
1421 return self._load_sync(self.group_data)
1422 return self._load_sync(self.group_data)
1422
1423
1423 def __unicode__(self):
1424 def __unicode__(self):
1424 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1425 self.users_group_id,
1426 self.users_group_id,
1426 self.users_group_name)
1427 self.users_group_name)
1427
1428
1428 @classmethod
1429 @classmethod
1429 def get_by_group_name(cls, group_name, cache=False,
1430 def get_by_group_name(cls, group_name, cache=False,
1430 case_insensitive=False):
1431 case_insensitive=False):
1431 if case_insensitive:
1432 if case_insensitive:
1432 q = cls.query().filter(func.lower(cls.users_group_name) ==
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1433 func.lower(group_name))
1434 func.lower(group_name))
1434
1435
1435 else:
1436 else:
1436 q = cls.query().filter(cls.users_group_name == group_name)
1437 q = cls.query().filter(cls.users_group_name == group_name)
1437 if cache:
1438 if cache:
1438 q = q.options(
1439 q = q.options(
1439 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1440 return q.scalar()
1441 return q.scalar()
1441
1442
1442 @classmethod
1443 @classmethod
1443 def get(cls, user_group_id, cache=False):
1444 def get(cls, user_group_id, cache=False):
1444 if not user_group_id:
1445 if not user_group_id:
1445 return
1446 return
1446
1447
1447 user_group = cls.query()
1448 user_group = cls.query()
1448 if cache:
1449 if cache:
1449 user_group = user_group.options(
1450 user_group = user_group.options(
1450 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1451 return user_group.get(user_group_id)
1452 return user_group.get(user_group_id)
1452
1453
1453 def permissions(self, with_admins=True, with_owner=True,
1454 def permissions(self, with_admins=True, with_owner=True,
1454 expand_from_user_groups=False):
1455 expand_from_user_groups=False):
1455 """
1456 """
1456 Permissions for user groups
1457 Permissions for user groups
1457 """
1458 """
1458 _admin_perm = 'usergroup.admin'
1459 _admin_perm = 'usergroup.admin'
1459
1460
1460 owner_row = []
1461 owner_row = []
1461 if with_owner:
1462 if with_owner:
1462 usr = AttributeDict(self.user.get_dict())
1463 usr = AttributeDict(self.user.get_dict())
1463 usr.owner_row = True
1464 usr.owner_row = True
1464 usr.permission = _admin_perm
1465 usr.permission = _admin_perm
1465 owner_row.append(usr)
1466 owner_row.append(usr)
1466
1467
1467 super_admin_ids = []
1468 super_admin_ids = []
1468 super_admin_rows = []
1469 super_admin_rows = []
1469 if with_admins:
1470 if with_admins:
1470 for usr in User.get_all_super_admins():
1471 for usr in User.get_all_super_admins():
1471 super_admin_ids.append(usr.user_id)
1472 super_admin_ids.append(usr.user_id)
1472 # if this admin is also owner, don't double the record
1473 # if this admin is also owner, don't double the record
1473 if usr.user_id == owner_row[0].user_id:
1474 if usr.user_id == owner_row[0].user_id:
1474 owner_row[0].admin_row = True
1475 owner_row[0].admin_row = True
1475 else:
1476 else:
1476 usr = AttributeDict(usr.get_dict())
1477 usr = AttributeDict(usr.get_dict())
1477 usr.admin_row = True
1478 usr.admin_row = True
1478 usr.permission = _admin_perm
1479 usr.permission = _admin_perm
1479 super_admin_rows.append(usr)
1480 super_admin_rows.append(usr)
1480
1481
1481 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1482 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1483 joinedload(UserUserGroupToPerm.user),
1484 joinedload(UserUserGroupToPerm.user),
1484 joinedload(UserUserGroupToPerm.permission),)
1485 joinedload(UserUserGroupToPerm.permission),)
1485
1486
1486 # get owners and admins and permissions. We do a trick of re-writing
1487 # get owners and admins and permissions. We do a trick of re-writing
1487 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1488 # has a global reference and changing one object propagates to all
1489 # has a global reference and changing one object propagates to all
1489 # others. This means if admin is also an owner admin_row that change
1490 # others. This means if admin is also an owner admin_row that change
1490 # would propagate to both objects
1491 # would propagate to both objects
1491 perm_rows = []
1492 perm_rows = []
1492 for _usr in q.all():
1493 for _usr in q.all():
1493 usr = AttributeDict(_usr.user.get_dict())
1494 usr = AttributeDict(_usr.user.get_dict())
1494 # if this user is also owner/admin, mark as duplicate record
1495 # if this user is also owner/admin, mark as duplicate record
1495 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1496 usr.duplicate_perm = True
1497 usr.duplicate_perm = True
1497 usr.permission = _usr.permission.permission_name
1498 usr.permission = _usr.permission.permission_name
1498 perm_rows.append(usr)
1499 perm_rows.append(usr)
1499
1500
1500 # filter the perm rows by 'default' first and then sort them by
1501 # filter the perm rows by 'default' first and then sort them by
1501 # admin,write,read,none permissions sorted again alphabetically in
1502 # admin,write,read,none permissions sorted again alphabetically in
1502 # each group
1503 # each group
1503 perm_rows = sorted(perm_rows, key=display_user_sort)
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1504
1505
1505 user_groups_rows = []
1506 user_groups_rows = []
1506 if expand_from_user_groups:
1507 if expand_from_user_groups:
1507 for ug in self.permission_user_groups(with_members=True):
1508 for ug in self.permission_user_groups(with_members=True):
1508 for user_data in ug.members:
1509 for user_data in ug.members:
1509 user_groups_rows.append(user_data)
1510 user_groups_rows.append(user_data)
1510
1511
1511 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1512
1513
1513 def permission_user_groups(self, with_members=False):
1514 def permission_user_groups(self, with_members=False):
1514 q = UserGroupUserGroupToPerm.query()\
1515 q = UserGroupUserGroupToPerm.query()\
1515 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1516 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1517 joinedload(UserGroupUserGroupToPerm.target_user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1518 joinedload(UserGroupUserGroupToPerm.permission),)
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1519
1520
1520 perm_rows = []
1521 perm_rows = []
1521 for _user_group in q.all():
1522 for _user_group in q.all():
1522 entry = AttributeDict(_user_group.user_group.get_dict())
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1523 entry.permission = _user_group.permission.permission_name
1524 entry.permission = _user_group.permission.permission_name
1524 if with_members:
1525 if with_members:
1525 entry.members = [x.user.get_dict()
1526 entry.members = [x.user.get_dict()
1526 for x in _user_group.user_group.members]
1527 for x in _user_group.user_group.members]
1527 perm_rows.append(entry)
1528 perm_rows.append(entry)
1528
1529
1529 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1530 return perm_rows
1531 return perm_rows
1531
1532
1532 def _get_default_perms(self, user_group, suffix=''):
1533 def _get_default_perms(self, user_group, suffix=''):
1533 from rhodecode.model.permission import PermissionModel
1534 from rhodecode.model.permission import PermissionModel
1534 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1535
1536
1536 def get_default_perms(self, suffix=''):
1537 def get_default_perms(self, suffix=''):
1537 return self._get_default_perms(self, suffix)
1538 return self._get_default_perms(self, suffix)
1538
1539
1539 def get_api_data(self, with_group_members=True, include_secrets=False):
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1540 """
1541 """
1541 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1542 basically forwarded.
1543 basically forwarded.
1543
1544
1544 """
1545 """
1545 user_group = self
1546 user_group = self
1546 data = {
1547 data = {
1547 'users_group_id': user_group.users_group_id,
1548 'users_group_id': user_group.users_group_id,
1548 'group_name': user_group.users_group_name,
1549 'group_name': user_group.users_group_name,
1549 'group_description': user_group.user_group_description,
1550 'group_description': user_group.user_group_description,
1550 'active': user_group.users_group_active,
1551 'active': user_group.users_group_active,
1551 'owner': user_group.user.username,
1552 'owner': user_group.user.username,
1552 'sync': user_group.sync,
1553 'sync': user_group.sync,
1553 'owner_email': user_group.user.email,
1554 'owner_email': user_group.user.email,
1554 }
1555 }
1555
1556
1556 if with_group_members:
1557 if with_group_members:
1557 users = []
1558 users = []
1558 for user in user_group.members:
1559 for user in user_group.members:
1559 user = user.user
1560 user = user.user
1560 users.append(user.get_api_data(include_secrets=include_secrets))
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1561 data['users'] = users
1562 data['users'] = users
1562
1563
1563 return data
1564 return data
1564
1565
1565
1566
1566 class UserGroupMember(Base, BaseModel):
1567 class UserGroupMember(Base, BaseModel):
1567 __tablename__ = 'users_groups_members'
1568 __tablename__ = 'users_groups_members'
1568 __table_args__ = (
1569 __table_args__ = (
1569 base_table_args,
1570 base_table_args,
1570 )
1571 )
1571
1572
1572 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1573 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1575
1576
1576 user = relationship('User', lazy='joined')
1577 user = relationship('User', lazy='joined')
1577 users_group = relationship('UserGroup')
1578 users_group = relationship('UserGroup')
1578
1579
1579 def __init__(self, gr_id='', u_id=''):
1580 def __init__(self, gr_id='', u_id=''):
1580 self.users_group_id = gr_id
1581 self.users_group_id = gr_id
1581 self.user_id = u_id
1582 self.user_id = u_id
1582
1583
1583
1584
1584 class RepositoryField(Base, BaseModel):
1585 class RepositoryField(Base, BaseModel):
1585 __tablename__ = 'repositories_fields'
1586 __tablename__ = 'repositories_fields'
1586 __table_args__ = (
1587 __table_args__ = (
1587 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1588 base_table_args,
1589 base_table_args,
1589 )
1590 )
1590
1591
1591 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1592
1593
1593 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1595 field_key = Column("field_key", String(250))
1596 field_key = Column("field_key", String(250))
1596 field_label = Column("field_label", String(1024), nullable=False)
1597 field_label = Column("field_label", String(1024), nullable=False)
1597 field_value = Column("field_value", String(10000), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1598 field_desc = Column("field_desc", String(1024), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1599 field_type = Column("field_type", String(255), nullable=False, unique=None)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1600 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1601
1602
1602 repository = relationship('Repository')
1603 repository = relationship('Repository')
1603
1604
1604 @property
1605 @property
1605 def field_key_prefixed(self):
1606 def field_key_prefixed(self):
1606 return 'ex_%s' % self.field_key
1607 return 'ex_%s' % self.field_key
1607
1608
1608 @classmethod
1609 @classmethod
1609 def un_prefix_key(cls, key):
1610 def un_prefix_key(cls, key):
1610 if key.startswith(cls.PREFIX):
1611 if key.startswith(cls.PREFIX):
1611 return key[len(cls.PREFIX):]
1612 return key[len(cls.PREFIX):]
1612 return key
1613 return key
1613
1614
1614 @classmethod
1615 @classmethod
1615 def get_by_key_name(cls, key, repo):
1616 def get_by_key_name(cls, key, repo):
1616 row = cls.query()\
1617 row = cls.query()\
1617 .filter(cls.repository == repo)\
1618 .filter(cls.repository == repo)\
1618 .filter(cls.field_key == key).scalar()
1619 .filter(cls.field_key == key).scalar()
1619 return row
1620 return row
1620
1621
1621
1622
1622 class Repository(Base, BaseModel):
1623 class Repository(Base, BaseModel):
1623 __tablename__ = 'repositories'
1624 __tablename__ = 'repositories'
1624 __table_args__ = (
1625 __table_args__ = (
1625 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1626 base_table_args,
1627 base_table_args,
1627 )
1628 )
1628 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1629 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1630 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1631
1632
1632 STATE_CREATED = 'repo_state_created'
1633 STATE_CREATED = 'repo_state_created'
1633 STATE_PENDING = 'repo_state_pending'
1634 STATE_PENDING = 'repo_state_pending'
1634 STATE_ERROR = 'repo_state_error'
1635 STATE_ERROR = 'repo_state_error'
1635
1636
1636 LOCK_AUTOMATIC = 'lock_auto'
1637 LOCK_AUTOMATIC = 'lock_auto'
1637 LOCK_API = 'lock_api'
1638 LOCK_API = 'lock_api'
1638 LOCK_WEB = 'lock_web'
1639 LOCK_WEB = 'lock_web'
1639 LOCK_PULL = 'lock_pull'
1640 LOCK_PULL = 'lock_pull'
1640
1641
1641 NAME_SEP = URL_SEP
1642 NAME_SEP = URL_SEP
1642
1643
1643 repo_id = Column(
1644 repo_id = Column(
1644 "repo_id", Integer(), nullable=False, unique=True, default=None,
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1645 primary_key=True)
1646 primary_key=True)
1646 _repo_name = Column(
1647 _repo_name = Column(
1647 "repo_name", Text(), nullable=False, default=None)
1648 "repo_name", Text(), nullable=False, default=None)
1648 _repo_name_hash = Column(
1649 _repo_name_hash = Column(
1649 "repo_name_hash", String(255), nullable=False, unique=True)
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1650 repo_state = Column("repo_state", String(255), nullable=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1651
1652
1652 clone_uri = Column(
1653 clone_uri = Column(
1653 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1654 default=None)
1655 default=None)
1655 push_uri = Column(
1656 push_uri = Column(
1656 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1657 default=None)
1658 default=None)
1658 repo_type = Column(
1659 repo_type = Column(
1659 "repo_type", String(255), nullable=False, unique=False, default=None)
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1660 user_id = Column(
1661 user_id = Column(
1661 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1662 unique=False, default=None)
1663 unique=False, default=None)
1663 private = Column(
1664 private = Column(
1664 "private", Boolean(), nullable=True, unique=None, default=None)
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1665 archived = Column(
1666 archived = Column(
1666 "archived", Boolean(), nullable=True, unique=None, default=None)
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1667 enable_statistics = Column(
1668 enable_statistics = Column(
1668 "statistics", Boolean(), nullable=True, unique=None, default=True)
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1669 enable_downloads = Column(
1670 enable_downloads = Column(
1670 "downloads", Boolean(), nullable=True, unique=None, default=True)
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1671 description = Column(
1672 description = Column(
1672 "description", String(10000), nullable=True, unique=None, default=None)
1673 "description", String(10000), nullable=True, unique=None, default=None)
1673 created_on = Column(
1674 created_on = Column(
1674 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1675 default=datetime.datetime.now)
1676 default=datetime.datetime.now)
1676 updated_on = Column(
1677 updated_on = Column(
1677 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1678 default=datetime.datetime.now)
1679 default=datetime.datetime.now)
1679 _landing_revision = Column(
1680 _landing_revision = Column(
1680 "landing_revision", String(255), nullable=False, unique=False,
1681 "landing_revision", String(255), nullable=False, unique=False,
1681 default=None)
1682 default=None)
1682 enable_locking = Column(
1683 enable_locking = Column(
1683 "enable_locking", Boolean(), nullable=False, unique=None,
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1684 default=False)
1685 default=False)
1685 _locked = Column(
1686 _locked = Column(
1686 "locked", String(255), nullable=True, unique=False, default=None)
1687 "locked", String(255), nullable=True, unique=False, default=None)
1687 _changeset_cache = Column(
1688 _changeset_cache = Column(
1688 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1689
1690
1690 fork_id = Column(
1691 fork_id = Column(
1691 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1692 nullable=True, unique=False, default=None)
1693 nullable=True, unique=False, default=None)
1693 group_id = Column(
1694 group_id = Column(
1694 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1695 unique=False, default=None)
1696 unique=False, default=None)
1696
1697
1697 user = relationship('User', lazy='joined')
1698 user = relationship('User', lazy='joined')
1698 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1699 group = relationship('RepoGroup', lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1700 repo_to_perm = relationship(
1701 repo_to_perm = relationship(
1701 'UserRepoToPerm', cascade='all',
1702 'UserRepoToPerm', cascade='all',
1702 order_by='UserRepoToPerm.repo_to_perm_id')
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1703 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1704 stats = relationship('Statistics', cascade='all', uselist=False)
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1705
1706
1706 followers = relationship(
1707 followers = relationship(
1707 'UserFollowing',
1708 'UserFollowing',
1708 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1709 cascade='all')
1710 cascade='all')
1710 extra_fields = relationship(
1711 extra_fields = relationship(
1711 'RepositoryField', cascade="all, delete-orphan")
1712 'RepositoryField', cascade="all, delete-orphan")
1712 logs = relationship('UserLog')
1713 logs = relationship('UserLog')
1713 comments = relationship(
1714 comments = relationship(
1714 'ChangesetComment', cascade="all, delete-orphan")
1715 'ChangesetComment', cascade="all, delete-orphan")
1715 pull_requests_source = relationship(
1716 pull_requests_source = relationship(
1716 'PullRequest',
1717 'PullRequest',
1717 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1718 cascade="all, delete-orphan")
1719 cascade="all, delete-orphan")
1719 pull_requests_target = relationship(
1720 pull_requests_target = relationship(
1720 'PullRequest',
1721 'PullRequest',
1721 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1722 cascade="all, delete-orphan")
1723 cascade="all, delete-orphan")
1723 ui = relationship('RepoRhodeCodeUi', cascade="all")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1724 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1725 integrations = relationship('Integration', cascade="all, delete-orphan")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1726
1727
1727 scoped_tokens = relationship('UserApiKeys', cascade="all")
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1728
1729
1729 # no cascade, set NULL
1730 # no cascade, set NULL
1730 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1731
1732
1732 def __unicode__(self):
1733 def __unicode__(self):
1733 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1734 safe_unicode(self.repo_name))
1735 safe_unicode(self.repo_name))
1735
1736
1736 @hybrid_property
1737 @hybrid_property
1737 def description_safe(self):
1738 def description_safe(self):
1738 from rhodecode.lib import helpers as h
1739 from rhodecode.lib import helpers as h
1739 return h.escape(self.description)
1740 return h.escape(self.description)
1740
1741
1741 @hybrid_property
1742 @hybrid_property
1742 def landing_rev(self):
1743 def landing_rev(self):
1743 # always should return [rev_type, rev]
1744 # always should return [rev_type, rev]
1744 if self._landing_revision:
1745 if self._landing_revision:
1745 _rev_info = self._landing_revision.split(':')
1746 _rev_info = self._landing_revision.split(':')
1746 if len(_rev_info) < 2:
1747 if len(_rev_info) < 2:
1747 _rev_info.insert(0, 'rev')
1748 _rev_info.insert(0, 'rev')
1748 return [_rev_info[0], _rev_info[1]]
1749 return [_rev_info[0], _rev_info[1]]
1749 return [None, None]
1750 return [None, None]
1750
1751
1751 @landing_rev.setter
1752 @landing_rev.setter
1752 def landing_rev(self, val):
1753 def landing_rev(self, val):
1753 if ':' not in val:
1754 if ':' not in val:
1754 raise ValueError('value must be delimited with `:` and consist '
1755 raise ValueError('value must be delimited with `:` and consist '
1755 'of <rev_type>:<rev>, got %s instead' % val)
1756 'of <rev_type>:<rev>, got %s instead' % val)
1756 self._landing_revision = val
1757 self._landing_revision = val
1757
1758
1758 @hybrid_property
1759 @hybrid_property
1759 def locked(self):
1760 def locked(self):
1760 if self._locked:
1761 if self._locked:
1761 user_id, timelocked, reason = self._locked.split(':')
1762 user_id, timelocked, reason = self._locked.split(':')
1762 lock_values = int(user_id), timelocked, reason
1763 lock_values = int(user_id), timelocked, reason
1763 else:
1764 else:
1764 lock_values = [None, None, None]
1765 lock_values = [None, None, None]
1765 return lock_values
1766 return lock_values
1766
1767
1767 @locked.setter
1768 @locked.setter
1768 def locked(self, val):
1769 def locked(self, val):
1769 if val and isinstance(val, (list, tuple)):
1770 if val and isinstance(val, (list, tuple)):
1770 self._locked = ':'.join(map(str, val))
1771 self._locked = ':'.join(map(str, val))
1771 else:
1772 else:
1772 self._locked = None
1773 self._locked = None
1773
1774
1774 @hybrid_property
1775 @hybrid_property
1775 def changeset_cache(self):
1776 def changeset_cache(self):
1776 from rhodecode.lib.vcs.backends.base import EmptyCommit
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1777 dummy = EmptyCommit().__json__()
1778 dummy = EmptyCommit().__json__()
1778 if not self._changeset_cache:
1779 if not self._changeset_cache:
1779 dummy['source_repo_id'] = self.repo_id
1780 dummy['source_repo_id'] = self.repo_id
1780 return json.loads(json.dumps(dummy))
1781 return json.loads(json.dumps(dummy))
1781
1782
1782 try:
1783 try:
1783 return json.loads(self._changeset_cache)
1784 return json.loads(self._changeset_cache)
1784 except TypeError:
1785 except TypeError:
1785 return dummy
1786 return dummy
1786 except Exception:
1787 except Exception:
1787 log.error(traceback.format_exc())
1788 log.error(traceback.format_exc())
1788 return dummy
1789 return dummy
1789
1790
1790 @changeset_cache.setter
1791 @changeset_cache.setter
1791 def changeset_cache(self, val):
1792 def changeset_cache(self, val):
1792 try:
1793 try:
1793 self._changeset_cache = json.dumps(val)
1794 self._changeset_cache = json.dumps(val)
1794 except Exception:
1795 except Exception:
1795 log.error(traceback.format_exc())
1796 log.error(traceback.format_exc())
1796
1797
1797 @hybrid_property
1798 @hybrid_property
1798 def repo_name(self):
1799 def repo_name(self):
1799 return self._repo_name
1800 return self._repo_name
1800
1801
1801 @repo_name.setter
1802 @repo_name.setter
1802 def repo_name(self, value):
1803 def repo_name(self, value):
1803 self._repo_name = value
1804 self._repo_name = value
1804 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1805 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1805
1806
1806 @classmethod
1807 @classmethod
1807 def normalize_repo_name(cls, repo_name):
1808 def normalize_repo_name(cls, repo_name):
1808 """
1809 """
1809 Normalizes os specific repo_name to the format internally stored inside
1810 Normalizes os specific repo_name to the format internally stored inside
1810 database using URL_SEP
1811 database using URL_SEP
1811
1812
1812 :param cls:
1813 :param cls:
1813 :param repo_name:
1814 :param repo_name:
1814 """
1815 """
1815 return cls.NAME_SEP.join(repo_name.split(os.sep))
1816 return cls.NAME_SEP.join(repo_name.split(os.sep))
1816
1817
1817 @classmethod
1818 @classmethod
1818 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1819 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1819 session = Session()
1820 session = Session()
1820 q = session.query(cls).filter(cls.repo_name == repo_name)
1821 q = session.query(cls).filter(cls.repo_name == repo_name)
1821
1822
1822 if cache:
1823 if cache:
1823 if identity_cache:
1824 if identity_cache:
1824 val = cls.identity_cache(session, 'repo_name', repo_name)
1825 val = cls.identity_cache(session, 'repo_name', repo_name)
1825 if val:
1826 if val:
1826 return val
1827 return val
1827 else:
1828 else:
1828 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1829 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1829 q = q.options(
1830 q = q.options(
1830 FromCache("sql_cache_short", cache_key))
1831 FromCache("sql_cache_short", cache_key))
1831
1832
1832 return q.scalar()
1833 return q.scalar()
1833
1834
1834 @classmethod
1835 @classmethod
1835 def get_by_id_or_repo_name(cls, repoid):
1836 def get_by_id_or_repo_name(cls, repoid):
1836 if isinstance(repoid, (int, long)):
1837 if isinstance(repoid, (int, long)):
1837 try:
1838 try:
1838 repo = cls.get(repoid)
1839 repo = cls.get(repoid)
1839 except ValueError:
1840 except ValueError:
1840 repo = None
1841 repo = None
1841 else:
1842 else:
1842 repo = cls.get_by_repo_name(repoid)
1843 repo = cls.get_by_repo_name(repoid)
1843 return repo
1844 return repo
1844
1845
1845 @classmethod
1846 @classmethod
1846 def get_by_full_path(cls, repo_full_path):
1847 def get_by_full_path(cls, repo_full_path):
1847 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1848 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1848 repo_name = cls.normalize_repo_name(repo_name)
1849 repo_name = cls.normalize_repo_name(repo_name)
1849 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1850 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1850
1851
1851 @classmethod
1852 @classmethod
1852 def get_repo_forks(cls, repo_id):
1853 def get_repo_forks(cls, repo_id):
1853 return cls.query().filter(Repository.fork_id == repo_id)
1854 return cls.query().filter(Repository.fork_id == repo_id)
1854
1855
1855 @classmethod
1856 @classmethod
1856 def base_path(cls):
1857 def base_path(cls):
1857 """
1858 """
1858 Returns base path when all repos are stored
1859 Returns base path when all repos are stored
1859
1860
1860 :param cls:
1861 :param cls:
1861 """
1862 """
1862 q = Session().query(RhodeCodeUi)\
1863 q = Session().query(RhodeCodeUi)\
1863 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1864 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1864 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1865 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1865 return q.one().ui_value
1866 return q.one().ui_value
1866
1867
1867 @classmethod
1868 @classmethod
1868 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1869 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1869 case_insensitive=True, archived=False):
1870 case_insensitive=True, archived=False):
1870 q = Repository.query()
1871 q = Repository.query()
1871
1872
1872 if not archived:
1873 if not archived:
1873 q = q.filter(Repository.archived.isnot(true()))
1874 q = q.filter(Repository.archived.isnot(true()))
1874
1875
1875 if not isinstance(user_id, Optional):
1876 if not isinstance(user_id, Optional):
1876 q = q.filter(Repository.user_id == user_id)
1877 q = q.filter(Repository.user_id == user_id)
1877
1878
1878 if not isinstance(group_id, Optional):
1879 if not isinstance(group_id, Optional):
1879 q = q.filter(Repository.group_id == group_id)
1880 q = q.filter(Repository.group_id == group_id)
1880
1881
1881 if case_insensitive:
1882 if case_insensitive:
1882 q = q.order_by(func.lower(Repository.repo_name))
1883 q = q.order_by(func.lower(Repository.repo_name))
1883 else:
1884 else:
1884 q = q.order_by(Repository.repo_name)
1885 q = q.order_by(Repository.repo_name)
1885
1886
1886 return q.all()
1887 return q.all()
1887
1888
1888 @property
1889 @property
1889 def repo_uid(self):
1890 def repo_uid(self):
1890 return '_{}'.format(self.repo_id)
1891 return '_{}'.format(self.repo_id)
1891
1892
1892 @property
1893 @property
1893 def forks(self):
1894 def forks(self):
1894 """
1895 """
1895 Return forks of this repo
1896 Return forks of this repo
1896 """
1897 """
1897 return Repository.get_repo_forks(self.repo_id)
1898 return Repository.get_repo_forks(self.repo_id)
1898
1899
1899 @property
1900 @property
1900 def parent(self):
1901 def parent(self):
1901 """
1902 """
1902 Returns fork parent
1903 Returns fork parent
1903 """
1904 """
1904 return self.fork
1905 return self.fork
1905
1906
1906 @property
1907 @property
1907 def just_name(self):
1908 def just_name(self):
1908 return self.repo_name.split(self.NAME_SEP)[-1]
1909 return self.repo_name.split(self.NAME_SEP)[-1]
1909
1910
1910 @property
1911 @property
1911 def groups_with_parents(self):
1912 def groups_with_parents(self):
1912 groups = []
1913 groups = []
1913 if self.group is None:
1914 if self.group is None:
1914 return groups
1915 return groups
1915
1916
1916 cur_gr = self.group
1917 cur_gr = self.group
1917 groups.insert(0, cur_gr)
1918 groups.insert(0, cur_gr)
1918 while 1:
1919 while 1:
1919 gr = getattr(cur_gr, 'parent_group', None)
1920 gr = getattr(cur_gr, 'parent_group', None)
1920 cur_gr = cur_gr.parent_group
1921 cur_gr = cur_gr.parent_group
1921 if gr is None:
1922 if gr is None:
1922 break
1923 break
1923 groups.insert(0, gr)
1924 groups.insert(0, gr)
1924
1925
1925 return groups
1926 return groups
1926
1927
1927 @property
1928 @property
1928 def groups_and_repo(self):
1929 def groups_and_repo(self):
1929 return self.groups_with_parents, self
1930 return self.groups_with_parents, self
1930
1931
1931 @LazyProperty
1932 @LazyProperty
1932 def repo_path(self):
1933 def repo_path(self):
1933 """
1934 """
1934 Returns base full path for that repository means where it actually
1935 Returns base full path for that repository means where it actually
1935 exists on a filesystem
1936 exists on a filesystem
1936 """
1937 """
1937 q = Session().query(RhodeCodeUi).filter(
1938 q = Session().query(RhodeCodeUi).filter(
1938 RhodeCodeUi.ui_key == self.NAME_SEP)
1939 RhodeCodeUi.ui_key == self.NAME_SEP)
1939 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1940 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1940 return q.one().ui_value
1941 return q.one().ui_value
1941
1942
1942 @property
1943 @property
1943 def repo_full_path(self):
1944 def repo_full_path(self):
1944 p = [self.repo_path]
1945 p = [self.repo_path]
1945 # we need to split the name by / since this is how we store the
1946 # we need to split the name by / since this is how we store the
1946 # names in the database, but that eventually needs to be converted
1947 # names in the database, but that eventually needs to be converted
1947 # into a valid system path
1948 # into a valid system path
1948 p += self.repo_name.split(self.NAME_SEP)
1949 p += self.repo_name.split(self.NAME_SEP)
1949 return os.path.join(*map(safe_unicode, p))
1950 return os.path.join(*map(safe_unicode, p))
1950
1951
1951 @property
1952 @property
1952 def cache_keys(self):
1953 def cache_keys(self):
1953 """
1954 """
1954 Returns associated cache keys for that repo
1955 Returns associated cache keys for that repo
1955 """
1956 """
1956 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1957 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1957 repo_id=self.repo_id)
1958 repo_id=self.repo_id)
1958 return CacheKey.query()\
1959 return CacheKey.query()\
1959 .filter(CacheKey.cache_args == invalidation_namespace)\
1960 .filter(CacheKey.cache_args == invalidation_namespace)\
1960 .order_by(CacheKey.cache_key)\
1961 .order_by(CacheKey.cache_key)\
1961 .all()
1962 .all()
1962
1963
1963 @property
1964 @property
1964 def cached_diffs_relative_dir(self):
1965 def cached_diffs_relative_dir(self):
1965 """
1966 """
1966 Return a relative to the repository store path of cached diffs
1967 Return a relative to the repository store path of cached diffs
1967 used for safe display for users, who shouldn't know the absolute store
1968 used for safe display for users, who shouldn't know the absolute store
1968 path
1969 path
1969 """
1970 """
1970 return os.path.join(
1971 return os.path.join(
1971 os.path.dirname(self.repo_name),
1972 os.path.dirname(self.repo_name),
1972 self.cached_diffs_dir.split(os.path.sep)[-1])
1973 self.cached_diffs_dir.split(os.path.sep)[-1])
1973
1974
1974 @property
1975 @property
1975 def cached_diffs_dir(self):
1976 def cached_diffs_dir(self):
1976 path = self.repo_full_path
1977 path = self.repo_full_path
1977 return os.path.join(
1978 return os.path.join(
1978 os.path.dirname(path),
1979 os.path.dirname(path),
1979 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1980 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1980
1981
1981 def cached_diffs(self):
1982 def cached_diffs(self):
1982 diff_cache_dir = self.cached_diffs_dir
1983 diff_cache_dir = self.cached_diffs_dir
1983 if os.path.isdir(diff_cache_dir):
1984 if os.path.isdir(diff_cache_dir):
1984 return os.listdir(diff_cache_dir)
1985 return os.listdir(diff_cache_dir)
1985 return []
1986 return []
1986
1987
1987 def shadow_repos(self):
1988 def shadow_repos(self):
1988 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1989 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1989 return [
1990 return [
1990 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1991 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1991 if x.startswith(shadow_repos_pattern)]
1992 if x.startswith(shadow_repos_pattern)]
1992
1993
1993 def get_new_name(self, repo_name):
1994 def get_new_name(self, repo_name):
1994 """
1995 """
1995 returns new full repository name based on assigned group and new new
1996 returns new full repository name based on assigned group and new new
1996
1997
1997 :param group_name:
1998 :param group_name:
1998 """
1999 """
1999 path_prefix = self.group.full_path_splitted if self.group else []
2000 path_prefix = self.group.full_path_splitted if self.group else []
2000 return self.NAME_SEP.join(path_prefix + [repo_name])
2001 return self.NAME_SEP.join(path_prefix + [repo_name])
2001
2002
2002 @property
2003 @property
2003 def _config(self):
2004 def _config(self):
2004 """
2005 """
2005 Returns db based config object.
2006 Returns db based config object.
2006 """
2007 """
2007 from rhodecode.lib.utils import make_db_config
2008 from rhodecode.lib.utils import make_db_config
2008 return make_db_config(clear_session=False, repo=self)
2009 return make_db_config(clear_session=False, repo=self)
2009
2010
2010 def permissions(self, with_admins=True, with_owner=True,
2011 def permissions(self, with_admins=True, with_owner=True,
2011 expand_from_user_groups=False):
2012 expand_from_user_groups=False):
2012 """
2013 """
2013 Permissions for repositories
2014 Permissions for repositories
2014 """
2015 """
2015 _admin_perm = 'repository.admin'
2016 _admin_perm = 'repository.admin'
2016
2017
2017 owner_row = []
2018 owner_row = []
2018 if with_owner:
2019 if with_owner:
2019 usr = AttributeDict(self.user.get_dict())
2020 usr = AttributeDict(self.user.get_dict())
2020 usr.owner_row = True
2021 usr.owner_row = True
2021 usr.permission = _admin_perm
2022 usr.permission = _admin_perm
2022 usr.permission_id = None
2023 usr.permission_id = None
2023 owner_row.append(usr)
2024 owner_row.append(usr)
2024
2025
2025 super_admin_ids = []
2026 super_admin_ids = []
2026 super_admin_rows = []
2027 super_admin_rows = []
2027 if with_admins:
2028 if with_admins:
2028 for usr in User.get_all_super_admins():
2029 for usr in User.get_all_super_admins():
2029 super_admin_ids.append(usr.user_id)
2030 super_admin_ids.append(usr.user_id)
2030 # if this admin is also owner, don't double the record
2031 # if this admin is also owner, don't double the record
2031 if usr.user_id == owner_row[0].user_id:
2032 if usr.user_id == owner_row[0].user_id:
2032 owner_row[0].admin_row = True
2033 owner_row[0].admin_row = True
2033 else:
2034 else:
2034 usr = AttributeDict(usr.get_dict())
2035 usr = AttributeDict(usr.get_dict())
2035 usr.admin_row = True
2036 usr.admin_row = True
2036 usr.permission = _admin_perm
2037 usr.permission = _admin_perm
2037 usr.permission_id = None
2038 usr.permission_id = None
2038 super_admin_rows.append(usr)
2039 super_admin_rows.append(usr)
2039
2040
2040 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2041 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2041 q = q.options(joinedload(UserRepoToPerm.repository),
2042 q = q.options(joinedload(UserRepoToPerm.repository),
2042 joinedload(UserRepoToPerm.user),
2043 joinedload(UserRepoToPerm.user),
2043 joinedload(UserRepoToPerm.permission),)
2044 joinedload(UserRepoToPerm.permission),)
2044
2045
2045 # get owners and admins and permissions. We do a trick of re-writing
2046 # get owners and admins and permissions. We do a trick of re-writing
2046 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2047 # has a global reference and changing one object propagates to all
2048 # has a global reference and changing one object propagates to all
2048 # others. This means if admin is also an owner admin_row that change
2049 # others. This means if admin is also an owner admin_row that change
2049 # would propagate to both objects
2050 # would propagate to both objects
2050 perm_rows = []
2051 perm_rows = []
2051 for _usr in q.all():
2052 for _usr in q.all():
2052 usr = AttributeDict(_usr.user.get_dict())
2053 usr = AttributeDict(_usr.user.get_dict())
2053 # if this user is also owner/admin, mark as duplicate record
2054 # if this user is also owner/admin, mark as duplicate record
2054 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2055 usr.duplicate_perm = True
2056 usr.duplicate_perm = True
2056 # also check if this permission is maybe used by branch_permissions
2057 # also check if this permission is maybe used by branch_permissions
2057 if _usr.branch_perm_entry:
2058 if _usr.branch_perm_entry:
2058 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2059 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2059
2060
2060 usr.permission = _usr.permission.permission_name
2061 usr.permission = _usr.permission.permission_name
2061 usr.permission_id = _usr.repo_to_perm_id
2062 usr.permission_id = _usr.repo_to_perm_id
2062 perm_rows.append(usr)
2063 perm_rows.append(usr)
2063
2064
2064 # filter the perm rows by 'default' first and then sort them by
2065 # filter the perm rows by 'default' first and then sort them by
2065 # admin,write,read,none permissions sorted again alphabetically in
2066 # admin,write,read,none permissions sorted again alphabetically in
2066 # each group
2067 # each group
2067 perm_rows = sorted(perm_rows, key=display_user_sort)
2068 perm_rows = sorted(perm_rows, key=display_user_sort)
2068
2069
2069 user_groups_rows = []
2070 user_groups_rows = []
2070 if expand_from_user_groups:
2071 if expand_from_user_groups:
2071 for ug in self.permission_user_groups(with_members=True):
2072 for ug in self.permission_user_groups(with_members=True):
2072 for user_data in ug.members:
2073 for user_data in ug.members:
2073 user_groups_rows.append(user_data)
2074 user_groups_rows.append(user_data)
2074
2075
2075 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2076 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2076
2077
2077 def permission_user_groups(self, with_members=True):
2078 def permission_user_groups(self, with_members=True):
2078 q = UserGroupRepoToPerm.query()\
2079 q = UserGroupRepoToPerm.query()\
2079 .filter(UserGroupRepoToPerm.repository == self)
2080 .filter(UserGroupRepoToPerm.repository == self)
2080 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2081 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2081 joinedload(UserGroupRepoToPerm.users_group),
2082 joinedload(UserGroupRepoToPerm.users_group),
2082 joinedload(UserGroupRepoToPerm.permission),)
2083 joinedload(UserGroupRepoToPerm.permission),)
2083
2084
2084 perm_rows = []
2085 perm_rows = []
2085 for _user_group in q.all():
2086 for _user_group in q.all():
2086 entry = AttributeDict(_user_group.users_group.get_dict())
2087 entry = AttributeDict(_user_group.users_group.get_dict())
2087 entry.permission = _user_group.permission.permission_name
2088 entry.permission = _user_group.permission.permission_name
2088 if with_members:
2089 if with_members:
2089 entry.members = [x.user.get_dict()
2090 entry.members = [x.user.get_dict()
2090 for x in _user_group.users_group.members]
2091 for x in _user_group.users_group.members]
2091 perm_rows.append(entry)
2092 perm_rows.append(entry)
2092
2093
2093 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2094 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2094 return perm_rows
2095 return perm_rows
2095
2096
2096 def get_api_data(self, include_secrets=False):
2097 def get_api_data(self, include_secrets=False):
2097 """
2098 """
2098 Common function for generating repo api data
2099 Common function for generating repo api data
2099
2100
2100 :param include_secrets: See :meth:`User.get_api_data`.
2101 :param include_secrets: See :meth:`User.get_api_data`.
2101
2102
2102 """
2103 """
2103 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2104 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2104 # move this methods on models level.
2105 # move this methods on models level.
2105 from rhodecode.model.settings import SettingsModel
2106 from rhodecode.model.settings import SettingsModel
2106 from rhodecode.model.repo import RepoModel
2107 from rhodecode.model.repo import RepoModel
2107
2108
2108 repo = self
2109 repo = self
2109 _user_id, _time, _reason = self.locked
2110 _user_id, _time, _reason = self.locked
2110
2111
2111 data = {
2112 data = {
2112 'repo_id': repo.repo_id,
2113 'repo_id': repo.repo_id,
2113 'repo_name': repo.repo_name,
2114 'repo_name': repo.repo_name,
2114 'repo_type': repo.repo_type,
2115 'repo_type': repo.repo_type,
2115 'clone_uri': repo.clone_uri or '',
2116 'clone_uri': repo.clone_uri or '',
2116 'push_uri': repo.push_uri or '',
2117 'push_uri': repo.push_uri or '',
2117 'url': RepoModel().get_url(self),
2118 'url': RepoModel().get_url(self),
2118 'private': repo.private,
2119 'private': repo.private,
2119 'created_on': repo.created_on,
2120 'created_on': repo.created_on,
2120 'description': repo.description_safe,
2121 'description': repo.description_safe,
2121 'landing_rev': repo.landing_rev,
2122 'landing_rev': repo.landing_rev,
2122 'owner': repo.user.username,
2123 'owner': repo.user.username,
2123 'fork_of': repo.fork.repo_name if repo.fork else None,
2124 'fork_of': repo.fork.repo_name if repo.fork else None,
2124 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2125 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2125 'enable_statistics': repo.enable_statistics,
2126 'enable_statistics': repo.enable_statistics,
2126 'enable_locking': repo.enable_locking,
2127 'enable_locking': repo.enable_locking,
2127 'enable_downloads': repo.enable_downloads,
2128 'enable_downloads': repo.enable_downloads,
2128 'last_changeset': repo.changeset_cache,
2129 'last_changeset': repo.changeset_cache,
2129 'locked_by': User.get(_user_id).get_api_data(
2130 'locked_by': User.get(_user_id).get_api_data(
2130 include_secrets=include_secrets) if _user_id else None,
2131 include_secrets=include_secrets) if _user_id else None,
2131 'locked_date': time_to_datetime(_time) if _time else None,
2132 'locked_date': time_to_datetime(_time) if _time else None,
2132 'lock_reason': _reason if _reason else None,
2133 'lock_reason': _reason if _reason else None,
2133 }
2134 }
2134
2135
2135 # TODO: mikhail: should be per-repo settings here
2136 # TODO: mikhail: should be per-repo settings here
2136 rc_config = SettingsModel().get_all_settings()
2137 rc_config = SettingsModel().get_all_settings()
2137 repository_fields = str2bool(
2138 repository_fields = str2bool(
2138 rc_config.get('rhodecode_repository_fields'))
2139 rc_config.get('rhodecode_repository_fields'))
2139 if repository_fields:
2140 if repository_fields:
2140 for f in self.extra_fields:
2141 for f in self.extra_fields:
2141 data[f.field_key_prefixed] = f.field_value
2142 data[f.field_key_prefixed] = f.field_value
2142
2143
2143 return data
2144 return data
2144
2145
2145 @classmethod
2146 @classmethod
2146 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2147 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2147 if not lock_time:
2148 if not lock_time:
2148 lock_time = time.time()
2149 lock_time = time.time()
2149 if not lock_reason:
2150 if not lock_reason:
2150 lock_reason = cls.LOCK_AUTOMATIC
2151 lock_reason = cls.LOCK_AUTOMATIC
2151 repo.locked = [user_id, lock_time, lock_reason]
2152 repo.locked = [user_id, lock_time, lock_reason]
2152 Session().add(repo)
2153 Session().add(repo)
2153 Session().commit()
2154 Session().commit()
2154
2155
2155 @classmethod
2156 @classmethod
2156 def unlock(cls, repo):
2157 def unlock(cls, repo):
2157 repo.locked = None
2158 repo.locked = None
2158 Session().add(repo)
2159 Session().add(repo)
2159 Session().commit()
2160 Session().commit()
2160
2161
2161 @classmethod
2162 @classmethod
2162 def getlock(cls, repo):
2163 def getlock(cls, repo):
2163 return repo.locked
2164 return repo.locked
2164
2165
2165 def is_user_lock(self, user_id):
2166 def is_user_lock(self, user_id):
2166 if self.lock[0]:
2167 if self.lock[0]:
2167 lock_user_id = safe_int(self.lock[0])
2168 lock_user_id = safe_int(self.lock[0])
2168 user_id = safe_int(user_id)
2169 user_id = safe_int(user_id)
2169 # both are ints, and they are equal
2170 # both are ints, and they are equal
2170 return all([lock_user_id, user_id]) and lock_user_id == user_id
2171 return all([lock_user_id, user_id]) and lock_user_id == user_id
2171
2172
2172 return False
2173 return False
2173
2174
2174 def get_locking_state(self, action, user_id, only_when_enabled=True):
2175 def get_locking_state(self, action, user_id, only_when_enabled=True):
2175 """
2176 """
2176 Checks locking on this repository, if locking is enabled and lock is
2177 Checks locking on this repository, if locking is enabled and lock is
2177 present returns a tuple of make_lock, locked, locked_by.
2178 present returns a tuple of make_lock, locked, locked_by.
2178 make_lock can have 3 states None (do nothing) True, make lock
2179 make_lock can have 3 states None (do nothing) True, make lock
2179 False release lock, This value is later propagated to hooks, which
2180 False release lock, This value is later propagated to hooks, which
2180 do the locking. Think about this as signals passed to hooks what to do.
2181 do the locking. Think about this as signals passed to hooks what to do.
2181
2182
2182 """
2183 """
2183 # TODO: johbo: This is part of the business logic and should be moved
2184 # TODO: johbo: This is part of the business logic and should be moved
2184 # into the RepositoryModel.
2185 # into the RepositoryModel.
2185
2186
2186 if action not in ('push', 'pull'):
2187 if action not in ('push', 'pull'):
2187 raise ValueError("Invalid action value: %s" % repr(action))
2188 raise ValueError("Invalid action value: %s" % repr(action))
2188
2189
2189 # defines if locked error should be thrown to user
2190 # defines if locked error should be thrown to user
2190 currently_locked = False
2191 currently_locked = False
2191 # defines if new lock should be made, tri-state
2192 # defines if new lock should be made, tri-state
2192 make_lock = None
2193 make_lock = None
2193 repo = self
2194 repo = self
2194 user = User.get(user_id)
2195 user = User.get(user_id)
2195
2196
2196 lock_info = repo.locked
2197 lock_info = repo.locked
2197
2198
2198 if repo and (repo.enable_locking or not only_when_enabled):
2199 if repo and (repo.enable_locking or not only_when_enabled):
2199 if action == 'push':
2200 if action == 'push':
2200 # check if it's already locked !, if it is compare users
2201 # check if it's already locked !, if it is compare users
2201 locked_by_user_id = lock_info[0]
2202 locked_by_user_id = lock_info[0]
2202 if user.user_id == locked_by_user_id:
2203 if user.user_id == locked_by_user_id:
2203 log.debug(
2204 log.debug(
2204 'Got `push` action from user %s, now unlocking', user)
2205 'Got `push` action from user %s, now unlocking', user)
2205 # unlock if we have push from user who locked
2206 # unlock if we have push from user who locked
2206 make_lock = False
2207 make_lock = False
2207 else:
2208 else:
2208 # we're not the same user who locked, ban with
2209 # we're not the same user who locked, ban with
2209 # code defined in settings (default is 423 HTTP Locked) !
2210 # code defined in settings (default is 423 HTTP Locked) !
2210 log.debug('Repo %s is currently locked by %s', repo, user)
2211 log.debug('Repo %s is currently locked by %s', repo, user)
2211 currently_locked = True
2212 currently_locked = True
2212 elif action == 'pull':
2213 elif action == 'pull':
2213 # [0] user [1] date
2214 # [0] user [1] date
2214 if lock_info[0] and lock_info[1]:
2215 if lock_info[0] and lock_info[1]:
2215 log.debug('Repo %s is currently locked by %s', repo, user)
2216 log.debug('Repo %s is currently locked by %s', repo, user)
2216 currently_locked = True
2217 currently_locked = True
2217 else:
2218 else:
2218 log.debug('Setting lock on repo %s by %s', repo, user)
2219 log.debug('Setting lock on repo %s by %s', repo, user)
2219 make_lock = True
2220 make_lock = True
2220
2221
2221 else:
2222 else:
2222 log.debug('Repository %s do not have locking enabled', repo)
2223 log.debug('Repository %s do not have locking enabled', repo)
2223
2224
2224 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2225 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2225 make_lock, currently_locked, lock_info)
2226 make_lock, currently_locked, lock_info)
2226
2227
2227 from rhodecode.lib.auth import HasRepoPermissionAny
2228 from rhodecode.lib.auth import HasRepoPermissionAny
2228 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2229 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2229 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2230 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2230 # if we don't have at least write permission we cannot make a lock
2231 # if we don't have at least write permission we cannot make a lock
2231 log.debug('lock state reset back to FALSE due to lack '
2232 log.debug('lock state reset back to FALSE due to lack '
2232 'of at least read permission')
2233 'of at least read permission')
2233 make_lock = False
2234 make_lock = False
2234
2235
2235 return make_lock, currently_locked, lock_info
2236 return make_lock, currently_locked, lock_info
2236
2237
2237 @property
2238 @property
2238 def last_commit_cache_update_diff(self):
2239 def last_commit_cache_update_diff(self):
2239 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2240 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2240
2241
2241 @property
2242 @property
2242 def last_commit_change(self):
2243 def last_commit_change(self):
2243 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2244 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2244 empty_date = datetime.datetime.fromtimestamp(0)
2245 empty_date = datetime.datetime.fromtimestamp(0)
2245 date_latest = self.changeset_cache.get('date', empty_date)
2246 date_latest = self.changeset_cache.get('date', empty_date)
2246 try:
2247 try:
2247 return parse_datetime(date_latest)
2248 return parse_datetime(date_latest)
2248 except Exception:
2249 except Exception:
2249 return empty_date
2250 return empty_date
2250
2251
2251 @property
2252 @property
2252 def last_db_change(self):
2253 def last_db_change(self):
2253 return self.updated_on
2254 return self.updated_on
2254
2255
2255 @property
2256 @property
2256 def clone_uri_hidden(self):
2257 def clone_uri_hidden(self):
2257 clone_uri = self.clone_uri
2258 clone_uri = self.clone_uri
2258 if clone_uri:
2259 if clone_uri:
2259 import urlobject
2260 import urlobject
2260 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2261 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2261 if url_obj.password:
2262 if url_obj.password:
2262 clone_uri = url_obj.with_password('*****')
2263 clone_uri = url_obj.with_password('*****')
2263 return clone_uri
2264 return clone_uri
2264
2265
2265 @property
2266 @property
2266 def push_uri_hidden(self):
2267 def push_uri_hidden(self):
2267 push_uri = self.push_uri
2268 push_uri = self.push_uri
2268 if push_uri:
2269 if push_uri:
2269 import urlobject
2270 import urlobject
2270 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2271 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2271 if url_obj.password:
2272 if url_obj.password:
2272 push_uri = url_obj.with_password('*****')
2273 push_uri = url_obj.with_password('*****')
2273 return push_uri
2274 return push_uri
2274
2275
2275 def clone_url(self, **override):
2276 def clone_url(self, **override):
2276 from rhodecode.model.settings import SettingsModel
2277 from rhodecode.model.settings import SettingsModel
2277
2278
2278 uri_tmpl = None
2279 uri_tmpl = None
2279 if 'with_id' in override:
2280 if 'with_id' in override:
2280 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2281 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2281 del override['with_id']
2282 del override['with_id']
2282
2283
2283 if 'uri_tmpl' in override:
2284 if 'uri_tmpl' in override:
2284 uri_tmpl = override['uri_tmpl']
2285 uri_tmpl = override['uri_tmpl']
2285 del override['uri_tmpl']
2286 del override['uri_tmpl']
2286
2287
2287 ssh = False
2288 ssh = False
2288 if 'ssh' in override:
2289 if 'ssh' in override:
2289 ssh = True
2290 ssh = True
2290 del override['ssh']
2291 del override['ssh']
2291
2292
2292 # we didn't override our tmpl from **overrides
2293 # we didn't override our tmpl from **overrides
2293 request = get_current_request()
2294 request = get_current_request()
2294 if not uri_tmpl:
2295 if not uri_tmpl:
2295 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2296 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2296 rc_config = request.call_context.rc_config
2297 rc_config = request.call_context.rc_config
2297 else:
2298 else:
2298 rc_config = SettingsModel().get_all_settings(cache=True)
2299 rc_config = SettingsModel().get_all_settings(cache=True)
2299 if ssh:
2300 if ssh:
2300 uri_tmpl = rc_config.get(
2301 uri_tmpl = rc_config.get(
2301 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2302 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2302 else:
2303 else:
2303 uri_tmpl = rc_config.get(
2304 uri_tmpl = rc_config.get(
2304 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2305 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2305
2306
2306 return get_clone_url(request=request,
2307 return get_clone_url(request=request,
2307 uri_tmpl=uri_tmpl,
2308 uri_tmpl=uri_tmpl,
2308 repo_name=self.repo_name,
2309 repo_name=self.repo_name,
2309 repo_id=self.repo_id, **override)
2310 repo_id=self.repo_id, **override)
2310
2311
2311 def set_state(self, state):
2312 def set_state(self, state):
2312 self.repo_state = state
2313 self.repo_state = state
2313 Session().add(self)
2314 Session().add(self)
2314 #==========================================================================
2315 #==========================================================================
2315 # SCM PROPERTIES
2316 # SCM PROPERTIES
2316 #==========================================================================
2317 #==========================================================================
2317
2318
2318 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2319 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2319 return get_commit_safe(
2320 return get_commit_safe(
2320 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2321 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2321
2322
2322 def get_changeset(self, rev=None, pre_load=None):
2323 def get_changeset(self, rev=None, pre_load=None):
2323 warnings.warn("Use get_commit", DeprecationWarning)
2324 warnings.warn("Use get_commit", DeprecationWarning)
2324 commit_id = None
2325 commit_id = None
2325 commit_idx = None
2326 commit_idx = None
2326 if isinstance(rev, compat.string_types):
2327 if isinstance(rev, compat.string_types):
2327 commit_id = rev
2328 commit_id = rev
2328 else:
2329 else:
2329 commit_idx = rev
2330 commit_idx = rev
2330 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2331 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2331 pre_load=pre_load)
2332 pre_load=pre_load)
2332
2333
2333 def get_landing_commit(self):
2334 def get_landing_commit(self):
2334 """
2335 """
2335 Returns landing commit, or if that doesn't exist returns the tip
2336 Returns landing commit, or if that doesn't exist returns the tip
2336 """
2337 """
2337 _rev_type, _rev = self.landing_rev
2338 _rev_type, _rev = self.landing_rev
2338 commit = self.get_commit(_rev)
2339 commit = self.get_commit(_rev)
2339 if isinstance(commit, EmptyCommit):
2340 if isinstance(commit, EmptyCommit):
2340 return self.get_commit()
2341 return self.get_commit()
2341 return commit
2342 return commit
2342
2343
2343 def flush_commit_cache(self):
2344 def flush_commit_cache(self):
2344 self.update_commit_cache(cs_cache={'raw_id':'0'})
2345 self.update_commit_cache(cs_cache={'raw_id':'0'})
2345 self.update_commit_cache()
2346 self.update_commit_cache()
2346
2347
2347 def update_commit_cache(self, cs_cache=None, config=None):
2348 def update_commit_cache(self, cs_cache=None, config=None):
2348 """
2349 """
2349 Update cache of last commit for repository, keys should be::
2350 Update cache of last commit for repository, keys should be::
2350
2351
2351 source_repo_id
2352 source_repo_id
2352 short_id
2353 short_id
2353 raw_id
2354 raw_id
2354 revision
2355 revision
2355 parents
2356 parents
2356 message
2357 message
2357 date
2358 date
2358 author
2359 author
2359 updated_on
2360 updated_on
2360
2361
2361 """
2362 """
2362 from rhodecode.lib.vcs.backends.base import BaseChangeset
2363 from rhodecode.lib.vcs.backends.base import BaseChangeset
2363 if cs_cache is None:
2364 if cs_cache is None:
2364 # use no-cache version here
2365 # use no-cache version here
2365 scm_repo = self.scm_instance(cache=False, config=config)
2366 scm_repo = self.scm_instance(cache=False, config=config)
2366
2367
2367 empty = scm_repo is None or scm_repo.is_empty()
2368 empty = scm_repo is None or scm_repo.is_empty()
2368 if not empty:
2369 if not empty:
2369 cs_cache = scm_repo.get_commit(
2370 cs_cache = scm_repo.get_commit(
2370 pre_load=["author", "date", "message", "parents", "branch"])
2371 pre_load=["author", "date", "message", "parents", "branch"])
2371 else:
2372 else:
2372 cs_cache = EmptyCommit()
2373 cs_cache = EmptyCommit()
2373
2374
2374 if isinstance(cs_cache, BaseChangeset):
2375 if isinstance(cs_cache, BaseChangeset):
2375 cs_cache = cs_cache.__json__()
2376 cs_cache = cs_cache.__json__()
2376
2377
2377 def is_outdated(new_cs_cache):
2378 def is_outdated(new_cs_cache):
2378 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2379 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2379 new_cs_cache['revision'] != self.changeset_cache['revision']):
2380 new_cs_cache['revision'] != self.changeset_cache['revision']):
2380 return True
2381 return True
2381 return False
2382 return False
2382
2383
2383 # check if we have maybe already latest cached revision
2384 # check if we have maybe already latest cached revision
2384 if is_outdated(cs_cache) or not self.changeset_cache:
2385 if is_outdated(cs_cache) or not self.changeset_cache:
2385 _default = datetime.datetime.utcnow()
2386 _default = datetime.datetime.utcnow()
2386 last_change = cs_cache.get('date') or _default
2387 last_change = cs_cache.get('date') or _default
2387 # we check if last update is newer than the new value
2388 # we check if last update is newer than the new value
2388 # if yes, we use the current timestamp instead. Imagine you get
2389 # if yes, we use the current timestamp instead. Imagine you get
2389 # old commit pushed 1y ago, we'd set last update 1y to ago.
2390 # old commit pushed 1y ago, we'd set last update 1y to ago.
2390 last_change_timestamp = datetime_to_time(last_change)
2391 last_change_timestamp = datetime_to_time(last_change)
2391 current_timestamp = datetime_to_time(last_change)
2392 current_timestamp = datetime_to_time(last_change)
2392 if last_change_timestamp > current_timestamp:
2393 if last_change_timestamp > current_timestamp:
2393 cs_cache['date'] = _default
2394 cs_cache['date'] = _default
2394
2395
2395 cs_cache['updated_on'] = time.time()
2396 cs_cache['updated_on'] = time.time()
2396 self.changeset_cache = cs_cache
2397 self.changeset_cache = cs_cache
2397 self.updated_on = last_change
2398 self.updated_on = last_change
2398 Session().add(self)
2399 Session().add(self)
2399 Session().commit()
2400 Session().commit()
2400
2401
2401 log.debug('updated repo `%s` with new commit cache %s',
2402 log.debug('updated repo `%s` with new commit cache %s',
2402 self.repo_name, cs_cache)
2403 self.repo_name, cs_cache)
2403 else:
2404 else:
2404 cs_cache = self.changeset_cache
2405 cs_cache = self.changeset_cache
2405 cs_cache['updated_on'] = time.time()
2406 cs_cache['updated_on'] = time.time()
2406 self.changeset_cache = cs_cache
2407 self.changeset_cache = cs_cache
2407 Session().add(self)
2408 Session().add(self)
2408 Session().commit()
2409 Session().commit()
2409
2410
2410 log.debug('Skipping update_commit_cache for repo:`%s` '
2411 log.debug('Skipping update_commit_cache for repo:`%s` '
2411 'commit already with latest changes', self.repo_name)
2412 'commit already with latest changes', self.repo_name)
2412
2413
2413 @property
2414 @property
2414 def tip(self):
2415 def tip(self):
2415 return self.get_commit('tip')
2416 return self.get_commit('tip')
2416
2417
2417 @property
2418 @property
2418 def author(self):
2419 def author(self):
2419 return self.tip.author
2420 return self.tip.author
2420
2421
2421 @property
2422 @property
2422 def last_change(self):
2423 def last_change(self):
2423 return self.scm_instance().last_change
2424 return self.scm_instance().last_change
2424
2425
2425 def get_comments(self, revisions=None):
2426 def get_comments(self, revisions=None):
2426 """
2427 """
2427 Returns comments for this repository grouped by revisions
2428 Returns comments for this repository grouped by revisions
2428
2429
2429 :param revisions: filter query by revisions only
2430 :param revisions: filter query by revisions only
2430 """
2431 """
2431 cmts = ChangesetComment.query()\
2432 cmts = ChangesetComment.query()\
2432 .filter(ChangesetComment.repo == self)
2433 .filter(ChangesetComment.repo == self)
2433 if revisions:
2434 if revisions:
2434 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2435 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2435 grouped = collections.defaultdict(list)
2436 grouped = collections.defaultdict(list)
2436 for cmt in cmts.all():
2437 for cmt in cmts.all():
2437 grouped[cmt.revision].append(cmt)
2438 grouped[cmt.revision].append(cmt)
2438 return grouped
2439 return grouped
2439
2440
2440 def statuses(self, revisions=None):
2441 def statuses(self, revisions=None):
2441 """
2442 """
2442 Returns statuses for this repository
2443 Returns statuses for this repository
2443
2444
2444 :param revisions: list of revisions to get statuses for
2445 :param revisions: list of revisions to get statuses for
2445 """
2446 """
2446 statuses = ChangesetStatus.query()\
2447 statuses = ChangesetStatus.query()\
2447 .filter(ChangesetStatus.repo == self)\
2448 .filter(ChangesetStatus.repo == self)\
2448 .filter(ChangesetStatus.version == 0)
2449 .filter(ChangesetStatus.version == 0)
2449
2450
2450 if revisions:
2451 if revisions:
2451 # Try doing the filtering in chunks to avoid hitting limits
2452 # Try doing the filtering in chunks to avoid hitting limits
2452 size = 500
2453 size = 500
2453 status_results = []
2454 status_results = []
2454 for chunk in xrange(0, len(revisions), size):
2455 for chunk in xrange(0, len(revisions), size):
2455 status_results += statuses.filter(
2456 status_results += statuses.filter(
2456 ChangesetStatus.revision.in_(
2457 ChangesetStatus.revision.in_(
2457 revisions[chunk: chunk+size])
2458 revisions[chunk: chunk+size])
2458 ).all()
2459 ).all()
2459 else:
2460 else:
2460 status_results = statuses.all()
2461 status_results = statuses.all()
2461
2462
2462 grouped = {}
2463 grouped = {}
2463
2464
2464 # maybe we have open new pullrequest without a status?
2465 # maybe we have open new pullrequest without a status?
2465 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2466 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2466 status_lbl = ChangesetStatus.get_status_lbl(stat)
2467 status_lbl = ChangesetStatus.get_status_lbl(stat)
2467 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2468 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2468 for rev in pr.revisions:
2469 for rev in pr.revisions:
2469 pr_id = pr.pull_request_id
2470 pr_id = pr.pull_request_id
2470 pr_repo = pr.target_repo.repo_name
2471 pr_repo = pr.target_repo.repo_name
2471 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2472 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2472
2473
2473 for stat in status_results:
2474 for stat in status_results:
2474 pr_id = pr_repo = None
2475 pr_id = pr_repo = None
2475 if stat.pull_request:
2476 if stat.pull_request:
2476 pr_id = stat.pull_request.pull_request_id
2477 pr_id = stat.pull_request.pull_request_id
2477 pr_repo = stat.pull_request.target_repo.repo_name
2478 pr_repo = stat.pull_request.target_repo.repo_name
2478 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2479 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2479 pr_id, pr_repo]
2480 pr_id, pr_repo]
2480 return grouped
2481 return grouped
2481
2482
2482 # ==========================================================================
2483 # ==========================================================================
2483 # SCM CACHE INSTANCE
2484 # SCM CACHE INSTANCE
2484 # ==========================================================================
2485 # ==========================================================================
2485
2486
2486 def scm_instance(self, **kwargs):
2487 def scm_instance(self, **kwargs):
2487 import rhodecode
2488 import rhodecode
2488
2489
2489 # Passing a config will not hit the cache currently only used
2490 # Passing a config will not hit the cache currently only used
2490 # for repo2dbmapper
2491 # for repo2dbmapper
2491 config = kwargs.pop('config', None)
2492 config = kwargs.pop('config', None)
2492 cache = kwargs.pop('cache', None)
2493 cache = kwargs.pop('cache', None)
2493 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2494 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2494 if vcs_full_cache is not None:
2495 if vcs_full_cache is not None:
2495 # allows override global config
2496 # allows override global config
2496 full_cache = vcs_full_cache
2497 full_cache = vcs_full_cache
2497 else:
2498 else:
2498 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2499 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2499 # if cache is NOT defined use default global, else we have a full
2500 # if cache is NOT defined use default global, else we have a full
2500 # control over cache behaviour
2501 # control over cache behaviour
2501 if cache is None and full_cache and not config:
2502 if cache is None and full_cache and not config:
2502 log.debug('Initializing pure cached instance for %s', self.repo_path)
2503 log.debug('Initializing pure cached instance for %s', self.repo_path)
2503 return self._get_instance_cached()
2504 return self._get_instance_cached()
2504
2505
2505 # cache here is sent to the "vcs server"
2506 # cache here is sent to the "vcs server"
2506 return self._get_instance(cache=bool(cache), config=config)
2507 return self._get_instance(cache=bool(cache), config=config)
2507
2508
2508 def _get_instance_cached(self):
2509 def _get_instance_cached(self):
2509 from rhodecode.lib import rc_cache
2510 from rhodecode.lib import rc_cache
2510
2511
2511 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2512 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2512 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2513 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2513 repo_id=self.repo_id)
2514 repo_id=self.repo_id)
2514 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2515 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2515
2516
2516 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2517 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2517 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2518 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2518 return self._get_instance(repo_state_uid=_cache_state_uid)
2519 return self._get_instance(repo_state_uid=_cache_state_uid)
2519
2520
2520 # we must use thread scoped cache here,
2521 # we must use thread scoped cache here,
2521 # because each thread of gevent needs it's own not shared connection and cache
2522 # because each thread of gevent needs it's own not shared connection and cache
2522 # we also alter `args` so the cache key is individual for every green thread.
2523 # we also alter `args` so the cache key is individual for every green thread.
2523 inv_context_manager = rc_cache.InvalidationContext(
2524 inv_context_manager = rc_cache.InvalidationContext(
2524 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2525 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2525 thread_scoped=True)
2526 thread_scoped=True)
2526 with inv_context_manager as invalidation_context:
2527 with inv_context_manager as invalidation_context:
2527 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2528 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2528 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2529 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2529
2530
2530 # re-compute and store cache if we get invalidate signal
2531 # re-compute and store cache if we get invalidate signal
2531 if invalidation_context.should_invalidate():
2532 if invalidation_context.should_invalidate():
2532 instance = get_instance_cached.refresh(*args)
2533 instance = get_instance_cached.refresh(*args)
2533 else:
2534 else:
2534 instance = get_instance_cached(*args)
2535 instance = get_instance_cached(*args)
2535
2536
2536 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2537 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2537 return instance
2538 return instance
2538
2539
2539 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2540 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2540 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2541 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2541 self.repo_type, self.repo_path, cache)
2542 self.repo_type, self.repo_path, cache)
2542 config = config or self._config
2543 config = config or self._config
2543 custom_wire = {
2544 custom_wire = {
2544 'cache': cache, # controls the vcs.remote cache
2545 'cache': cache, # controls the vcs.remote cache
2545 'repo_state_uid': repo_state_uid
2546 'repo_state_uid': repo_state_uid
2546 }
2547 }
2547 repo = get_vcs_instance(
2548 repo = get_vcs_instance(
2548 repo_path=safe_str(self.repo_full_path),
2549 repo_path=safe_str(self.repo_full_path),
2549 config=config,
2550 config=config,
2550 with_wire=custom_wire,
2551 with_wire=custom_wire,
2551 create=False,
2552 create=False,
2552 _vcs_alias=self.repo_type)
2553 _vcs_alias=self.repo_type)
2553 if repo is not None:
2554 if repo is not None:
2554 repo.count() # cache rebuild
2555 repo.count() # cache rebuild
2555 return repo
2556 return repo
2556
2557
2557 def get_shadow_repository_path(self, workspace_id):
2558 def get_shadow_repository_path(self, workspace_id):
2558 from rhodecode.lib.vcs.backends.base import BaseRepository
2559 from rhodecode.lib.vcs.backends.base import BaseRepository
2559 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2560 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2560 self.repo_full_path, self.repo_id, workspace_id)
2561 self.repo_full_path, self.repo_id, workspace_id)
2561 return shadow_repo_path
2562 return shadow_repo_path
2562
2563
2563 def __json__(self):
2564 def __json__(self):
2564 return {'landing_rev': self.landing_rev}
2565 return {'landing_rev': self.landing_rev}
2565
2566
2566 def get_dict(self):
2567 def get_dict(self):
2567
2568
2568 # Since we transformed `repo_name` to a hybrid property, we need to
2569 # Since we transformed `repo_name` to a hybrid property, we need to
2569 # keep compatibility with the code which uses `repo_name` field.
2570 # keep compatibility with the code which uses `repo_name` field.
2570
2571
2571 result = super(Repository, self).get_dict()
2572 result = super(Repository, self).get_dict()
2572 result['repo_name'] = result.pop('_repo_name', None)
2573 result['repo_name'] = result.pop('_repo_name', None)
2573 return result
2574 return result
2574
2575
2575
2576
2576 class RepoGroup(Base, BaseModel):
2577 class RepoGroup(Base, BaseModel):
2577 __tablename__ = 'groups'
2578 __tablename__ = 'groups'
2578 __table_args__ = (
2579 __table_args__ = (
2579 UniqueConstraint('group_name', 'group_parent_id'),
2580 UniqueConstraint('group_name', 'group_parent_id'),
2580 base_table_args,
2581 base_table_args,
2581 )
2582 )
2582 __mapper_args__ = {'order_by': 'group_name'}
2583 __mapper_args__ = {'order_by': 'group_name'}
2583
2584
2584 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2585 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2585
2586
2586 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2587 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2587 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2588 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2588 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2589 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2589 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2590 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2590 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2591 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2591 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2592 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2593 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2593 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2594 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2594 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2595 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2595 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2596 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2596 _changeset_cache = Column(
2597 _changeset_cache = Column(
2597 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2598 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2598
2599
2599 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2600 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2600 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2601 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2601 parent_group = relationship('RepoGroup', remote_side=group_id)
2602 parent_group = relationship('RepoGroup', remote_side=group_id)
2602 user = relationship('User')
2603 user = relationship('User')
2603 integrations = relationship('Integration', cascade="all, delete-orphan")
2604 integrations = relationship('Integration', cascade="all, delete-orphan")
2604
2605
2605 # no cascade, set NULL
2606 # no cascade, set NULL
2606 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2607 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2607
2608
2608 def __init__(self, group_name='', parent_group=None):
2609 def __init__(self, group_name='', parent_group=None):
2609 self.group_name = group_name
2610 self.group_name = group_name
2610 self.parent_group = parent_group
2611 self.parent_group = parent_group
2611
2612
2612 def __unicode__(self):
2613 def __unicode__(self):
2613 return u"<%s('id:%s:%s')>" % (
2614 return u"<%s('id:%s:%s')>" % (
2614 self.__class__.__name__, self.group_id, self.group_name)
2615 self.__class__.__name__, self.group_id, self.group_name)
2615
2616
2616 @hybrid_property
2617 @hybrid_property
2617 def group_name(self):
2618 def group_name(self):
2618 return self._group_name
2619 return self._group_name
2619
2620
2620 @group_name.setter
2621 @group_name.setter
2621 def group_name(self, value):
2622 def group_name(self, value):
2622 self._group_name = value
2623 self._group_name = value
2623 self.group_name_hash = self.hash_repo_group_name(value)
2624 self.group_name_hash = self.hash_repo_group_name(value)
2624
2625
2625 @hybrid_property
2626 @hybrid_property
2626 def changeset_cache(self):
2627 def changeset_cache(self):
2627 from rhodecode.lib.vcs.backends.base import EmptyCommit
2628 from rhodecode.lib.vcs.backends.base import EmptyCommit
2628 dummy = EmptyCommit().__json__()
2629 dummy = EmptyCommit().__json__()
2629 if not self._changeset_cache:
2630 if not self._changeset_cache:
2630 dummy['source_repo_id'] = ''
2631 dummy['source_repo_id'] = ''
2631 return json.loads(json.dumps(dummy))
2632 return json.loads(json.dumps(dummy))
2632
2633
2633 try:
2634 try:
2634 return json.loads(self._changeset_cache)
2635 return json.loads(self._changeset_cache)
2635 except TypeError:
2636 except TypeError:
2636 return dummy
2637 return dummy
2637 except Exception:
2638 except Exception:
2638 log.error(traceback.format_exc())
2639 log.error(traceback.format_exc())
2639 return dummy
2640 return dummy
2640
2641
2641 @changeset_cache.setter
2642 @changeset_cache.setter
2642 def changeset_cache(self, val):
2643 def changeset_cache(self, val):
2643 try:
2644 try:
2644 self._changeset_cache = json.dumps(val)
2645 self._changeset_cache = json.dumps(val)
2645 except Exception:
2646 except Exception:
2646 log.error(traceback.format_exc())
2647 log.error(traceback.format_exc())
2647
2648
2648 @validates('group_parent_id')
2649 @validates('group_parent_id')
2649 def validate_group_parent_id(self, key, val):
2650 def validate_group_parent_id(self, key, val):
2650 """
2651 """
2651 Check cycle references for a parent group to self
2652 Check cycle references for a parent group to self
2652 """
2653 """
2653 if self.group_id and val:
2654 if self.group_id and val:
2654 assert val != self.group_id
2655 assert val != self.group_id
2655
2656
2656 return val
2657 return val
2657
2658
2658 @hybrid_property
2659 @hybrid_property
2659 def description_safe(self):
2660 def description_safe(self):
2660 from rhodecode.lib import helpers as h
2661 from rhodecode.lib import helpers as h
2661 return h.escape(self.group_description)
2662 return h.escape(self.group_description)
2662
2663
2663 @classmethod
2664 @classmethod
2664 def hash_repo_group_name(cls, repo_group_name):
2665 def hash_repo_group_name(cls, repo_group_name):
2665 val = remove_formatting(repo_group_name)
2666 val = remove_formatting(repo_group_name)
2666 val = safe_str(val).lower()
2667 val = safe_str(val).lower()
2667 chars = []
2668 chars = []
2668 for c in val:
2669 for c in val:
2669 if c not in string.ascii_letters:
2670 if c not in string.ascii_letters:
2670 c = str(ord(c))
2671 c = str(ord(c))
2671 chars.append(c)
2672 chars.append(c)
2672
2673
2673 return ''.join(chars)
2674 return ''.join(chars)
2674
2675
2675 @classmethod
2676 @classmethod
2676 def _generate_choice(cls, repo_group):
2677 def _generate_choice(cls, repo_group):
2677 from webhelpers.html import literal as _literal
2678 from webhelpers.html import literal as _literal
2678 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2679 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2679 return repo_group.group_id, _name(repo_group.full_path_splitted)
2680 return repo_group.group_id, _name(repo_group.full_path_splitted)
2680
2681
2681 @classmethod
2682 @classmethod
2682 def groups_choices(cls, groups=None, show_empty_group=True):
2683 def groups_choices(cls, groups=None, show_empty_group=True):
2683 if not groups:
2684 if not groups:
2684 groups = cls.query().all()
2685 groups = cls.query().all()
2685
2686
2686 repo_groups = []
2687 repo_groups = []
2687 if show_empty_group:
2688 if show_empty_group:
2688 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2689 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2689
2690
2690 repo_groups.extend([cls._generate_choice(x) for x in groups])
2691 repo_groups.extend([cls._generate_choice(x) for x in groups])
2691
2692
2692 repo_groups = sorted(
2693 repo_groups = sorted(
2693 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2694 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2694 return repo_groups
2695 return repo_groups
2695
2696
2696 @classmethod
2697 @classmethod
2697 def url_sep(cls):
2698 def url_sep(cls):
2698 return URL_SEP
2699 return URL_SEP
2699
2700
2700 @classmethod
2701 @classmethod
2701 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2702 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2702 if case_insensitive:
2703 if case_insensitive:
2703 gr = cls.query().filter(func.lower(cls.group_name)
2704 gr = cls.query().filter(func.lower(cls.group_name)
2704 == func.lower(group_name))
2705 == func.lower(group_name))
2705 else:
2706 else:
2706 gr = cls.query().filter(cls.group_name == group_name)
2707 gr = cls.query().filter(cls.group_name == group_name)
2707 if cache:
2708 if cache:
2708 name_key = _hash_key(group_name)
2709 name_key = _hash_key(group_name)
2709 gr = gr.options(
2710 gr = gr.options(
2710 FromCache("sql_cache_short", "get_group_%s" % name_key))
2711 FromCache("sql_cache_short", "get_group_%s" % name_key))
2711 return gr.scalar()
2712 return gr.scalar()
2712
2713
2713 @classmethod
2714 @classmethod
2714 def get_user_personal_repo_group(cls, user_id):
2715 def get_user_personal_repo_group(cls, user_id):
2715 user = User.get(user_id)
2716 user = User.get(user_id)
2716 if user.username == User.DEFAULT_USER:
2717 if user.username == User.DEFAULT_USER:
2717 return None
2718 return None
2718
2719
2719 return cls.query()\
2720 return cls.query()\
2720 .filter(cls.personal == true()) \
2721 .filter(cls.personal == true()) \
2721 .filter(cls.user == user) \
2722 .filter(cls.user == user) \
2722 .order_by(cls.group_id.asc()) \
2723 .order_by(cls.group_id.asc()) \
2723 .first()
2724 .first()
2724
2725
2725 @classmethod
2726 @classmethod
2726 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2727 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2727 case_insensitive=True):
2728 case_insensitive=True):
2728 q = RepoGroup.query()
2729 q = RepoGroup.query()
2729
2730
2730 if not isinstance(user_id, Optional):
2731 if not isinstance(user_id, Optional):
2731 q = q.filter(RepoGroup.user_id == user_id)
2732 q = q.filter(RepoGroup.user_id == user_id)
2732
2733
2733 if not isinstance(group_id, Optional):
2734 if not isinstance(group_id, Optional):
2734 q = q.filter(RepoGroup.group_parent_id == group_id)
2735 q = q.filter(RepoGroup.group_parent_id == group_id)
2735
2736
2736 if case_insensitive:
2737 if case_insensitive:
2737 q = q.order_by(func.lower(RepoGroup.group_name))
2738 q = q.order_by(func.lower(RepoGroup.group_name))
2738 else:
2739 else:
2739 q = q.order_by(RepoGroup.group_name)
2740 q = q.order_by(RepoGroup.group_name)
2740 return q.all()
2741 return q.all()
2741
2742
2742 @property
2743 @property
2743 def parents(self, parents_recursion_limit = 10):
2744 def parents(self, parents_recursion_limit = 10):
2744 groups = []
2745 groups = []
2745 if self.parent_group is None:
2746 if self.parent_group is None:
2746 return groups
2747 return groups
2747 cur_gr = self.parent_group
2748 cur_gr = self.parent_group
2748 groups.insert(0, cur_gr)
2749 groups.insert(0, cur_gr)
2749 cnt = 0
2750 cnt = 0
2750 while 1:
2751 while 1:
2751 cnt += 1
2752 cnt += 1
2752 gr = getattr(cur_gr, 'parent_group', None)
2753 gr = getattr(cur_gr, 'parent_group', None)
2753 cur_gr = cur_gr.parent_group
2754 cur_gr = cur_gr.parent_group
2754 if gr is None:
2755 if gr is None:
2755 break
2756 break
2756 if cnt == parents_recursion_limit:
2757 if cnt == parents_recursion_limit:
2757 # this will prevent accidental infinit loops
2758 # this will prevent accidental infinit loops
2758 log.error('more than %s parents found for group %s, stopping '
2759 log.error('more than %s parents found for group %s, stopping '
2759 'recursive parent fetching', parents_recursion_limit, self)
2760 'recursive parent fetching', parents_recursion_limit, self)
2760 break
2761 break
2761
2762
2762 groups.insert(0, gr)
2763 groups.insert(0, gr)
2763 return groups
2764 return groups
2764
2765
2765 @property
2766 @property
2766 def last_commit_cache_update_diff(self):
2767 def last_commit_cache_update_diff(self):
2767 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2768 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2768
2769
2769 @property
2770 @property
2770 def last_commit_change(self):
2771 def last_commit_change(self):
2771 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2772 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2772 empty_date = datetime.datetime.fromtimestamp(0)
2773 empty_date = datetime.datetime.fromtimestamp(0)
2773 date_latest = self.changeset_cache.get('date', empty_date)
2774 date_latest = self.changeset_cache.get('date', empty_date)
2774 try:
2775 try:
2775 return parse_datetime(date_latest)
2776 return parse_datetime(date_latest)
2776 except Exception:
2777 except Exception:
2777 return empty_date
2778 return empty_date
2778
2779
2779 @property
2780 @property
2780 def last_db_change(self):
2781 def last_db_change(self):
2781 return self.updated_on
2782 return self.updated_on
2782
2783
2783 @property
2784 @property
2784 def children(self):
2785 def children(self):
2785 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2786 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2786
2787
2787 @property
2788 @property
2788 def name(self):
2789 def name(self):
2789 return self.group_name.split(RepoGroup.url_sep())[-1]
2790 return self.group_name.split(RepoGroup.url_sep())[-1]
2790
2791
2791 @property
2792 @property
2792 def full_path(self):
2793 def full_path(self):
2793 return self.group_name
2794 return self.group_name
2794
2795
2795 @property
2796 @property
2796 def full_path_splitted(self):
2797 def full_path_splitted(self):
2797 return self.group_name.split(RepoGroup.url_sep())
2798 return self.group_name.split(RepoGroup.url_sep())
2798
2799
2799 @property
2800 @property
2800 def repositories(self):
2801 def repositories(self):
2801 return Repository.query()\
2802 return Repository.query()\
2802 .filter(Repository.group == self)\
2803 .filter(Repository.group == self)\
2803 .order_by(Repository.repo_name)
2804 .order_by(Repository.repo_name)
2804
2805
2805 @property
2806 @property
2806 def repositories_recursive_count(self):
2807 def repositories_recursive_count(self):
2807 cnt = self.repositories.count()
2808 cnt = self.repositories.count()
2808
2809
2809 def children_count(group):
2810 def children_count(group):
2810 cnt = 0
2811 cnt = 0
2811 for child in group.children:
2812 for child in group.children:
2812 cnt += child.repositories.count()
2813 cnt += child.repositories.count()
2813 cnt += children_count(child)
2814 cnt += children_count(child)
2814 return cnt
2815 return cnt
2815
2816
2816 return cnt + children_count(self)
2817 return cnt + children_count(self)
2817
2818
2818 def _recursive_objects(self, include_repos=True, include_groups=True):
2819 def _recursive_objects(self, include_repos=True, include_groups=True):
2819 all_ = []
2820 all_ = []
2820
2821
2821 def _get_members(root_gr):
2822 def _get_members(root_gr):
2822 if include_repos:
2823 if include_repos:
2823 for r in root_gr.repositories:
2824 for r in root_gr.repositories:
2824 all_.append(r)
2825 all_.append(r)
2825 childs = root_gr.children.all()
2826 childs = root_gr.children.all()
2826 if childs:
2827 if childs:
2827 for gr in childs:
2828 for gr in childs:
2828 if include_groups:
2829 if include_groups:
2829 all_.append(gr)
2830 all_.append(gr)
2830 _get_members(gr)
2831 _get_members(gr)
2831
2832
2832 root_group = []
2833 root_group = []
2833 if include_groups:
2834 if include_groups:
2834 root_group = [self]
2835 root_group = [self]
2835
2836
2836 _get_members(self)
2837 _get_members(self)
2837 return root_group + all_
2838 return root_group + all_
2838
2839
2839 def recursive_groups_and_repos(self):
2840 def recursive_groups_and_repos(self):
2840 """
2841 """
2841 Recursive return all groups, with repositories in those groups
2842 Recursive return all groups, with repositories in those groups
2842 """
2843 """
2843 return self._recursive_objects()
2844 return self._recursive_objects()
2844
2845
2845 def recursive_groups(self):
2846 def recursive_groups(self):
2846 """
2847 """
2847 Returns all children groups for this group including children of children
2848 Returns all children groups for this group including children of children
2848 """
2849 """
2849 return self._recursive_objects(include_repos=False)
2850 return self._recursive_objects(include_repos=False)
2850
2851
2851 def recursive_repos(self):
2852 def recursive_repos(self):
2852 """
2853 """
2853 Returns all children repositories for this group
2854 Returns all children repositories for this group
2854 """
2855 """
2855 return self._recursive_objects(include_groups=False)
2856 return self._recursive_objects(include_groups=False)
2856
2857
2857 def get_new_name(self, group_name):
2858 def get_new_name(self, group_name):
2858 """
2859 """
2859 returns new full group name based on parent and new name
2860 returns new full group name based on parent and new name
2860
2861
2861 :param group_name:
2862 :param group_name:
2862 """
2863 """
2863 path_prefix = (self.parent_group.full_path_splitted if
2864 path_prefix = (self.parent_group.full_path_splitted if
2864 self.parent_group else [])
2865 self.parent_group else [])
2865 return RepoGroup.url_sep().join(path_prefix + [group_name])
2866 return RepoGroup.url_sep().join(path_prefix + [group_name])
2866
2867
2867 def update_commit_cache(self, config=None):
2868 def update_commit_cache(self, config=None):
2868 """
2869 """
2869 Update cache of last changeset for newest repository inside this group, keys should be::
2870 Update cache of last changeset for newest repository inside this group, keys should be::
2870
2871
2871 source_repo_id
2872 source_repo_id
2872 short_id
2873 short_id
2873 raw_id
2874 raw_id
2874 revision
2875 revision
2875 parents
2876 parents
2876 message
2877 message
2877 date
2878 date
2878 author
2879 author
2879
2880
2880 """
2881 """
2881 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2882 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2882
2883
2883 def repo_groups_and_repos():
2884 def repo_groups_and_repos():
2884 all_entries = OrderedDefaultDict(list)
2885 all_entries = OrderedDefaultDict(list)
2885
2886
2886 def _get_members(root_gr, pos=0):
2887 def _get_members(root_gr, pos=0):
2887
2888
2888 for repo in root_gr.repositories:
2889 for repo in root_gr.repositories:
2889 all_entries[root_gr].append(repo)
2890 all_entries[root_gr].append(repo)
2890
2891
2891 # fill in all parent positions
2892 # fill in all parent positions
2892 for parent_group in root_gr.parents:
2893 for parent_group in root_gr.parents:
2893 all_entries[parent_group].extend(all_entries[root_gr])
2894 all_entries[parent_group].extend(all_entries[root_gr])
2894
2895
2895 children_groups = root_gr.children.all()
2896 children_groups = root_gr.children.all()
2896 if children_groups:
2897 if children_groups:
2897 for cnt, gr in enumerate(children_groups, 1):
2898 for cnt, gr in enumerate(children_groups, 1):
2898 _get_members(gr, pos=pos+cnt)
2899 _get_members(gr, pos=pos+cnt)
2899
2900
2900 _get_members(root_gr=self)
2901 _get_members(root_gr=self)
2901 return all_entries
2902 return all_entries
2902
2903
2903 empty_date = datetime.datetime.fromtimestamp(0)
2904 empty_date = datetime.datetime.fromtimestamp(0)
2904 for repo_group, repos in repo_groups_and_repos().items():
2905 for repo_group, repos in repo_groups_and_repos().items():
2905
2906
2906 latest_repo_cs_cache = {}
2907 latest_repo_cs_cache = {}
2907 _date_latest = empty_date
2908 _date_latest = empty_date
2908 for repo in repos:
2909 for repo in repos:
2909 repo_cs_cache = repo.changeset_cache
2910 repo_cs_cache = repo.changeset_cache
2910 date_latest = latest_repo_cs_cache.get('date', empty_date)
2911 date_latest = latest_repo_cs_cache.get('date', empty_date)
2911 date_current = repo_cs_cache.get('date', empty_date)
2912 date_current = repo_cs_cache.get('date', empty_date)
2912 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2913 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2913 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2914 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2914 latest_repo_cs_cache = repo_cs_cache
2915 latest_repo_cs_cache = repo_cs_cache
2915 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2916 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2916 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2917 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2917
2918
2918 latest_repo_cs_cache['updated_on'] = time.time()
2919 latest_repo_cs_cache['updated_on'] = time.time()
2919 repo_group.changeset_cache = latest_repo_cs_cache
2920 repo_group.changeset_cache = latest_repo_cs_cache
2920 repo_group.updated_on = _date_latest
2921 repo_group.updated_on = _date_latest
2921 Session().add(repo_group)
2922 Session().add(repo_group)
2922 Session().commit()
2923 Session().commit()
2923
2924
2924 log.debug('updated repo group `%s` with new commit cache %s',
2925 log.debug('updated repo group `%s` with new commit cache %s',
2925 repo_group.group_name, latest_repo_cs_cache)
2926 repo_group.group_name, latest_repo_cs_cache)
2926
2927
2927 def permissions(self, with_admins=True, with_owner=True,
2928 def permissions(self, with_admins=True, with_owner=True,
2928 expand_from_user_groups=False):
2929 expand_from_user_groups=False):
2929 """
2930 """
2930 Permissions for repository groups
2931 Permissions for repository groups
2931 """
2932 """
2932 _admin_perm = 'group.admin'
2933 _admin_perm = 'group.admin'
2933
2934
2934 owner_row = []
2935 owner_row = []
2935 if with_owner:
2936 if with_owner:
2936 usr = AttributeDict(self.user.get_dict())
2937 usr = AttributeDict(self.user.get_dict())
2937 usr.owner_row = True
2938 usr.owner_row = True
2938 usr.permission = _admin_perm
2939 usr.permission = _admin_perm
2939 owner_row.append(usr)
2940 owner_row.append(usr)
2940
2941
2941 super_admin_ids = []
2942 super_admin_ids = []
2942 super_admin_rows = []
2943 super_admin_rows = []
2943 if with_admins:
2944 if with_admins:
2944 for usr in User.get_all_super_admins():
2945 for usr in User.get_all_super_admins():
2945 super_admin_ids.append(usr.user_id)
2946 super_admin_ids.append(usr.user_id)
2946 # if this admin is also owner, don't double the record
2947 # if this admin is also owner, don't double the record
2947 if usr.user_id == owner_row[0].user_id:
2948 if usr.user_id == owner_row[0].user_id:
2948 owner_row[0].admin_row = True
2949 owner_row[0].admin_row = True
2949 else:
2950 else:
2950 usr = AttributeDict(usr.get_dict())
2951 usr = AttributeDict(usr.get_dict())
2951 usr.admin_row = True
2952 usr.admin_row = True
2952 usr.permission = _admin_perm
2953 usr.permission = _admin_perm
2953 super_admin_rows.append(usr)
2954 super_admin_rows.append(usr)
2954
2955
2955 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2956 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2956 q = q.options(joinedload(UserRepoGroupToPerm.group),
2957 q = q.options(joinedload(UserRepoGroupToPerm.group),
2957 joinedload(UserRepoGroupToPerm.user),
2958 joinedload(UserRepoGroupToPerm.user),
2958 joinedload(UserRepoGroupToPerm.permission),)
2959 joinedload(UserRepoGroupToPerm.permission),)
2959
2960
2960 # get owners and admins and permissions. We do a trick of re-writing
2961 # get owners and admins and permissions. We do a trick of re-writing
2961 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2962 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2962 # has a global reference and changing one object propagates to all
2963 # has a global reference and changing one object propagates to all
2963 # others. This means if admin is also an owner admin_row that change
2964 # others. This means if admin is also an owner admin_row that change
2964 # would propagate to both objects
2965 # would propagate to both objects
2965 perm_rows = []
2966 perm_rows = []
2966 for _usr in q.all():
2967 for _usr in q.all():
2967 usr = AttributeDict(_usr.user.get_dict())
2968 usr = AttributeDict(_usr.user.get_dict())
2968 # if this user is also owner/admin, mark as duplicate record
2969 # if this user is also owner/admin, mark as duplicate record
2969 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2970 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2970 usr.duplicate_perm = True
2971 usr.duplicate_perm = True
2971 usr.permission = _usr.permission.permission_name
2972 usr.permission = _usr.permission.permission_name
2972 perm_rows.append(usr)
2973 perm_rows.append(usr)
2973
2974
2974 # filter the perm rows by 'default' first and then sort them by
2975 # filter the perm rows by 'default' first and then sort them by
2975 # admin,write,read,none permissions sorted again alphabetically in
2976 # admin,write,read,none permissions sorted again alphabetically in
2976 # each group
2977 # each group
2977 perm_rows = sorted(perm_rows, key=display_user_sort)
2978 perm_rows = sorted(perm_rows, key=display_user_sort)
2978
2979
2979 user_groups_rows = []
2980 user_groups_rows = []
2980 if expand_from_user_groups:
2981 if expand_from_user_groups:
2981 for ug in self.permission_user_groups(with_members=True):
2982 for ug in self.permission_user_groups(with_members=True):
2982 for user_data in ug.members:
2983 for user_data in ug.members:
2983 user_groups_rows.append(user_data)
2984 user_groups_rows.append(user_data)
2984
2985
2985 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2986 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2986
2987
2987 def permission_user_groups(self, with_members=False):
2988 def permission_user_groups(self, with_members=False):
2988 q = UserGroupRepoGroupToPerm.query()\
2989 q = UserGroupRepoGroupToPerm.query()\
2989 .filter(UserGroupRepoGroupToPerm.group == self)
2990 .filter(UserGroupRepoGroupToPerm.group == self)
2990 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2991 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2991 joinedload(UserGroupRepoGroupToPerm.users_group),
2992 joinedload(UserGroupRepoGroupToPerm.users_group),
2992 joinedload(UserGroupRepoGroupToPerm.permission),)
2993 joinedload(UserGroupRepoGroupToPerm.permission),)
2993
2994
2994 perm_rows = []
2995 perm_rows = []
2995 for _user_group in q.all():
2996 for _user_group in q.all():
2996 entry = AttributeDict(_user_group.users_group.get_dict())
2997 entry = AttributeDict(_user_group.users_group.get_dict())
2997 entry.permission = _user_group.permission.permission_name
2998 entry.permission = _user_group.permission.permission_name
2998 if with_members:
2999 if with_members:
2999 entry.members = [x.user.get_dict()
3000 entry.members = [x.user.get_dict()
3000 for x in _user_group.users_group.members]
3001 for x in _user_group.users_group.members]
3001 perm_rows.append(entry)
3002 perm_rows.append(entry)
3002
3003
3003 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3004 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3004 return perm_rows
3005 return perm_rows
3005
3006
3006 def get_api_data(self):
3007 def get_api_data(self):
3007 """
3008 """
3008 Common function for generating api data
3009 Common function for generating api data
3009
3010
3010 """
3011 """
3011 group = self
3012 group = self
3012 data = {
3013 data = {
3013 'group_id': group.group_id,
3014 'group_id': group.group_id,
3014 'group_name': group.group_name,
3015 'group_name': group.group_name,
3015 'group_description': group.description_safe,
3016 'group_description': group.description_safe,
3016 'parent_group': group.parent_group.group_name if group.parent_group else None,
3017 'parent_group': group.parent_group.group_name if group.parent_group else None,
3017 'repositories': [x.repo_name for x in group.repositories],
3018 'repositories': [x.repo_name for x in group.repositories],
3018 'owner': group.user.username,
3019 'owner': group.user.username,
3019 }
3020 }
3020 return data
3021 return data
3021
3022
3022 def get_dict(self):
3023 def get_dict(self):
3023 # Since we transformed `group_name` to a hybrid property, we need to
3024 # Since we transformed `group_name` to a hybrid property, we need to
3024 # keep compatibility with the code which uses `group_name` field.
3025 # keep compatibility with the code which uses `group_name` field.
3025 result = super(RepoGroup, self).get_dict()
3026 result = super(RepoGroup, self).get_dict()
3026 result['group_name'] = result.pop('_group_name', None)
3027 result['group_name'] = result.pop('_group_name', None)
3027 return result
3028 return result
3028
3029
3029
3030
3030 class Permission(Base, BaseModel):
3031 class Permission(Base, BaseModel):
3031 __tablename__ = 'permissions'
3032 __tablename__ = 'permissions'
3032 __table_args__ = (
3033 __table_args__ = (
3033 Index('p_perm_name_idx', 'permission_name'),
3034 Index('p_perm_name_idx', 'permission_name'),
3034 base_table_args,
3035 base_table_args,
3035 )
3036 )
3036
3037
3037 PERMS = [
3038 PERMS = [
3038 ('hg.admin', _('RhodeCode Super Administrator')),
3039 ('hg.admin', _('RhodeCode Super Administrator')),
3039
3040
3040 ('repository.none', _('Repository no access')),
3041 ('repository.none', _('Repository no access')),
3041 ('repository.read', _('Repository read access')),
3042 ('repository.read', _('Repository read access')),
3042 ('repository.write', _('Repository write access')),
3043 ('repository.write', _('Repository write access')),
3043 ('repository.admin', _('Repository admin access')),
3044 ('repository.admin', _('Repository admin access')),
3044
3045
3045 ('group.none', _('Repository group no access')),
3046 ('group.none', _('Repository group no access')),
3046 ('group.read', _('Repository group read access')),
3047 ('group.read', _('Repository group read access')),
3047 ('group.write', _('Repository group write access')),
3048 ('group.write', _('Repository group write access')),
3048 ('group.admin', _('Repository group admin access')),
3049 ('group.admin', _('Repository group admin access')),
3049
3050
3050 ('usergroup.none', _('User group no access')),
3051 ('usergroup.none', _('User group no access')),
3051 ('usergroup.read', _('User group read access')),
3052 ('usergroup.read', _('User group read access')),
3052 ('usergroup.write', _('User group write access')),
3053 ('usergroup.write', _('User group write access')),
3053 ('usergroup.admin', _('User group admin access')),
3054 ('usergroup.admin', _('User group admin access')),
3054
3055
3055 ('branch.none', _('Branch no permissions')),
3056 ('branch.none', _('Branch no permissions')),
3056 ('branch.merge', _('Branch access by web merge')),
3057 ('branch.merge', _('Branch access by web merge')),
3057 ('branch.push', _('Branch access by push')),
3058 ('branch.push', _('Branch access by push')),
3058 ('branch.push_force', _('Branch access by push with force')),
3059 ('branch.push_force', _('Branch access by push with force')),
3059
3060
3060 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3061 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3061 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3062 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3062
3063
3063 ('hg.usergroup.create.false', _('User Group creation disabled')),
3064 ('hg.usergroup.create.false', _('User Group creation disabled')),
3064 ('hg.usergroup.create.true', _('User Group creation enabled')),
3065 ('hg.usergroup.create.true', _('User Group creation enabled')),
3065
3066
3066 ('hg.create.none', _('Repository creation disabled')),
3067 ('hg.create.none', _('Repository creation disabled')),
3067 ('hg.create.repository', _('Repository creation enabled')),
3068 ('hg.create.repository', _('Repository creation enabled')),
3068 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3069 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3069 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3070 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3070
3071
3071 ('hg.fork.none', _('Repository forking disabled')),
3072 ('hg.fork.none', _('Repository forking disabled')),
3072 ('hg.fork.repository', _('Repository forking enabled')),
3073 ('hg.fork.repository', _('Repository forking enabled')),
3073
3074
3074 ('hg.register.none', _('Registration disabled')),
3075 ('hg.register.none', _('Registration disabled')),
3075 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3076 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3076 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3077 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3077
3078
3078 ('hg.password_reset.enabled', _('Password reset enabled')),
3079 ('hg.password_reset.enabled', _('Password reset enabled')),
3079 ('hg.password_reset.hidden', _('Password reset hidden')),
3080 ('hg.password_reset.hidden', _('Password reset hidden')),
3080 ('hg.password_reset.disabled', _('Password reset disabled')),
3081 ('hg.password_reset.disabled', _('Password reset disabled')),
3081
3082
3082 ('hg.extern_activate.manual', _('Manual activation of external account')),
3083 ('hg.extern_activate.manual', _('Manual activation of external account')),
3083 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3084 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3084
3085
3085 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3086 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3086 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3087 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3087 ]
3088 ]
3088
3089
3089 # definition of system default permissions for DEFAULT user, created on
3090 # definition of system default permissions for DEFAULT user, created on
3090 # system setup
3091 # system setup
3091 DEFAULT_USER_PERMISSIONS = [
3092 DEFAULT_USER_PERMISSIONS = [
3092 # object perms
3093 # object perms
3093 'repository.read',
3094 'repository.read',
3094 'group.read',
3095 'group.read',
3095 'usergroup.read',
3096 'usergroup.read',
3096 # branch, for backward compat we need same value as before so forced pushed
3097 # branch, for backward compat we need same value as before so forced pushed
3097 'branch.push_force',
3098 'branch.push_force',
3098 # global
3099 # global
3099 'hg.create.repository',
3100 'hg.create.repository',
3100 'hg.repogroup.create.false',
3101 'hg.repogroup.create.false',
3101 'hg.usergroup.create.false',
3102 'hg.usergroup.create.false',
3102 'hg.create.write_on_repogroup.true',
3103 'hg.create.write_on_repogroup.true',
3103 'hg.fork.repository',
3104 'hg.fork.repository',
3104 'hg.register.manual_activate',
3105 'hg.register.manual_activate',
3105 'hg.password_reset.enabled',
3106 'hg.password_reset.enabled',
3106 'hg.extern_activate.auto',
3107 'hg.extern_activate.auto',
3107 'hg.inherit_default_perms.true',
3108 'hg.inherit_default_perms.true',
3108 ]
3109 ]
3109
3110
3110 # defines which permissions are more important higher the more important
3111 # defines which permissions are more important higher the more important
3111 # Weight defines which permissions are more important.
3112 # Weight defines which permissions are more important.
3112 # The higher number the more important.
3113 # The higher number the more important.
3113 PERM_WEIGHTS = {
3114 PERM_WEIGHTS = {
3114 'repository.none': 0,
3115 'repository.none': 0,
3115 'repository.read': 1,
3116 'repository.read': 1,
3116 'repository.write': 3,
3117 'repository.write': 3,
3117 'repository.admin': 4,
3118 'repository.admin': 4,
3118
3119
3119 'group.none': 0,
3120 'group.none': 0,
3120 'group.read': 1,
3121 'group.read': 1,
3121 'group.write': 3,
3122 'group.write': 3,
3122 'group.admin': 4,
3123 'group.admin': 4,
3123
3124
3124 'usergroup.none': 0,
3125 'usergroup.none': 0,
3125 'usergroup.read': 1,
3126 'usergroup.read': 1,
3126 'usergroup.write': 3,
3127 'usergroup.write': 3,
3127 'usergroup.admin': 4,
3128 'usergroup.admin': 4,
3128
3129
3129 'branch.none': 0,
3130 'branch.none': 0,
3130 'branch.merge': 1,
3131 'branch.merge': 1,
3131 'branch.push': 3,
3132 'branch.push': 3,
3132 'branch.push_force': 4,
3133 'branch.push_force': 4,
3133
3134
3134 'hg.repogroup.create.false': 0,
3135 'hg.repogroup.create.false': 0,
3135 'hg.repogroup.create.true': 1,
3136 'hg.repogroup.create.true': 1,
3136
3137
3137 'hg.usergroup.create.false': 0,
3138 'hg.usergroup.create.false': 0,
3138 'hg.usergroup.create.true': 1,
3139 'hg.usergroup.create.true': 1,
3139
3140
3140 'hg.fork.none': 0,
3141 'hg.fork.none': 0,
3141 'hg.fork.repository': 1,
3142 'hg.fork.repository': 1,
3142 'hg.create.none': 0,
3143 'hg.create.none': 0,
3143 'hg.create.repository': 1
3144 'hg.create.repository': 1
3144 }
3145 }
3145
3146
3146 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3147 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3147 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3148 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3148 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3149 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3149
3150
3150 def __unicode__(self):
3151 def __unicode__(self):
3151 return u"<%s('%s:%s')>" % (
3152 return u"<%s('%s:%s')>" % (
3152 self.__class__.__name__, self.permission_id, self.permission_name
3153 self.__class__.__name__, self.permission_id, self.permission_name
3153 )
3154 )
3154
3155
3155 @classmethod
3156 @classmethod
3156 def get_by_key(cls, key):
3157 def get_by_key(cls, key):
3157 return cls.query().filter(cls.permission_name == key).scalar()
3158 return cls.query().filter(cls.permission_name == key).scalar()
3158
3159
3159 @classmethod
3160 @classmethod
3160 def get_default_repo_perms(cls, user_id, repo_id=None):
3161 def get_default_repo_perms(cls, user_id, repo_id=None):
3161 q = Session().query(UserRepoToPerm, Repository, Permission)\
3162 q = Session().query(UserRepoToPerm, Repository, Permission)\
3162 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3163 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3163 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3164 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3164 .filter(UserRepoToPerm.user_id == user_id)
3165 .filter(UserRepoToPerm.user_id == user_id)
3165 if repo_id:
3166 if repo_id:
3166 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3167 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3167 return q.all()
3168 return q.all()
3168
3169
3169 @classmethod
3170 @classmethod
3170 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3171 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3171 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3172 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3172 .join(
3173 .join(
3173 Permission,
3174 Permission,
3174 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3175 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3175 .join(
3176 .join(
3176 UserRepoToPerm,
3177 UserRepoToPerm,
3177 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3178 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3178 .filter(UserRepoToPerm.user_id == user_id)
3179 .filter(UserRepoToPerm.user_id == user_id)
3179
3180
3180 if repo_id:
3181 if repo_id:
3181 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3182 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3182 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3183 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3183
3184
3184 @classmethod
3185 @classmethod
3185 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3186 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3186 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3187 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3187 .join(
3188 .join(
3188 Permission,
3189 Permission,
3189 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3190 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3190 .join(
3191 .join(
3191 Repository,
3192 Repository,
3192 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3193 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3193 .join(
3194 .join(
3194 UserGroup,
3195 UserGroup,
3195 UserGroupRepoToPerm.users_group_id ==
3196 UserGroupRepoToPerm.users_group_id ==
3196 UserGroup.users_group_id)\
3197 UserGroup.users_group_id)\
3197 .join(
3198 .join(
3198 UserGroupMember,
3199 UserGroupMember,
3199 UserGroupRepoToPerm.users_group_id ==
3200 UserGroupRepoToPerm.users_group_id ==
3200 UserGroupMember.users_group_id)\
3201 UserGroupMember.users_group_id)\
3201 .filter(
3202 .filter(
3202 UserGroupMember.user_id == user_id,
3203 UserGroupMember.user_id == user_id,
3203 UserGroup.users_group_active == true())
3204 UserGroup.users_group_active == true())
3204 if repo_id:
3205 if repo_id:
3205 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3206 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3206 return q.all()
3207 return q.all()
3207
3208
3208 @classmethod
3209 @classmethod
3209 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3210 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3210 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3211 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3211 .join(
3212 .join(
3212 Permission,
3213 Permission,
3213 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3214 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3214 .join(
3215 .join(
3215 UserGroupRepoToPerm,
3216 UserGroupRepoToPerm,
3216 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3217 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3217 .join(
3218 .join(
3218 UserGroup,
3219 UserGroup,
3219 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3220 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3220 .join(
3221 .join(
3221 UserGroupMember,
3222 UserGroupMember,
3222 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3223 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3223 .filter(
3224 .filter(
3224 UserGroupMember.user_id == user_id,
3225 UserGroupMember.user_id == user_id,
3225 UserGroup.users_group_active == true())
3226 UserGroup.users_group_active == true())
3226
3227
3227 if repo_id:
3228 if repo_id:
3228 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3229 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3229 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3230 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3230
3231
3231 @classmethod
3232 @classmethod
3232 def get_default_group_perms(cls, user_id, repo_group_id=None):
3233 def get_default_group_perms(cls, user_id, repo_group_id=None):
3233 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3234 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3234 .join(
3235 .join(
3235 Permission,
3236 Permission,
3236 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3237 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3237 .join(
3238 .join(
3238 RepoGroup,
3239 RepoGroup,
3239 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3240 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3240 .filter(UserRepoGroupToPerm.user_id == user_id)
3241 .filter(UserRepoGroupToPerm.user_id == user_id)
3241 if repo_group_id:
3242 if repo_group_id:
3242 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3243 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3243 return q.all()
3244 return q.all()
3244
3245
3245 @classmethod
3246 @classmethod
3246 def get_default_group_perms_from_user_group(
3247 def get_default_group_perms_from_user_group(
3247 cls, user_id, repo_group_id=None):
3248 cls, user_id, repo_group_id=None):
3248 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3249 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3249 .join(
3250 .join(
3250 Permission,
3251 Permission,
3251 UserGroupRepoGroupToPerm.permission_id ==
3252 UserGroupRepoGroupToPerm.permission_id ==
3252 Permission.permission_id)\
3253 Permission.permission_id)\
3253 .join(
3254 .join(
3254 RepoGroup,
3255 RepoGroup,
3255 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3256 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3256 .join(
3257 .join(
3257 UserGroup,
3258 UserGroup,
3258 UserGroupRepoGroupToPerm.users_group_id ==
3259 UserGroupRepoGroupToPerm.users_group_id ==
3259 UserGroup.users_group_id)\
3260 UserGroup.users_group_id)\
3260 .join(
3261 .join(
3261 UserGroupMember,
3262 UserGroupMember,
3262 UserGroupRepoGroupToPerm.users_group_id ==
3263 UserGroupRepoGroupToPerm.users_group_id ==
3263 UserGroupMember.users_group_id)\
3264 UserGroupMember.users_group_id)\
3264 .filter(
3265 .filter(
3265 UserGroupMember.user_id == user_id,
3266 UserGroupMember.user_id == user_id,
3266 UserGroup.users_group_active == true())
3267 UserGroup.users_group_active == true())
3267 if repo_group_id:
3268 if repo_group_id:
3268 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3269 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3269 return q.all()
3270 return q.all()
3270
3271
3271 @classmethod
3272 @classmethod
3272 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3273 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3273 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3274 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3274 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3275 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3275 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3276 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3276 .filter(UserUserGroupToPerm.user_id == user_id)
3277 .filter(UserUserGroupToPerm.user_id == user_id)
3277 if user_group_id:
3278 if user_group_id:
3278 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3279 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3279 return q.all()
3280 return q.all()
3280
3281
3281 @classmethod
3282 @classmethod
3282 def get_default_user_group_perms_from_user_group(
3283 def get_default_user_group_perms_from_user_group(
3283 cls, user_id, user_group_id=None):
3284 cls, user_id, user_group_id=None):
3284 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3285 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3285 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3286 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3286 .join(
3287 .join(
3287 Permission,
3288 Permission,
3288 UserGroupUserGroupToPerm.permission_id ==
3289 UserGroupUserGroupToPerm.permission_id ==
3289 Permission.permission_id)\
3290 Permission.permission_id)\
3290 .join(
3291 .join(
3291 TargetUserGroup,
3292 TargetUserGroup,
3292 UserGroupUserGroupToPerm.target_user_group_id ==
3293 UserGroupUserGroupToPerm.target_user_group_id ==
3293 TargetUserGroup.users_group_id)\
3294 TargetUserGroup.users_group_id)\
3294 .join(
3295 .join(
3295 UserGroup,
3296 UserGroup,
3296 UserGroupUserGroupToPerm.user_group_id ==
3297 UserGroupUserGroupToPerm.user_group_id ==
3297 UserGroup.users_group_id)\
3298 UserGroup.users_group_id)\
3298 .join(
3299 .join(
3299 UserGroupMember,
3300 UserGroupMember,
3300 UserGroupUserGroupToPerm.user_group_id ==
3301 UserGroupUserGroupToPerm.user_group_id ==
3301 UserGroupMember.users_group_id)\
3302 UserGroupMember.users_group_id)\
3302 .filter(
3303 .filter(
3303 UserGroupMember.user_id == user_id,
3304 UserGroupMember.user_id == user_id,
3304 UserGroup.users_group_active == true())
3305 UserGroup.users_group_active == true())
3305 if user_group_id:
3306 if user_group_id:
3306 q = q.filter(
3307 q = q.filter(
3307 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3308 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3308
3309
3309 return q.all()
3310 return q.all()
3310
3311
3311
3312
3312 class UserRepoToPerm(Base, BaseModel):
3313 class UserRepoToPerm(Base, BaseModel):
3313 __tablename__ = 'repo_to_perm'
3314 __tablename__ = 'repo_to_perm'
3314 __table_args__ = (
3315 __table_args__ = (
3315 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3316 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3316 base_table_args
3317 base_table_args
3317 )
3318 )
3318
3319
3319 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3320 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3320 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3321 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3322 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3322 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3323
3324
3324 user = relationship('User')
3325 user = relationship('User')
3325 repository = relationship('Repository')
3326 repository = relationship('Repository')
3326 permission = relationship('Permission')
3327 permission = relationship('Permission')
3327
3328
3328 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3329 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3329
3330
3330 @classmethod
3331 @classmethod
3331 def create(cls, user, repository, permission):
3332 def create(cls, user, repository, permission):
3332 n = cls()
3333 n = cls()
3333 n.user = user
3334 n.user = user
3334 n.repository = repository
3335 n.repository = repository
3335 n.permission = permission
3336 n.permission = permission
3336 Session().add(n)
3337 Session().add(n)
3337 return n
3338 return n
3338
3339
3339 def __unicode__(self):
3340 def __unicode__(self):
3340 return u'<%s => %s >' % (self.user, self.repository)
3341 return u'<%s => %s >' % (self.user, self.repository)
3341
3342
3342
3343
3343 class UserUserGroupToPerm(Base, BaseModel):
3344 class UserUserGroupToPerm(Base, BaseModel):
3344 __tablename__ = 'user_user_group_to_perm'
3345 __tablename__ = 'user_user_group_to_perm'
3345 __table_args__ = (
3346 __table_args__ = (
3346 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3347 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3347 base_table_args
3348 base_table_args
3348 )
3349 )
3349
3350
3350 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3351 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3351 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3352 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3352 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3353 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3353 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3354 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3354
3355
3355 user = relationship('User')
3356 user = relationship('User')
3356 user_group = relationship('UserGroup')
3357 user_group = relationship('UserGroup')
3357 permission = relationship('Permission')
3358 permission = relationship('Permission')
3358
3359
3359 @classmethod
3360 @classmethod
3360 def create(cls, user, user_group, permission):
3361 def create(cls, user, user_group, permission):
3361 n = cls()
3362 n = cls()
3362 n.user = user
3363 n.user = user
3363 n.user_group = user_group
3364 n.user_group = user_group
3364 n.permission = permission
3365 n.permission = permission
3365 Session().add(n)
3366 Session().add(n)
3366 return n
3367 return n
3367
3368
3368 def __unicode__(self):
3369 def __unicode__(self):
3369 return u'<%s => %s >' % (self.user, self.user_group)
3370 return u'<%s => %s >' % (self.user, self.user_group)
3370
3371
3371
3372
3372 class UserToPerm(Base, BaseModel):
3373 class UserToPerm(Base, BaseModel):
3373 __tablename__ = 'user_to_perm'
3374 __tablename__ = 'user_to_perm'
3374 __table_args__ = (
3375 __table_args__ = (
3375 UniqueConstraint('user_id', 'permission_id'),
3376 UniqueConstraint('user_id', 'permission_id'),
3376 base_table_args
3377 base_table_args
3377 )
3378 )
3378
3379
3379 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3380 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3380 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3381 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3381 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3382 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3382
3383
3383 user = relationship('User')
3384 user = relationship('User')
3384 permission = relationship('Permission', lazy='joined')
3385 permission = relationship('Permission', lazy='joined')
3385
3386
3386 def __unicode__(self):
3387 def __unicode__(self):
3387 return u'<%s => %s >' % (self.user, self.permission)
3388 return u'<%s => %s >' % (self.user, self.permission)
3388
3389
3389
3390
3390 class UserGroupRepoToPerm(Base, BaseModel):
3391 class UserGroupRepoToPerm(Base, BaseModel):
3391 __tablename__ = 'users_group_repo_to_perm'
3392 __tablename__ = 'users_group_repo_to_perm'
3392 __table_args__ = (
3393 __table_args__ = (
3393 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3394 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3394 base_table_args
3395 base_table_args
3395 )
3396 )
3396
3397
3397 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3398 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3398 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3399 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3399 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3400 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3400 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3401 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3401
3402
3402 users_group = relationship('UserGroup')
3403 users_group = relationship('UserGroup')
3403 permission = relationship('Permission')
3404 permission = relationship('Permission')
3404 repository = relationship('Repository')
3405 repository = relationship('Repository')
3405 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3406 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3406
3407
3407 @classmethod
3408 @classmethod
3408 def create(cls, users_group, repository, permission):
3409 def create(cls, users_group, repository, permission):
3409 n = cls()
3410 n = cls()
3410 n.users_group = users_group
3411 n.users_group = users_group
3411 n.repository = repository
3412 n.repository = repository
3412 n.permission = permission
3413 n.permission = permission
3413 Session().add(n)
3414 Session().add(n)
3414 return n
3415 return n
3415
3416
3416 def __unicode__(self):
3417 def __unicode__(self):
3417 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3418 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3418
3419
3419
3420
3420 class UserGroupUserGroupToPerm(Base, BaseModel):
3421 class UserGroupUserGroupToPerm(Base, BaseModel):
3421 __tablename__ = 'user_group_user_group_to_perm'
3422 __tablename__ = 'user_group_user_group_to_perm'
3422 __table_args__ = (
3423 __table_args__ = (
3423 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3424 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3424 CheckConstraint('target_user_group_id != user_group_id'),
3425 CheckConstraint('target_user_group_id != user_group_id'),
3425 base_table_args
3426 base_table_args
3426 )
3427 )
3427
3428
3428 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)
3429 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)
3429 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3430 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3430 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3431 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3431 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3432 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3432
3433
3433 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3434 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3434 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3435 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3435 permission = relationship('Permission')
3436 permission = relationship('Permission')
3436
3437
3437 @classmethod
3438 @classmethod
3438 def create(cls, target_user_group, user_group, permission):
3439 def create(cls, target_user_group, user_group, permission):
3439 n = cls()
3440 n = cls()
3440 n.target_user_group = target_user_group
3441 n.target_user_group = target_user_group
3441 n.user_group = user_group
3442 n.user_group = user_group
3442 n.permission = permission
3443 n.permission = permission
3443 Session().add(n)
3444 Session().add(n)
3444 return n
3445 return n
3445
3446
3446 def __unicode__(self):
3447 def __unicode__(self):
3447 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3448 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3448
3449
3449
3450
3450 class UserGroupToPerm(Base, BaseModel):
3451 class UserGroupToPerm(Base, BaseModel):
3451 __tablename__ = 'users_group_to_perm'
3452 __tablename__ = 'users_group_to_perm'
3452 __table_args__ = (
3453 __table_args__ = (
3453 UniqueConstraint('users_group_id', 'permission_id',),
3454 UniqueConstraint('users_group_id', 'permission_id',),
3454 base_table_args
3455 base_table_args
3455 )
3456 )
3456
3457
3457 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3458 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3458 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3459 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3459 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3460 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3460
3461
3461 users_group = relationship('UserGroup')
3462 users_group = relationship('UserGroup')
3462 permission = relationship('Permission')
3463 permission = relationship('Permission')
3463
3464
3464
3465
3465 class UserRepoGroupToPerm(Base, BaseModel):
3466 class UserRepoGroupToPerm(Base, BaseModel):
3466 __tablename__ = 'user_repo_group_to_perm'
3467 __tablename__ = 'user_repo_group_to_perm'
3467 __table_args__ = (
3468 __table_args__ = (
3468 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3469 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3469 base_table_args
3470 base_table_args
3470 )
3471 )
3471
3472
3472 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3473 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3473 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3474 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3475 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3475 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3476 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3476
3477
3477 user = relationship('User')
3478 user = relationship('User')
3478 group = relationship('RepoGroup')
3479 group = relationship('RepoGroup')
3479 permission = relationship('Permission')
3480 permission = relationship('Permission')
3480
3481
3481 @classmethod
3482 @classmethod
3482 def create(cls, user, repository_group, permission):
3483 def create(cls, user, repository_group, permission):
3483 n = cls()
3484 n = cls()
3484 n.user = user
3485 n.user = user
3485 n.group = repository_group
3486 n.group = repository_group
3486 n.permission = permission
3487 n.permission = permission
3487 Session().add(n)
3488 Session().add(n)
3488 return n
3489 return n
3489
3490
3490
3491
3491 class UserGroupRepoGroupToPerm(Base, BaseModel):
3492 class UserGroupRepoGroupToPerm(Base, BaseModel):
3492 __tablename__ = 'users_group_repo_group_to_perm'
3493 __tablename__ = 'users_group_repo_group_to_perm'
3493 __table_args__ = (
3494 __table_args__ = (
3494 UniqueConstraint('users_group_id', 'group_id'),
3495 UniqueConstraint('users_group_id', 'group_id'),
3495 base_table_args
3496 base_table_args
3496 )
3497 )
3497
3498
3498 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)
3499 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)
3499 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3500 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3501 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3502 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3502
3503
3503 users_group = relationship('UserGroup')
3504 users_group = relationship('UserGroup')
3504 permission = relationship('Permission')
3505 permission = relationship('Permission')
3505 group = relationship('RepoGroup')
3506 group = relationship('RepoGroup')
3506
3507
3507 @classmethod
3508 @classmethod
3508 def create(cls, user_group, repository_group, permission):
3509 def create(cls, user_group, repository_group, permission):
3509 n = cls()
3510 n = cls()
3510 n.users_group = user_group
3511 n.users_group = user_group
3511 n.group = repository_group
3512 n.group = repository_group
3512 n.permission = permission
3513 n.permission = permission
3513 Session().add(n)
3514 Session().add(n)
3514 return n
3515 return n
3515
3516
3516 def __unicode__(self):
3517 def __unicode__(self):
3517 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3518 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3518
3519
3519
3520
3520 class Statistics(Base, BaseModel):
3521 class Statistics(Base, BaseModel):
3521 __tablename__ = 'statistics'
3522 __tablename__ = 'statistics'
3522 __table_args__ = (
3523 __table_args__ = (
3523 base_table_args
3524 base_table_args
3524 )
3525 )
3525
3526
3526 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3527 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3527 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3528 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3528 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3529 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3529 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3530 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3530 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3531 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3531 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3532 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3532
3533
3533 repository = relationship('Repository', single_parent=True)
3534 repository = relationship('Repository', single_parent=True)
3534
3535
3535
3536
3536 class UserFollowing(Base, BaseModel):
3537 class UserFollowing(Base, BaseModel):
3537 __tablename__ = 'user_followings'
3538 __tablename__ = 'user_followings'
3538 __table_args__ = (
3539 __table_args__ = (
3539 UniqueConstraint('user_id', 'follows_repository_id'),
3540 UniqueConstraint('user_id', 'follows_repository_id'),
3540 UniqueConstraint('user_id', 'follows_user_id'),
3541 UniqueConstraint('user_id', 'follows_user_id'),
3541 base_table_args
3542 base_table_args
3542 )
3543 )
3543
3544
3544 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3545 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3546 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3546 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3547 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3547 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3548 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3548 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3549 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3549
3550
3550 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3551 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3551
3552
3552 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3553 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3553 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3554 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3554
3555
3555 @classmethod
3556 @classmethod
3556 def get_repo_followers(cls, repo_id):
3557 def get_repo_followers(cls, repo_id):
3557 return cls.query().filter(cls.follows_repo_id == repo_id)
3558 return cls.query().filter(cls.follows_repo_id == repo_id)
3558
3559
3559
3560
3560 class CacheKey(Base, BaseModel):
3561 class CacheKey(Base, BaseModel):
3561 __tablename__ = 'cache_invalidation'
3562 __tablename__ = 'cache_invalidation'
3562 __table_args__ = (
3563 __table_args__ = (
3563 UniqueConstraint('cache_key'),
3564 UniqueConstraint('cache_key'),
3564 Index('key_idx', 'cache_key'),
3565 Index('key_idx', 'cache_key'),
3565 base_table_args,
3566 base_table_args,
3566 )
3567 )
3567
3568
3568 CACHE_TYPE_FEED = 'FEED'
3569 CACHE_TYPE_FEED = 'FEED'
3569
3570
3570 # namespaces used to register process/thread aware caches
3571 # namespaces used to register process/thread aware caches
3571 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3572 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3572 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3573 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3573
3574
3574 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3575 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3575 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3576 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3576 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3577 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3577 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3578 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3578 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3579 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3579
3580
3580 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3581 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3581 self.cache_key = cache_key
3582 self.cache_key = cache_key
3582 self.cache_args = cache_args
3583 self.cache_args = cache_args
3583 self.cache_active = False
3584 self.cache_active = False
3584 # first key should be same for all entries, since all workers should share it
3585 # first key should be same for all entries, since all workers should share it
3585 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3586 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3586
3587
3587 def __unicode__(self):
3588 def __unicode__(self):
3588 return u"<%s('%s:%s[%s]')>" % (
3589 return u"<%s('%s:%s[%s]')>" % (
3589 self.__class__.__name__,
3590 self.__class__.__name__,
3590 self.cache_id, self.cache_key, self.cache_active)
3591 self.cache_id, self.cache_key, self.cache_active)
3591
3592
3592 def _cache_key_partition(self):
3593 def _cache_key_partition(self):
3593 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3594 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3594 return prefix, repo_name, suffix
3595 return prefix, repo_name, suffix
3595
3596
3596 def get_prefix(self):
3597 def get_prefix(self):
3597 """
3598 """
3598 Try to extract prefix from existing cache key. The key could consist
3599 Try to extract prefix from existing cache key. The key could consist
3599 of prefix, repo_name, suffix
3600 of prefix, repo_name, suffix
3600 """
3601 """
3601 # this returns prefix, repo_name, suffix
3602 # this returns prefix, repo_name, suffix
3602 return self._cache_key_partition()[0]
3603 return self._cache_key_partition()[0]
3603
3604
3604 def get_suffix(self):
3605 def get_suffix(self):
3605 """
3606 """
3606 get suffix that might have been used in _get_cache_key to
3607 get suffix that might have been used in _get_cache_key to
3607 generate self.cache_key. Only used for informational purposes
3608 generate self.cache_key. Only used for informational purposes
3608 in repo_edit.mako.
3609 in repo_edit.mako.
3609 """
3610 """
3610 # prefix, repo_name, suffix
3611 # prefix, repo_name, suffix
3611 return self._cache_key_partition()[2]
3612 return self._cache_key_partition()[2]
3612
3613
3613 @classmethod
3614 @classmethod
3614 def generate_new_state_uid(cls, based_on=None):
3615 def generate_new_state_uid(cls, based_on=None):
3615 if based_on:
3616 if based_on:
3616 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3617 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3617 else:
3618 else:
3618 return str(uuid.uuid4())
3619 return str(uuid.uuid4())
3619
3620
3620 @classmethod
3621 @classmethod
3621 def delete_all_cache(cls):
3622 def delete_all_cache(cls):
3622 """
3623 """
3623 Delete all cache keys from database.
3624 Delete all cache keys from database.
3624 Should only be run when all instances are down and all entries
3625 Should only be run when all instances are down and all entries
3625 thus stale.
3626 thus stale.
3626 """
3627 """
3627 cls.query().delete()
3628 cls.query().delete()
3628 Session().commit()
3629 Session().commit()
3629
3630
3630 @classmethod
3631 @classmethod
3631 def set_invalidate(cls, cache_uid, delete=False):
3632 def set_invalidate(cls, cache_uid, delete=False):
3632 """
3633 """
3633 Mark all caches of a repo as invalid in the database.
3634 Mark all caches of a repo as invalid in the database.
3634 """
3635 """
3635
3636
3636 try:
3637 try:
3637 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3638 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3638 if delete:
3639 if delete:
3639 qry.delete()
3640 qry.delete()
3640 log.debug('cache objects deleted for cache args %s',
3641 log.debug('cache objects deleted for cache args %s',
3641 safe_str(cache_uid))
3642 safe_str(cache_uid))
3642 else:
3643 else:
3643 qry.update({"cache_active": False,
3644 qry.update({"cache_active": False,
3644 "cache_state_uid": cls.generate_new_state_uid()})
3645 "cache_state_uid": cls.generate_new_state_uid()})
3645 log.debug('cache objects marked as invalid for cache args %s',
3646 log.debug('cache objects marked as invalid for cache args %s',
3646 safe_str(cache_uid))
3647 safe_str(cache_uid))
3647
3648
3648 Session().commit()
3649 Session().commit()
3649 except Exception:
3650 except Exception:
3650 log.exception(
3651 log.exception(
3651 'Cache key invalidation failed for cache args %s',
3652 'Cache key invalidation failed for cache args %s',
3652 safe_str(cache_uid))
3653 safe_str(cache_uid))
3653 Session().rollback()
3654 Session().rollback()
3654
3655
3655 @classmethod
3656 @classmethod
3656 def get_active_cache(cls, cache_key):
3657 def get_active_cache(cls, cache_key):
3657 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3658 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3658 if inv_obj:
3659 if inv_obj:
3659 return inv_obj
3660 return inv_obj
3660 return None
3661 return None
3661
3662
3662 @classmethod
3663 @classmethod
3663 def get_namespace_map(cls, namespace):
3664 def get_namespace_map(cls, namespace):
3664 return {
3665 return {
3665 x.cache_key: x
3666 x.cache_key: x
3666 for x in cls.query().filter(cls.cache_args == namespace)}
3667 for x in cls.query().filter(cls.cache_args == namespace)}
3667
3668
3668
3669
3669 class ChangesetComment(Base, BaseModel):
3670 class ChangesetComment(Base, BaseModel):
3670 __tablename__ = 'changeset_comments'
3671 __tablename__ = 'changeset_comments'
3671 __table_args__ = (
3672 __table_args__ = (
3672 Index('cc_revision_idx', 'revision'),
3673 Index('cc_revision_idx', 'revision'),
3673 base_table_args,
3674 base_table_args,
3674 )
3675 )
3675
3676
3676 COMMENT_OUTDATED = u'comment_outdated'
3677 COMMENT_OUTDATED = u'comment_outdated'
3677 COMMENT_TYPE_NOTE = u'note'
3678 COMMENT_TYPE_NOTE = u'note'
3678 COMMENT_TYPE_TODO = u'todo'
3679 COMMENT_TYPE_TODO = u'todo'
3679 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3680 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3680
3681
3681 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3682 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3682 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3683 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3683 revision = Column('revision', String(40), nullable=True)
3684 revision = Column('revision', String(40), nullable=True)
3684 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3685 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3685 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3686 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3686 line_no = Column('line_no', Unicode(10), nullable=True)
3687 line_no = Column('line_no', Unicode(10), nullable=True)
3687 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3688 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3688 f_path = Column('f_path', Unicode(1000), nullable=True)
3689 f_path = Column('f_path', Unicode(1000), nullable=True)
3689 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3690 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3690 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3691 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3691 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3692 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3692 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3693 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3693 renderer = Column('renderer', Unicode(64), nullable=True)
3694 renderer = Column('renderer', Unicode(64), nullable=True)
3694 display_state = Column('display_state', Unicode(128), nullable=True)
3695 display_state = Column('display_state', Unicode(128), nullable=True)
3695
3696
3696 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3697 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3697 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3698 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3698
3699
3699 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3700 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3700 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3701 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3701
3702
3702 author = relationship('User', lazy='joined')
3703 author = relationship('User', lazy='joined')
3703 repo = relationship('Repository')
3704 repo = relationship('Repository')
3704 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3705 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3705 pull_request = relationship('PullRequest', lazy='joined')
3706 pull_request = relationship('PullRequest', lazy='joined')
3706 pull_request_version = relationship('PullRequestVersion')
3707 pull_request_version = relationship('PullRequestVersion')
3707
3708
3708 @classmethod
3709 @classmethod
3709 def get_users(cls, revision=None, pull_request_id=None):
3710 def get_users(cls, revision=None, pull_request_id=None):
3710 """
3711 """
3711 Returns user associated with this ChangesetComment. ie those
3712 Returns user associated with this ChangesetComment. ie those
3712 who actually commented
3713 who actually commented
3713
3714
3714 :param cls:
3715 :param cls:
3715 :param revision:
3716 :param revision:
3716 """
3717 """
3717 q = Session().query(User)\
3718 q = Session().query(User)\
3718 .join(ChangesetComment.author)
3719 .join(ChangesetComment.author)
3719 if revision:
3720 if revision:
3720 q = q.filter(cls.revision == revision)
3721 q = q.filter(cls.revision == revision)
3721 elif pull_request_id:
3722 elif pull_request_id:
3722 q = q.filter(cls.pull_request_id == pull_request_id)
3723 q = q.filter(cls.pull_request_id == pull_request_id)
3723 return q.all()
3724 return q.all()
3724
3725
3725 @classmethod
3726 @classmethod
3726 def get_index_from_version(cls, pr_version, versions):
3727 def get_index_from_version(cls, pr_version, versions):
3727 num_versions = [x.pull_request_version_id for x in versions]
3728 num_versions = [x.pull_request_version_id for x in versions]
3728 try:
3729 try:
3729 return num_versions.index(pr_version) +1
3730 return num_versions.index(pr_version) +1
3730 except (IndexError, ValueError):
3731 except (IndexError, ValueError):
3731 return
3732 return
3732
3733
3733 @property
3734 @property
3734 def outdated(self):
3735 def outdated(self):
3735 return self.display_state == self.COMMENT_OUTDATED
3736 return self.display_state == self.COMMENT_OUTDATED
3736
3737
3737 def outdated_at_version(self, version):
3738 def outdated_at_version(self, version):
3738 """
3739 """
3739 Checks if comment is outdated for given pull request version
3740 Checks if comment is outdated for given pull request version
3740 """
3741 """
3741 return self.outdated and self.pull_request_version_id != version
3742 return self.outdated and self.pull_request_version_id != version
3742
3743
3743 def older_than_version(self, version):
3744 def older_than_version(self, version):
3744 """
3745 """
3745 Checks if comment is made from previous version than given
3746 Checks if comment is made from previous version than given
3746 """
3747 """
3747 if version is None:
3748 if version is None:
3748 return self.pull_request_version_id is not None
3749 return self.pull_request_version_id is not None
3749
3750
3750 return self.pull_request_version_id < version
3751 return self.pull_request_version_id < version
3751
3752
3752 @property
3753 @property
3753 def resolved(self):
3754 def resolved(self):
3754 return self.resolved_by[0] if self.resolved_by else None
3755 return self.resolved_by[0] if self.resolved_by else None
3755
3756
3756 @property
3757 @property
3757 def is_todo(self):
3758 def is_todo(self):
3758 return self.comment_type == self.COMMENT_TYPE_TODO
3759 return self.comment_type == self.COMMENT_TYPE_TODO
3759
3760
3760 @property
3761 @property
3761 def is_inline(self):
3762 def is_inline(self):
3762 return self.line_no and self.f_path
3763 return self.line_no and self.f_path
3763
3764
3764 def get_index_version(self, versions):
3765 def get_index_version(self, versions):
3765 return self.get_index_from_version(
3766 return self.get_index_from_version(
3766 self.pull_request_version_id, versions)
3767 self.pull_request_version_id, versions)
3767
3768
3768 def __repr__(self):
3769 def __repr__(self):
3769 if self.comment_id:
3770 if self.comment_id:
3770 return '<DB:Comment #%s>' % self.comment_id
3771 return '<DB:Comment #%s>' % self.comment_id
3771 else:
3772 else:
3772 return '<DB:Comment at %#x>' % id(self)
3773 return '<DB:Comment at %#x>' % id(self)
3773
3774
3774 def get_api_data(self):
3775 def get_api_data(self):
3775 comment = self
3776 comment = self
3776 data = {
3777 data = {
3777 'comment_id': comment.comment_id,
3778 'comment_id': comment.comment_id,
3778 'comment_type': comment.comment_type,
3779 'comment_type': comment.comment_type,
3779 'comment_text': comment.text,
3780 'comment_text': comment.text,
3780 'comment_status': comment.status_change,
3781 'comment_status': comment.status_change,
3781 'comment_f_path': comment.f_path,
3782 'comment_f_path': comment.f_path,
3782 'comment_lineno': comment.line_no,
3783 'comment_lineno': comment.line_no,
3783 'comment_author': comment.author,
3784 'comment_author': comment.author,
3784 'comment_created_on': comment.created_on,
3785 'comment_created_on': comment.created_on,
3785 'comment_resolved_by': self.resolved
3786 'comment_resolved_by': self.resolved
3786 }
3787 }
3787 return data
3788 return data
3788
3789
3789 def __json__(self):
3790 def __json__(self):
3790 data = dict()
3791 data = dict()
3791 data.update(self.get_api_data())
3792 data.update(self.get_api_data())
3792 return data
3793 return data
3793
3794
3794
3795
3795 class ChangesetStatus(Base, BaseModel):
3796 class ChangesetStatus(Base, BaseModel):
3796 __tablename__ = 'changeset_statuses'
3797 __tablename__ = 'changeset_statuses'
3797 __table_args__ = (
3798 __table_args__ = (
3798 Index('cs_revision_idx', 'revision'),
3799 Index('cs_revision_idx', 'revision'),
3799 Index('cs_version_idx', 'version'),
3800 Index('cs_version_idx', 'version'),
3800 UniqueConstraint('repo_id', 'revision', 'version'),
3801 UniqueConstraint('repo_id', 'revision', 'version'),
3801 base_table_args
3802 base_table_args
3802 )
3803 )
3803
3804
3804 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3805 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3805 STATUS_APPROVED = 'approved'
3806 STATUS_APPROVED = 'approved'
3806 STATUS_REJECTED = 'rejected'
3807 STATUS_REJECTED = 'rejected'
3807 STATUS_UNDER_REVIEW = 'under_review'
3808 STATUS_UNDER_REVIEW = 'under_review'
3808
3809
3809 STATUSES = [
3810 STATUSES = [
3810 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3811 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3811 (STATUS_APPROVED, _("Approved")),
3812 (STATUS_APPROVED, _("Approved")),
3812 (STATUS_REJECTED, _("Rejected")),
3813 (STATUS_REJECTED, _("Rejected")),
3813 (STATUS_UNDER_REVIEW, _("Under Review")),
3814 (STATUS_UNDER_REVIEW, _("Under Review")),
3814 ]
3815 ]
3815
3816
3816 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3817 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3817 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3818 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3818 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3819 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3819 revision = Column('revision', String(40), nullable=False)
3820 revision = Column('revision', String(40), nullable=False)
3820 status = Column('status', String(128), nullable=False, default=DEFAULT)
3821 status = Column('status', String(128), nullable=False, default=DEFAULT)
3821 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3822 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3822 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3823 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3823 version = Column('version', Integer(), nullable=False, default=0)
3824 version = Column('version', Integer(), nullable=False, default=0)
3824 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3825 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3825
3826
3826 author = relationship('User', lazy='joined')
3827 author = relationship('User', lazy='joined')
3827 repo = relationship('Repository')
3828 repo = relationship('Repository')
3828 comment = relationship('ChangesetComment', lazy='joined')
3829 comment = relationship('ChangesetComment', lazy='joined')
3829 pull_request = relationship('PullRequest', lazy='joined')
3830 pull_request = relationship('PullRequest', lazy='joined')
3830
3831
3831 def __unicode__(self):
3832 def __unicode__(self):
3832 return u"<%s('%s[v%s]:%s')>" % (
3833 return u"<%s('%s[v%s]:%s')>" % (
3833 self.__class__.__name__,
3834 self.__class__.__name__,
3834 self.status, self.version, self.author
3835 self.status, self.version, self.author
3835 )
3836 )
3836
3837
3837 @classmethod
3838 @classmethod
3838 def get_status_lbl(cls, value):
3839 def get_status_lbl(cls, value):
3839 return dict(cls.STATUSES).get(value)
3840 return dict(cls.STATUSES).get(value)
3840
3841
3841 @property
3842 @property
3842 def status_lbl(self):
3843 def status_lbl(self):
3843 return ChangesetStatus.get_status_lbl(self.status)
3844 return ChangesetStatus.get_status_lbl(self.status)
3844
3845
3845 def get_api_data(self):
3846 def get_api_data(self):
3846 status = self
3847 status = self
3847 data = {
3848 data = {
3848 'status_id': status.changeset_status_id,
3849 'status_id': status.changeset_status_id,
3849 'status': status.status,
3850 'status': status.status,
3850 }
3851 }
3851 return data
3852 return data
3852
3853
3853 def __json__(self):
3854 def __json__(self):
3854 data = dict()
3855 data = dict()
3855 data.update(self.get_api_data())
3856 data.update(self.get_api_data())
3856 return data
3857 return data
3857
3858
3858
3859
3859 class _SetState(object):
3860 class _SetState(object):
3860 """
3861 """
3861 Context processor allowing changing state for sensitive operation such as
3862 Context processor allowing changing state for sensitive operation such as
3862 pull request update or merge
3863 pull request update or merge
3863 """
3864 """
3864
3865
3865 def __init__(self, pull_request, pr_state, back_state=None):
3866 def __init__(self, pull_request, pr_state, back_state=None):
3866 self._pr = pull_request
3867 self._pr = pull_request
3867 self._org_state = back_state or pull_request.pull_request_state
3868 self._org_state = back_state or pull_request.pull_request_state
3868 self._pr_state = pr_state
3869 self._pr_state = pr_state
3869 self._current_state = None
3870 self._current_state = None
3870
3871
3871 def __enter__(self):
3872 def __enter__(self):
3872 log.debug('StateLock: entering set state context, setting state to: `%s`',
3873 log.debug('StateLock: entering set state context, setting state to: `%s`',
3873 self._pr_state)
3874 self._pr_state)
3874 self.set_pr_state(self._pr_state)
3875 self.set_pr_state(self._pr_state)
3875 return self
3876 return self
3876
3877
3877 def __exit__(self, exc_type, exc_val, exc_tb):
3878 def __exit__(self, exc_type, exc_val, exc_tb):
3878 if exc_val is not None:
3879 if exc_val is not None:
3879 log.error(traceback.format_exc(exc_tb))
3880 log.error(traceback.format_exc(exc_tb))
3880 return None
3881 return None
3881
3882
3882 self.set_pr_state(self._org_state)
3883 self.set_pr_state(self._org_state)
3883 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3884 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3884 self._org_state)
3885 self._org_state)
3885 @property
3886 @property
3886 def state(self):
3887 def state(self):
3887 return self._current_state
3888 return self._current_state
3888
3889
3889 def set_pr_state(self, pr_state):
3890 def set_pr_state(self, pr_state):
3890 try:
3891 try:
3891 self._pr.pull_request_state = pr_state
3892 self._pr.pull_request_state = pr_state
3892 Session().add(self._pr)
3893 Session().add(self._pr)
3893 Session().commit()
3894 Session().commit()
3894 self._current_state = pr_state
3895 self._current_state = pr_state
3895 except Exception:
3896 except Exception:
3896 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3897 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3897 raise
3898 raise
3898
3899
3899
3900
3900 class _PullRequestBase(BaseModel):
3901 class _PullRequestBase(BaseModel):
3901 """
3902 """
3902 Common attributes of pull request and version entries.
3903 Common attributes of pull request and version entries.
3903 """
3904 """
3904
3905
3905 # .status values
3906 # .status values
3906 STATUS_NEW = u'new'
3907 STATUS_NEW = u'new'
3907 STATUS_OPEN = u'open'
3908 STATUS_OPEN = u'open'
3908 STATUS_CLOSED = u'closed'
3909 STATUS_CLOSED = u'closed'
3909
3910
3910 # available states
3911 # available states
3911 STATE_CREATING = u'creating'
3912 STATE_CREATING = u'creating'
3912 STATE_UPDATING = u'updating'
3913 STATE_UPDATING = u'updating'
3913 STATE_MERGING = u'merging'
3914 STATE_MERGING = u'merging'
3914 STATE_CREATED = u'created'
3915 STATE_CREATED = u'created'
3915
3916
3916 title = Column('title', Unicode(255), nullable=True)
3917 title = Column('title', Unicode(255), nullable=True)
3917 description = Column(
3918 description = Column(
3918 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3919 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3919 nullable=True)
3920 nullable=True)
3920 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3921 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3921
3922
3922 # new/open/closed status of pull request (not approve/reject/etc)
3923 # new/open/closed status of pull request (not approve/reject/etc)
3923 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3924 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3924 created_on = Column(
3925 created_on = Column(
3925 'created_on', DateTime(timezone=False), nullable=False,
3926 'created_on', DateTime(timezone=False), nullable=False,
3926 default=datetime.datetime.now)
3927 default=datetime.datetime.now)
3927 updated_on = Column(
3928 updated_on = Column(
3928 'updated_on', DateTime(timezone=False), nullable=False,
3929 'updated_on', DateTime(timezone=False), nullable=False,
3929 default=datetime.datetime.now)
3930 default=datetime.datetime.now)
3930
3931
3931 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3932 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3932
3933
3933 @declared_attr
3934 @declared_attr
3934 def user_id(cls):
3935 def user_id(cls):
3935 return Column(
3936 return Column(
3936 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3937 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3937 unique=None)
3938 unique=None)
3938
3939
3939 # 500 revisions max
3940 # 500 revisions max
3940 _revisions = Column(
3941 _revisions = Column(
3941 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3942 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3942
3943
3943 @declared_attr
3944 @declared_attr
3944 def source_repo_id(cls):
3945 def source_repo_id(cls):
3945 # TODO: dan: rename column to source_repo_id
3946 # TODO: dan: rename column to source_repo_id
3946 return Column(
3947 return Column(
3947 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3948 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3948 nullable=False)
3949 nullable=False)
3949
3950
3950 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3951 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3951
3952
3952 @hybrid_property
3953 @hybrid_property
3953 def source_ref(self):
3954 def source_ref(self):
3954 return self._source_ref
3955 return self._source_ref
3955
3956
3956 @source_ref.setter
3957 @source_ref.setter
3957 def source_ref(self, val):
3958 def source_ref(self, val):
3958 parts = (val or '').split(':')
3959 parts = (val or '').split(':')
3959 if len(parts) != 3:
3960 if len(parts) != 3:
3960 raise ValueError(
3961 raise ValueError(
3961 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3962 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3962 self._source_ref = safe_unicode(val)
3963 self._source_ref = safe_unicode(val)
3963
3964
3964 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3965 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3965
3966
3966 @hybrid_property
3967 @hybrid_property
3967 def target_ref(self):
3968 def target_ref(self):
3968 return self._target_ref
3969 return self._target_ref
3969
3970
3970 @target_ref.setter
3971 @target_ref.setter
3971 def target_ref(self, val):
3972 def target_ref(self, val):
3972 parts = (val or '').split(':')
3973 parts = (val or '').split(':')
3973 if len(parts) != 3:
3974 if len(parts) != 3:
3974 raise ValueError(
3975 raise ValueError(
3975 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3976 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3976 self._target_ref = safe_unicode(val)
3977 self._target_ref = safe_unicode(val)
3977
3978
3978 @declared_attr
3979 @declared_attr
3979 def target_repo_id(cls):
3980 def target_repo_id(cls):
3980 # TODO: dan: rename column to target_repo_id
3981 # TODO: dan: rename column to target_repo_id
3981 return Column(
3982 return Column(
3982 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3983 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3983 nullable=False)
3984 nullable=False)
3984
3985
3985 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3986 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3986
3987
3987 # TODO: dan: rename column to last_merge_source_rev
3988 # TODO: dan: rename column to last_merge_source_rev
3988 _last_merge_source_rev = Column(
3989 _last_merge_source_rev = Column(
3989 'last_merge_org_rev', String(40), nullable=True)
3990 'last_merge_org_rev', String(40), nullable=True)
3990 # TODO: dan: rename column to last_merge_target_rev
3991 # TODO: dan: rename column to last_merge_target_rev
3991 _last_merge_target_rev = Column(
3992 _last_merge_target_rev = Column(
3992 'last_merge_other_rev', String(40), nullable=True)
3993 'last_merge_other_rev', String(40), nullable=True)
3993 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3994 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3994 merge_rev = Column('merge_rev', String(40), nullable=True)
3995 merge_rev = Column('merge_rev', String(40), nullable=True)
3995
3996
3996 reviewer_data = Column(
3997 reviewer_data = Column(
3997 'reviewer_data_json', MutationObj.as_mutable(
3998 'reviewer_data_json', MutationObj.as_mutable(
3998 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3999 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3999
4000
4000 @property
4001 @property
4001 def reviewer_data_json(self):
4002 def reviewer_data_json(self):
4002 return json.dumps(self.reviewer_data)
4003 return json.dumps(self.reviewer_data)
4003
4004
4004 @hybrid_property
4005 @hybrid_property
4005 def description_safe(self):
4006 def description_safe(self):
4006 from rhodecode.lib import helpers as h
4007 from rhodecode.lib import helpers as h
4007 return h.escape(self.description)
4008 return h.escape(self.description)
4008
4009
4009 @hybrid_property
4010 @hybrid_property
4010 def revisions(self):
4011 def revisions(self):
4011 return self._revisions.split(':') if self._revisions else []
4012 return self._revisions.split(':') if self._revisions else []
4012
4013
4013 @revisions.setter
4014 @revisions.setter
4014 def revisions(self, val):
4015 def revisions(self, val):
4015 self._revisions = u':'.join(val)
4016 self._revisions = u':'.join(val)
4016
4017
4017 @hybrid_property
4018 @hybrid_property
4018 def last_merge_status(self):
4019 def last_merge_status(self):
4019 return safe_int(self._last_merge_status)
4020 return safe_int(self._last_merge_status)
4020
4021
4021 @last_merge_status.setter
4022 @last_merge_status.setter
4022 def last_merge_status(self, val):
4023 def last_merge_status(self, val):
4023 self._last_merge_status = val
4024 self._last_merge_status = val
4024
4025
4025 @declared_attr
4026 @declared_attr
4026 def author(cls):
4027 def author(cls):
4027 return relationship('User', lazy='joined')
4028 return relationship('User', lazy='joined')
4028
4029
4029 @declared_attr
4030 @declared_attr
4030 def source_repo(cls):
4031 def source_repo(cls):
4031 return relationship(
4032 return relationship(
4032 'Repository',
4033 'Repository',
4033 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4034 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4034
4035
4035 @property
4036 @property
4036 def source_ref_parts(self):
4037 def source_ref_parts(self):
4037 return self.unicode_to_reference(self.source_ref)
4038 return self.unicode_to_reference(self.source_ref)
4038
4039
4039 @declared_attr
4040 @declared_attr
4040 def target_repo(cls):
4041 def target_repo(cls):
4041 return relationship(
4042 return relationship(
4042 'Repository',
4043 'Repository',
4043 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4044 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4044
4045
4045 @property
4046 @property
4046 def target_ref_parts(self):
4047 def target_ref_parts(self):
4047 return self.unicode_to_reference(self.target_ref)
4048 return self.unicode_to_reference(self.target_ref)
4048
4049
4049 @property
4050 @property
4050 def shadow_merge_ref(self):
4051 def shadow_merge_ref(self):
4051 return self.unicode_to_reference(self._shadow_merge_ref)
4052 return self.unicode_to_reference(self._shadow_merge_ref)
4052
4053
4053 @shadow_merge_ref.setter
4054 @shadow_merge_ref.setter
4054 def shadow_merge_ref(self, ref):
4055 def shadow_merge_ref(self, ref):
4055 self._shadow_merge_ref = self.reference_to_unicode(ref)
4056 self._shadow_merge_ref = self.reference_to_unicode(ref)
4056
4057
4057 @staticmethod
4058 @staticmethod
4058 def unicode_to_reference(raw):
4059 def unicode_to_reference(raw):
4059 """
4060 """
4060 Convert a unicode (or string) to a reference object.
4061 Convert a unicode (or string) to a reference object.
4061 If unicode evaluates to False it returns None.
4062 If unicode evaluates to False it returns None.
4062 """
4063 """
4063 if raw:
4064 if raw:
4064 refs = raw.split(':')
4065 refs = raw.split(':')
4065 return Reference(*refs)
4066 return Reference(*refs)
4066 else:
4067 else:
4067 return None
4068 return None
4068
4069
4069 @staticmethod
4070 @staticmethod
4070 def reference_to_unicode(ref):
4071 def reference_to_unicode(ref):
4071 """
4072 """
4072 Convert a reference object to unicode.
4073 Convert a reference object to unicode.
4073 If reference is None it returns None.
4074 If reference is None it returns None.
4074 """
4075 """
4075 if ref:
4076 if ref:
4076 return u':'.join(ref)
4077 return u':'.join(ref)
4077 else:
4078 else:
4078 return None
4079 return None
4079
4080
4080 def get_api_data(self, with_merge_state=True):
4081 def get_api_data(self, with_merge_state=True):
4081 from rhodecode.model.pull_request import PullRequestModel
4082 from rhodecode.model.pull_request import PullRequestModel
4082
4083
4083 pull_request = self
4084 pull_request = self
4084 if with_merge_state:
4085 if with_merge_state:
4085 merge_status = PullRequestModel().merge_status(pull_request)
4086 merge_status = PullRequestModel().merge_status(pull_request)
4086 merge_state = {
4087 merge_state = {
4087 'status': merge_status[0],
4088 'status': merge_status[0],
4088 'message': safe_unicode(merge_status[1]),
4089 'message': safe_unicode(merge_status[1]),
4089 }
4090 }
4090 else:
4091 else:
4091 merge_state = {'status': 'not_available',
4092 merge_state = {'status': 'not_available',
4092 'message': 'not_available'}
4093 'message': 'not_available'}
4093
4094
4094 merge_data = {
4095 merge_data = {
4095 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4096 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4096 'reference': (
4097 'reference': (
4097 pull_request.shadow_merge_ref._asdict()
4098 pull_request.shadow_merge_ref._asdict()
4098 if pull_request.shadow_merge_ref else None),
4099 if pull_request.shadow_merge_ref else None),
4099 }
4100 }
4100
4101
4101 data = {
4102 data = {
4102 'pull_request_id': pull_request.pull_request_id,
4103 'pull_request_id': pull_request.pull_request_id,
4103 'url': PullRequestModel().get_url(pull_request),
4104 'url': PullRequestModel().get_url(pull_request),
4104 'title': pull_request.title,
4105 'title': pull_request.title,
4105 'description': pull_request.description,
4106 'description': pull_request.description,
4106 'status': pull_request.status,
4107 'status': pull_request.status,
4107 'state': pull_request.pull_request_state,
4108 'state': pull_request.pull_request_state,
4108 'created_on': pull_request.created_on,
4109 'created_on': pull_request.created_on,
4109 'updated_on': pull_request.updated_on,
4110 'updated_on': pull_request.updated_on,
4110 'commit_ids': pull_request.revisions,
4111 'commit_ids': pull_request.revisions,
4111 'review_status': pull_request.calculated_review_status(),
4112 'review_status': pull_request.calculated_review_status(),
4112 'mergeable': merge_state,
4113 'mergeable': merge_state,
4113 'source': {
4114 'source': {
4114 'clone_url': pull_request.source_repo.clone_url(),
4115 'clone_url': pull_request.source_repo.clone_url(),
4115 'repository': pull_request.source_repo.repo_name,
4116 'repository': pull_request.source_repo.repo_name,
4116 'reference': {
4117 'reference': {
4117 'name': pull_request.source_ref_parts.name,
4118 'name': pull_request.source_ref_parts.name,
4118 'type': pull_request.source_ref_parts.type,
4119 'type': pull_request.source_ref_parts.type,
4119 'commit_id': pull_request.source_ref_parts.commit_id,
4120 'commit_id': pull_request.source_ref_parts.commit_id,
4120 },
4121 },
4121 },
4122 },
4122 'target': {
4123 'target': {
4123 'clone_url': pull_request.target_repo.clone_url(),
4124 'clone_url': pull_request.target_repo.clone_url(),
4124 'repository': pull_request.target_repo.repo_name,
4125 'repository': pull_request.target_repo.repo_name,
4125 'reference': {
4126 'reference': {
4126 'name': pull_request.target_ref_parts.name,
4127 'name': pull_request.target_ref_parts.name,
4127 'type': pull_request.target_ref_parts.type,
4128 'type': pull_request.target_ref_parts.type,
4128 'commit_id': pull_request.target_ref_parts.commit_id,
4129 'commit_id': pull_request.target_ref_parts.commit_id,
4129 },
4130 },
4130 },
4131 },
4131 'merge': merge_data,
4132 'merge': merge_data,
4132 'author': pull_request.author.get_api_data(include_secrets=False,
4133 'author': pull_request.author.get_api_data(include_secrets=False,
4133 details='basic'),
4134 details='basic'),
4134 'reviewers': [
4135 'reviewers': [
4135 {
4136 {
4136 'user': reviewer.get_api_data(include_secrets=False,
4137 'user': reviewer.get_api_data(include_secrets=False,
4137 details='basic'),
4138 details='basic'),
4138 'reasons': reasons,
4139 'reasons': reasons,
4139 'review_status': st[0][1].status if st else 'not_reviewed',
4140 'review_status': st[0][1].status if st else 'not_reviewed',
4140 }
4141 }
4141 for obj, reviewer, reasons, mandatory, st in
4142 for obj, reviewer, reasons, mandatory, st in
4142 pull_request.reviewers_statuses()
4143 pull_request.reviewers_statuses()
4143 ]
4144 ]
4144 }
4145 }
4145
4146
4146 return data
4147 return data
4147
4148
4148 def set_state(self, pull_request_state, final_state=None):
4149 def set_state(self, pull_request_state, final_state=None):
4149 """
4150 """
4150 # goes from initial state to updating to initial state.
4151 # goes from initial state to updating to initial state.
4151 # initial state can be changed by specifying back_state=
4152 # initial state can be changed by specifying back_state=
4152 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4153 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4153 pull_request.merge()
4154 pull_request.merge()
4154
4155
4155 :param pull_request_state:
4156 :param pull_request_state:
4156 :param final_state:
4157 :param final_state:
4157
4158
4158 """
4159 """
4159
4160
4160 return _SetState(self, pull_request_state, back_state=final_state)
4161 return _SetState(self, pull_request_state, back_state=final_state)
4161
4162
4162
4163
4163 class PullRequest(Base, _PullRequestBase):
4164 class PullRequest(Base, _PullRequestBase):
4164 __tablename__ = 'pull_requests'
4165 __tablename__ = 'pull_requests'
4165 __table_args__ = (
4166 __table_args__ = (
4166 base_table_args,
4167 base_table_args,
4167 )
4168 )
4168
4169
4169 pull_request_id = Column(
4170 pull_request_id = Column(
4170 'pull_request_id', Integer(), nullable=False, primary_key=True)
4171 'pull_request_id', Integer(), nullable=False, primary_key=True)
4171
4172
4172 def __repr__(self):
4173 def __repr__(self):
4173 if self.pull_request_id:
4174 if self.pull_request_id:
4174 return '<DB:PullRequest #%s>' % self.pull_request_id
4175 return '<DB:PullRequest #%s>' % self.pull_request_id
4175 else:
4176 else:
4176 return '<DB:PullRequest at %#x>' % id(self)
4177 return '<DB:PullRequest at %#x>' % id(self)
4177
4178
4178 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4179 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4179 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4180 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4180 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4181 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4181 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4182 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4182 lazy='dynamic')
4183 lazy='dynamic')
4183
4184
4184 @classmethod
4185 @classmethod
4185 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4186 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4186 internal_methods=None):
4187 internal_methods=None):
4187
4188
4188 class PullRequestDisplay(object):
4189 class PullRequestDisplay(object):
4189 """
4190 """
4190 Special object wrapper for showing PullRequest data via Versions
4191 Special object wrapper for showing PullRequest data via Versions
4191 It mimics PR object as close as possible. This is read only object
4192 It mimics PR object as close as possible. This is read only object
4192 just for display
4193 just for display
4193 """
4194 """
4194
4195
4195 def __init__(self, attrs, internal=None):
4196 def __init__(self, attrs, internal=None):
4196 self.attrs = attrs
4197 self.attrs = attrs
4197 # internal have priority over the given ones via attrs
4198 # internal have priority over the given ones via attrs
4198 self.internal = internal or ['versions']
4199 self.internal = internal or ['versions']
4199
4200
4200 def __getattr__(self, item):
4201 def __getattr__(self, item):
4201 if item in self.internal:
4202 if item in self.internal:
4202 return getattr(self, item)
4203 return getattr(self, item)
4203 try:
4204 try:
4204 return self.attrs[item]
4205 return self.attrs[item]
4205 except KeyError:
4206 except KeyError:
4206 raise AttributeError(
4207 raise AttributeError(
4207 '%s object has no attribute %s' % (self, item))
4208 '%s object has no attribute %s' % (self, item))
4208
4209
4209 def __repr__(self):
4210 def __repr__(self):
4210 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4211 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4211
4212
4212 def versions(self):
4213 def versions(self):
4213 return pull_request_obj.versions.order_by(
4214 return pull_request_obj.versions.order_by(
4214 PullRequestVersion.pull_request_version_id).all()
4215 PullRequestVersion.pull_request_version_id).all()
4215
4216
4216 def is_closed(self):
4217 def is_closed(self):
4217 return pull_request_obj.is_closed()
4218 return pull_request_obj.is_closed()
4218
4219
4219 @property
4220 @property
4220 def pull_request_version_id(self):
4221 def pull_request_version_id(self):
4221 return getattr(pull_request_obj, 'pull_request_version_id', None)
4222 return getattr(pull_request_obj, 'pull_request_version_id', None)
4222
4223
4223 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4224 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4224
4225
4225 attrs.author = StrictAttributeDict(
4226 attrs.author = StrictAttributeDict(
4226 pull_request_obj.author.get_api_data())
4227 pull_request_obj.author.get_api_data())
4227 if pull_request_obj.target_repo:
4228 if pull_request_obj.target_repo:
4228 attrs.target_repo = StrictAttributeDict(
4229 attrs.target_repo = StrictAttributeDict(
4229 pull_request_obj.target_repo.get_api_data())
4230 pull_request_obj.target_repo.get_api_data())
4230 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4231 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4231
4232
4232 if pull_request_obj.source_repo:
4233 if pull_request_obj.source_repo:
4233 attrs.source_repo = StrictAttributeDict(
4234 attrs.source_repo = StrictAttributeDict(
4234 pull_request_obj.source_repo.get_api_data())
4235 pull_request_obj.source_repo.get_api_data())
4235 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4236 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4236
4237
4237 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4238 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4238 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4239 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4239 attrs.revisions = pull_request_obj.revisions
4240 attrs.revisions = pull_request_obj.revisions
4240
4241
4241 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4242 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4242 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4243 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4243 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4244 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4244
4245
4245 return PullRequestDisplay(attrs, internal=internal_methods)
4246 return PullRequestDisplay(attrs, internal=internal_methods)
4246
4247
4247 def is_closed(self):
4248 def is_closed(self):
4248 return self.status == self.STATUS_CLOSED
4249 return self.status == self.STATUS_CLOSED
4249
4250
4250 def __json__(self):
4251 def __json__(self):
4251 return {
4252 return {
4252 'revisions': self.revisions,
4253 'revisions': self.revisions,
4253 }
4254 }
4254
4255
4255 def calculated_review_status(self):
4256 def calculated_review_status(self):
4256 from rhodecode.model.changeset_status import ChangesetStatusModel
4257 from rhodecode.model.changeset_status import ChangesetStatusModel
4257 return ChangesetStatusModel().calculated_review_status(self)
4258 return ChangesetStatusModel().calculated_review_status(self)
4258
4259
4259 def reviewers_statuses(self):
4260 def reviewers_statuses(self):
4260 from rhodecode.model.changeset_status import ChangesetStatusModel
4261 from rhodecode.model.changeset_status import ChangesetStatusModel
4261 return ChangesetStatusModel().reviewers_statuses(self)
4262 return ChangesetStatusModel().reviewers_statuses(self)
4262
4263
4263 @property
4264 @property
4264 def workspace_id(self):
4265 def workspace_id(self):
4265 from rhodecode.model.pull_request import PullRequestModel
4266 from rhodecode.model.pull_request import PullRequestModel
4266 return PullRequestModel()._workspace_id(self)
4267 return PullRequestModel()._workspace_id(self)
4267
4268
4268 def get_shadow_repo(self):
4269 def get_shadow_repo(self):
4269 workspace_id = self.workspace_id
4270 workspace_id = self.workspace_id
4270 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4271 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4271 if os.path.isdir(shadow_repository_path):
4272 if os.path.isdir(shadow_repository_path):
4272 vcs_obj = self.target_repo.scm_instance()
4273 vcs_obj = self.target_repo.scm_instance()
4273 return vcs_obj.get_shadow_instance(shadow_repository_path)
4274 return vcs_obj.get_shadow_instance(shadow_repository_path)
4274
4275
4275
4276
4276 class PullRequestVersion(Base, _PullRequestBase):
4277 class PullRequestVersion(Base, _PullRequestBase):
4277 __tablename__ = 'pull_request_versions'
4278 __tablename__ = 'pull_request_versions'
4278 __table_args__ = (
4279 __table_args__ = (
4279 base_table_args,
4280 base_table_args,
4280 )
4281 )
4281
4282
4282 pull_request_version_id = Column(
4283 pull_request_version_id = Column(
4283 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4284 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4284 pull_request_id = Column(
4285 pull_request_id = Column(
4285 'pull_request_id', Integer(),
4286 'pull_request_id', Integer(),
4286 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4287 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4287 pull_request = relationship('PullRequest')
4288 pull_request = relationship('PullRequest')
4288
4289
4289 def __repr__(self):
4290 def __repr__(self):
4290 if self.pull_request_version_id:
4291 if self.pull_request_version_id:
4291 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4292 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4292 else:
4293 else:
4293 return '<DB:PullRequestVersion at %#x>' % id(self)
4294 return '<DB:PullRequestVersion at %#x>' % id(self)
4294
4295
4295 @property
4296 @property
4296 def reviewers(self):
4297 def reviewers(self):
4297 return self.pull_request.reviewers
4298 return self.pull_request.reviewers
4298
4299
4299 @property
4300 @property
4300 def versions(self):
4301 def versions(self):
4301 return self.pull_request.versions
4302 return self.pull_request.versions
4302
4303
4303 def is_closed(self):
4304 def is_closed(self):
4304 # calculate from original
4305 # calculate from original
4305 return self.pull_request.status == self.STATUS_CLOSED
4306 return self.pull_request.status == self.STATUS_CLOSED
4306
4307
4307 def calculated_review_status(self):
4308 def calculated_review_status(self):
4308 return self.pull_request.calculated_review_status()
4309 return self.pull_request.calculated_review_status()
4309
4310
4310 def reviewers_statuses(self):
4311 def reviewers_statuses(self):
4311 return self.pull_request.reviewers_statuses()
4312 return self.pull_request.reviewers_statuses()
4312
4313
4313
4314
4314 class PullRequestReviewers(Base, BaseModel):
4315 class PullRequestReviewers(Base, BaseModel):
4315 __tablename__ = 'pull_request_reviewers'
4316 __tablename__ = 'pull_request_reviewers'
4316 __table_args__ = (
4317 __table_args__ = (
4317 base_table_args,
4318 base_table_args,
4318 )
4319 )
4319
4320
4320 @hybrid_property
4321 @hybrid_property
4321 def reasons(self):
4322 def reasons(self):
4322 if not self._reasons:
4323 if not self._reasons:
4323 return []
4324 return []
4324 return self._reasons
4325 return self._reasons
4325
4326
4326 @reasons.setter
4327 @reasons.setter
4327 def reasons(self, val):
4328 def reasons(self, val):
4328 val = val or []
4329 val = val or []
4329 if any(not isinstance(x, compat.string_types) for x in val):
4330 if any(not isinstance(x, compat.string_types) for x in val):
4330 raise Exception('invalid reasons type, must be list of strings')
4331 raise Exception('invalid reasons type, must be list of strings')
4331 self._reasons = val
4332 self._reasons = val
4332
4333
4333 pull_requests_reviewers_id = Column(
4334 pull_requests_reviewers_id = Column(
4334 'pull_requests_reviewers_id', Integer(), nullable=False,
4335 'pull_requests_reviewers_id', Integer(), nullable=False,
4335 primary_key=True)
4336 primary_key=True)
4336 pull_request_id = Column(
4337 pull_request_id = Column(
4337 "pull_request_id", Integer(),
4338 "pull_request_id", Integer(),
4338 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4339 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4339 user_id = Column(
4340 user_id = Column(
4340 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4341 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4341 _reasons = Column(
4342 _reasons = Column(
4342 'reason', MutationList.as_mutable(
4343 'reason', MutationList.as_mutable(
4343 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4344 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4344
4345
4345 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4346 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4346 user = relationship('User')
4347 user = relationship('User')
4347 pull_request = relationship('PullRequest')
4348 pull_request = relationship('PullRequest')
4348
4349
4349 rule_data = Column(
4350 rule_data = Column(
4350 'rule_data_json',
4351 'rule_data_json',
4351 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4352 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4352
4353
4353 def rule_user_group_data(self):
4354 def rule_user_group_data(self):
4354 """
4355 """
4355 Returns the voting user group rule data for this reviewer
4356 Returns the voting user group rule data for this reviewer
4356 """
4357 """
4357
4358
4358 if self.rule_data and 'vote_rule' in self.rule_data:
4359 if self.rule_data and 'vote_rule' in self.rule_data:
4359 user_group_data = {}
4360 user_group_data = {}
4360 if 'rule_user_group_entry_id' in self.rule_data:
4361 if 'rule_user_group_entry_id' in self.rule_data:
4361 # means a group with voting rules !
4362 # means a group with voting rules !
4362 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4363 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4363 user_group_data['name'] = self.rule_data['rule_name']
4364 user_group_data['name'] = self.rule_data['rule_name']
4364 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4365 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4365
4366
4366 return user_group_data
4367 return user_group_data
4367
4368
4368 def __unicode__(self):
4369 def __unicode__(self):
4369 return u"<%s('id:%s')>" % (self.__class__.__name__,
4370 return u"<%s('id:%s')>" % (self.__class__.__name__,
4370 self.pull_requests_reviewers_id)
4371 self.pull_requests_reviewers_id)
4371
4372
4372
4373
4373 class Notification(Base, BaseModel):
4374 class Notification(Base, BaseModel):
4374 __tablename__ = 'notifications'
4375 __tablename__ = 'notifications'
4375 __table_args__ = (
4376 __table_args__ = (
4376 Index('notification_type_idx', 'type'),
4377 Index('notification_type_idx', 'type'),
4377 base_table_args,
4378 base_table_args,
4378 )
4379 )
4379
4380
4380 TYPE_CHANGESET_COMMENT = u'cs_comment'
4381 TYPE_CHANGESET_COMMENT = u'cs_comment'
4381 TYPE_MESSAGE = u'message'
4382 TYPE_MESSAGE = u'message'
4382 TYPE_MENTION = u'mention'
4383 TYPE_MENTION = u'mention'
4383 TYPE_REGISTRATION = u'registration'
4384 TYPE_REGISTRATION = u'registration'
4384 TYPE_PULL_REQUEST = u'pull_request'
4385 TYPE_PULL_REQUEST = u'pull_request'
4385 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4386 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4386
4387
4387 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4388 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4388 subject = Column('subject', Unicode(512), nullable=True)
4389 subject = Column('subject', Unicode(512), nullable=True)
4389 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4390 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4390 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4391 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4391 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4392 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4392 type_ = Column('type', Unicode(255))
4393 type_ = Column('type', Unicode(255))
4393
4394
4394 created_by_user = relationship('User')
4395 created_by_user = relationship('User')
4395 notifications_to_users = relationship('UserNotification', lazy='joined',
4396 notifications_to_users = relationship('UserNotification', lazy='joined',
4396 cascade="all, delete-orphan")
4397 cascade="all, delete-orphan")
4397
4398
4398 @property
4399 @property
4399 def recipients(self):
4400 def recipients(self):
4400 return [x.user for x in UserNotification.query()\
4401 return [x.user for x in UserNotification.query()\
4401 .filter(UserNotification.notification == self)\
4402 .filter(UserNotification.notification == self)\
4402 .order_by(UserNotification.user_id.asc()).all()]
4403 .order_by(UserNotification.user_id.asc()).all()]
4403
4404
4404 @classmethod
4405 @classmethod
4405 def create(cls, created_by, subject, body, recipients, type_=None):
4406 def create(cls, created_by, subject, body, recipients, type_=None):
4406 if type_ is None:
4407 if type_ is None:
4407 type_ = Notification.TYPE_MESSAGE
4408 type_ = Notification.TYPE_MESSAGE
4408
4409
4409 notification = cls()
4410 notification = cls()
4410 notification.created_by_user = created_by
4411 notification.created_by_user = created_by
4411 notification.subject = subject
4412 notification.subject = subject
4412 notification.body = body
4413 notification.body = body
4413 notification.type_ = type_
4414 notification.type_ = type_
4414 notification.created_on = datetime.datetime.now()
4415 notification.created_on = datetime.datetime.now()
4415
4416
4416 # For each recipient link the created notification to his account
4417 # For each recipient link the created notification to his account
4417 for u in recipients:
4418 for u in recipients:
4418 assoc = UserNotification()
4419 assoc = UserNotification()
4419 assoc.user_id = u.user_id
4420 assoc.user_id = u.user_id
4420 assoc.notification = notification
4421 assoc.notification = notification
4421
4422
4422 # if created_by is inside recipients mark his notification
4423 # if created_by is inside recipients mark his notification
4423 # as read
4424 # as read
4424 if u.user_id == created_by.user_id:
4425 if u.user_id == created_by.user_id:
4425 assoc.read = True
4426 assoc.read = True
4426 Session().add(assoc)
4427 Session().add(assoc)
4427
4428
4428 Session().add(notification)
4429 Session().add(notification)
4429
4430
4430 return notification
4431 return notification
4431
4432
4432
4433
4433 class UserNotification(Base, BaseModel):
4434 class UserNotification(Base, BaseModel):
4434 __tablename__ = 'user_to_notification'
4435 __tablename__ = 'user_to_notification'
4435 __table_args__ = (
4436 __table_args__ = (
4436 UniqueConstraint('user_id', 'notification_id'),
4437 UniqueConstraint('user_id', 'notification_id'),
4437 base_table_args
4438 base_table_args
4438 )
4439 )
4439
4440
4440 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4441 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4441 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4442 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4442 read = Column('read', Boolean, default=False)
4443 read = Column('read', Boolean, default=False)
4443 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4444 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4444
4445
4445 user = relationship('User', lazy="joined")
4446 user = relationship('User', lazy="joined")
4446 notification = relationship('Notification', lazy="joined",
4447 notification = relationship('Notification', lazy="joined",
4447 order_by=lambda: Notification.created_on.desc(),)
4448 order_by=lambda: Notification.created_on.desc(),)
4448
4449
4449 def mark_as_read(self):
4450 def mark_as_read(self):
4450 self.read = True
4451 self.read = True
4451 Session().add(self)
4452 Session().add(self)
4452
4453
4453
4454
4454 class Gist(Base, BaseModel):
4455 class Gist(Base, BaseModel):
4455 __tablename__ = 'gists'
4456 __tablename__ = 'gists'
4456 __table_args__ = (
4457 __table_args__ = (
4457 Index('g_gist_access_id_idx', 'gist_access_id'),
4458 Index('g_gist_access_id_idx', 'gist_access_id'),
4458 Index('g_created_on_idx', 'created_on'),
4459 Index('g_created_on_idx', 'created_on'),
4459 base_table_args
4460 base_table_args
4460 )
4461 )
4461
4462
4462 GIST_PUBLIC = u'public'
4463 GIST_PUBLIC = u'public'
4463 GIST_PRIVATE = u'private'
4464 GIST_PRIVATE = u'private'
4464 DEFAULT_FILENAME = u'gistfile1.txt'
4465 DEFAULT_FILENAME = u'gistfile1.txt'
4465
4466
4466 ACL_LEVEL_PUBLIC = u'acl_public'
4467 ACL_LEVEL_PUBLIC = u'acl_public'
4467 ACL_LEVEL_PRIVATE = u'acl_private'
4468 ACL_LEVEL_PRIVATE = u'acl_private'
4468
4469
4469 gist_id = Column('gist_id', Integer(), primary_key=True)
4470 gist_id = Column('gist_id', Integer(), primary_key=True)
4470 gist_access_id = Column('gist_access_id', Unicode(250))
4471 gist_access_id = Column('gist_access_id', Unicode(250))
4471 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4472 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4472 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4473 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4473 gist_expires = Column('gist_expires', Float(53), nullable=False)
4474 gist_expires = Column('gist_expires', Float(53), nullable=False)
4474 gist_type = Column('gist_type', Unicode(128), nullable=False)
4475 gist_type = Column('gist_type', Unicode(128), nullable=False)
4475 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4476 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4476 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4477 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4477 acl_level = Column('acl_level', Unicode(128), nullable=True)
4478 acl_level = Column('acl_level', Unicode(128), nullable=True)
4478
4479
4479 owner = relationship('User')
4480 owner = relationship('User')
4480
4481
4481 def __repr__(self):
4482 def __repr__(self):
4482 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4483 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4483
4484
4484 @hybrid_property
4485 @hybrid_property
4485 def description_safe(self):
4486 def description_safe(self):
4486 from rhodecode.lib import helpers as h
4487 from rhodecode.lib import helpers as h
4487 return h.escape(self.gist_description)
4488 return h.escape(self.gist_description)
4488
4489
4489 @classmethod
4490 @classmethod
4490 def get_or_404(cls, id_):
4491 def get_or_404(cls, id_):
4491 from pyramid.httpexceptions import HTTPNotFound
4492 from pyramid.httpexceptions import HTTPNotFound
4492
4493
4493 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4494 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4494 if not res:
4495 if not res:
4495 raise HTTPNotFound()
4496 raise HTTPNotFound()
4496 return res
4497 return res
4497
4498
4498 @classmethod
4499 @classmethod
4499 def get_by_access_id(cls, gist_access_id):
4500 def get_by_access_id(cls, gist_access_id):
4500 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4501 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4501
4502
4502 def gist_url(self):
4503 def gist_url(self):
4503 from rhodecode.model.gist import GistModel
4504 from rhodecode.model.gist import GistModel
4504 return GistModel().get_url(self)
4505 return GistModel().get_url(self)
4505
4506
4506 @classmethod
4507 @classmethod
4507 def base_path(cls):
4508 def base_path(cls):
4508 """
4509 """
4509 Returns base path when all gists are stored
4510 Returns base path when all gists are stored
4510
4511
4511 :param cls:
4512 :param cls:
4512 """
4513 """
4513 from rhodecode.model.gist import GIST_STORE_LOC
4514 from rhodecode.model.gist import GIST_STORE_LOC
4514 q = Session().query(RhodeCodeUi)\
4515 q = Session().query(RhodeCodeUi)\
4515 .filter(RhodeCodeUi.ui_key == URL_SEP)
4516 .filter(RhodeCodeUi.ui_key == URL_SEP)
4516 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4517 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4517 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4518 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4518
4519
4519 def get_api_data(self):
4520 def get_api_data(self):
4520 """
4521 """
4521 Common function for generating gist related data for API
4522 Common function for generating gist related data for API
4522 """
4523 """
4523 gist = self
4524 gist = self
4524 data = {
4525 data = {
4525 'gist_id': gist.gist_id,
4526 'gist_id': gist.gist_id,
4526 'type': gist.gist_type,
4527 'type': gist.gist_type,
4527 'access_id': gist.gist_access_id,
4528 'access_id': gist.gist_access_id,
4528 'description': gist.gist_description,
4529 'description': gist.gist_description,
4529 'url': gist.gist_url(),
4530 'url': gist.gist_url(),
4530 'expires': gist.gist_expires,
4531 'expires': gist.gist_expires,
4531 'created_on': gist.created_on,
4532 'created_on': gist.created_on,
4532 'modified_at': gist.modified_at,
4533 'modified_at': gist.modified_at,
4533 'content': None,
4534 'content': None,
4534 'acl_level': gist.acl_level,
4535 'acl_level': gist.acl_level,
4535 }
4536 }
4536 return data
4537 return data
4537
4538
4538 def __json__(self):
4539 def __json__(self):
4539 data = dict(
4540 data = dict(
4540 )
4541 )
4541 data.update(self.get_api_data())
4542 data.update(self.get_api_data())
4542 return data
4543 return data
4543 # SCM functions
4544 # SCM functions
4544
4545
4545 def scm_instance(self, **kwargs):
4546 def scm_instance(self, **kwargs):
4546 """
4547 """
4547 Get an instance of VCS Repository
4548 Get an instance of VCS Repository
4548
4549
4549 :param kwargs:
4550 :param kwargs:
4550 """
4551 """
4551 from rhodecode.model.gist import GistModel
4552 from rhodecode.model.gist import GistModel
4552 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4553 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4553 return get_vcs_instance(
4554 return get_vcs_instance(
4554 repo_path=safe_str(full_repo_path), create=False,
4555 repo_path=safe_str(full_repo_path), create=False,
4555 _vcs_alias=GistModel.vcs_backend)
4556 _vcs_alias=GistModel.vcs_backend)
4556
4557
4557
4558
4558 class ExternalIdentity(Base, BaseModel):
4559 class ExternalIdentity(Base, BaseModel):
4559 __tablename__ = 'external_identities'
4560 __tablename__ = 'external_identities'
4560 __table_args__ = (
4561 __table_args__ = (
4561 Index('local_user_id_idx', 'local_user_id'),
4562 Index('local_user_id_idx', 'local_user_id'),
4562 Index('external_id_idx', 'external_id'),
4563 Index('external_id_idx', 'external_id'),
4563 base_table_args
4564 base_table_args
4564 )
4565 )
4565
4566
4566 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4567 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4567 external_username = Column('external_username', Unicode(1024), default=u'')
4568 external_username = Column('external_username', Unicode(1024), default=u'')
4568 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4569 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4569 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4570 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4570 access_token = Column('access_token', String(1024), default=u'')
4571 access_token = Column('access_token', String(1024), default=u'')
4571 alt_token = Column('alt_token', String(1024), default=u'')
4572 alt_token = Column('alt_token', String(1024), default=u'')
4572 token_secret = Column('token_secret', String(1024), default=u'')
4573 token_secret = Column('token_secret', String(1024), default=u'')
4573
4574
4574 @classmethod
4575 @classmethod
4575 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4576 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4576 """
4577 """
4577 Returns ExternalIdentity instance based on search params
4578 Returns ExternalIdentity instance based on search params
4578
4579
4579 :param external_id:
4580 :param external_id:
4580 :param provider_name:
4581 :param provider_name:
4581 :return: ExternalIdentity
4582 :return: ExternalIdentity
4582 """
4583 """
4583 query = cls.query()
4584 query = cls.query()
4584 query = query.filter(cls.external_id == external_id)
4585 query = query.filter(cls.external_id == external_id)
4585 query = query.filter(cls.provider_name == provider_name)
4586 query = query.filter(cls.provider_name == provider_name)
4586 if local_user_id:
4587 if local_user_id:
4587 query = query.filter(cls.local_user_id == local_user_id)
4588 query = query.filter(cls.local_user_id == local_user_id)
4588 return query.first()
4589 return query.first()
4589
4590
4590 @classmethod
4591 @classmethod
4591 def user_by_external_id_and_provider(cls, external_id, provider_name):
4592 def user_by_external_id_and_provider(cls, external_id, provider_name):
4592 """
4593 """
4593 Returns User instance based on search params
4594 Returns User instance based on search params
4594
4595
4595 :param external_id:
4596 :param external_id:
4596 :param provider_name:
4597 :param provider_name:
4597 :return: User
4598 :return: User
4598 """
4599 """
4599 query = User.query()
4600 query = User.query()
4600 query = query.filter(cls.external_id == external_id)
4601 query = query.filter(cls.external_id == external_id)
4601 query = query.filter(cls.provider_name == provider_name)
4602 query = query.filter(cls.provider_name == provider_name)
4602 query = query.filter(User.user_id == cls.local_user_id)
4603 query = query.filter(User.user_id == cls.local_user_id)
4603 return query.first()
4604 return query.first()
4604
4605
4605 @classmethod
4606 @classmethod
4606 def by_local_user_id(cls, local_user_id):
4607 def by_local_user_id(cls, local_user_id):
4607 """
4608 """
4608 Returns all tokens for user
4609 Returns all tokens for user
4609
4610
4610 :param local_user_id:
4611 :param local_user_id:
4611 :return: ExternalIdentity
4612 :return: ExternalIdentity
4612 """
4613 """
4613 query = cls.query()
4614 query = cls.query()
4614 query = query.filter(cls.local_user_id == local_user_id)
4615 query = query.filter(cls.local_user_id == local_user_id)
4615 return query
4616 return query
4616
4617
4617 @classmethod
4618 @classmethod
4618 def load_provider_plugin(cls, plugin_id):
4619 def load_provider_plugin(cls, plugin_id):
4619 from rhodecode.authentication.base import loadplugin
4620 from rhodecode.authentication.base import loadplugin
4620 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4621 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4621 auth_plugin = loadplugin(_plugin_id)
4622 auth_plugin = loadplugin(_plugin_id)
4622 return auth_plugin
4623 return auth_plugin
4623
4624
4624
4625
4625 class Integration(Base, BaseModel):
4626 class Integration(Base, BaseModel):
4626 __tablename__ = 'integrations'
4627 __tablename__ = 'integrations'
4627 __table_args__ = (
4628 __table_args__ = (
4628 base_table_args
4629 base_table_args
4629 )
4630 )
4630
4631
4631 integration_id = Column('integration_id', Integer(), primary_key=True)
4632 integration_id = Column('integration_id', Integer(), primary_key=True)
4632 integration_type = Column('integration_type', String(255))
4633 integration_type = Column('integration_type', String(255))
4633 enabled = Column('enabled', Boolean(), nullable=False)
4634 enabled = Column('enabled', Boolean(), nullable=False)
4634 name = Column('name', String(255), nullable=False)
4635 name = Column('name', String(255), nullable=False)
4635 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4636 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4636 default=False)
4637 default=False)
4637
4638
4638 settings = Column(
4639 settings = Column(
4639 'settings_json', MutationObj.as_mutable(
4640 'settings_json', MutationObj.as_mutable(
4640 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4641 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4641 repo_id = Column(
4642 repo_id = Column(
4642 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4643 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4643 nullable=True, unique=None, default=None)
4644 nullable=True, unique=None, default=None)
4644 repo = relationship('Repository', lazy='joined')
4645 repo = relationship('Repository', lazy='joined')
4645
4646
4646 repo_group_id = Column(
4647 repo_group_id = Column(
4647 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4648 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4648 nullable=True, unique=None, default=None)
4649 nullable=True, unique=None, default=None)
4649 repo_group = relationship('RepoGroup', lazy='joined')
4650 repo_group = relationship('RepoGroup', lazy='joined')
4650
4651
4651 @property
4652 @property
4652 def scope(self):
4653 def scope(self):
4653 if self.repo:
4654 if self.repo:
4654 return repr(self.repo)
4655 return repr(self.repo)
4655 if self.repo_group:
4656 if self.repo_group:
4656 if self.child_repos_only:
4657 if self.child_repos_only:
4657 return repr(self.repo_group) + ' (child repos only)'
4658 return repr(self.repo_group) + ' (child repos only)'
4658 else:
4659 else:
4659 return repr(self.repo_group) + ' (recursive)'
4660 return repr(self.repo_group) + ' (recursive)'
4660 if self.child_repos_only:
4661 if self.child_repos_only:
4661 return 'root_repos'
4662 return 'root_repos'
4662 return 'global'
4663 return 'global'
4663
4664
4664 def __repr__(self):
4665 def __repr__(self):
4665 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4666 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4666
4667
4667
4668
4668 class RepoReviewRuleUser(Base, BaseModel):
4669 class RepoReviewRuleUser(Base, BaseModel):
4669 __tablename__ = 'repo_review_rules_users'
4670 __tablename__ = 'repo_review_rules_users'
4670 __table_args__ = (
4671 __table_args__ = (
4671 base_table_args
4672 base_table_args
4672 )
4673 )
4673
4674
4674 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4675 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4675 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4676 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4676 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4677 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4677 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4678 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4678 user = relationship('User')
4679 user = relationship('User')
4679
4680
4680 def rule_data(self):
4681 def rule_data(self):
4681 return {
4682 return {
4682 'mandatory': self.mandatory
4683 'mandatory': self.mandatory
4683 }
4684 }
4684
4685
4685
4686
4686 class RepoReviewRuleUserGroup(Base, BaseModel):
4687 class RepoReviewRuleUserGroup(Base, BaseModel):
4687 __tablename__ = 'repo_review_rules_users_groups'
4688 __tablename__ = 'repo_review_rules_users_groups'
4688 __table_args__ = (
4689 __table_args__ = (
4689 base_table_args
4690 base_table_args
4690 )
4691 )
4691
4692
4692 VOTE_RULE_ALL = -1
4693 VOTE_RULE_ALL = -1
4693
4694
4694 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4695 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4695 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4696 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4696 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4697 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4697 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4698 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4698 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4699 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4699 users_group = relationship('UserGroup')
4700 users_group = relationship('UserGroup')
4700
4701
4701 def rule_data(self):
4702 def rule_data(self):
4702 return {
4703 return {
4703 'mandatory': self.mandatory,
4704 'mandatory': self.mandatory,
4704 'vote_rule': self.vote_rule
4705 'vote_rule': self.vote_rule
4705 }
4706 }
4706
4707
4707 @property
4708 @property
4708 def vote_rule_label(self):
4709 def vote_rule_label(self):
4709 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4710 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4710 return 'all must vote'
4711 return 'all must vote'
4711 else:
4712 else:
4712 return 'min. vote {}'.format(self.vote_rule)
4713 return 'min. vote {}'.format(self.vote_rule)
4713
4714
4714
4715
4715 class RepoReviewRule(Base, BaseModel):
4716 class RepoReviewRule(Base, BaseModel):
4716 __tablename__ = 'repo_review_rules'
4717 __tablename__ = 'repo_review_rules'
4717 __table_args__ = (
4718 __table_args__ = (
4718 base_table_args
4719 base_table_args
4719 )
4720 )
4720
4721
4721 repo_review_rule_id = Column(
4722 repo_review_rule_id = Column(
4722 'repo_review_rule_id', Integer(), primary_key=True)
4723 'repo_review_rule_id', Integer(), primary_key=True)
4723 repo_id = Column(
4724 repo_id = Column(
4724 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4725 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4725 repo = relationship('Repository', backref='review_rules')
4726 repo = relationship('Repository', backref='review_rules')
4726
4727
4727 review_rule_name = Column('review_rule_name', String(255))
4728 review_rule_name = Column('review_rule_name', String(255))
4728 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4729 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4729 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4730 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4730 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4731 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4731
4732
4732 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4733 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4733 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4734 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4734 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4735 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4735 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4736 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4736
4737
4737 rule_users = relationship('RepoReviewRuleUser')
4738 rule_users = relationship('RepoReviewRuleUser')
4738 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4739 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4739
4740
4740 def _validate_pattern(self, value):
4741 def _validate_pattern(self, value):
4741 re.compile('^' + glob2re(value) + '$')
4742 re.compile('^' + glob2re(value) + '$')
4742
4743
4743 @hybrid_property
4744 @hybrid_property
4744 def source_branch_pattern(self):
4745 def source_branch_pattern(self):
4745 return self._branch_pattern or '*'
4746 return self._branch_pattern or '*'
4746
4747
4747 @source_branch_pattern.setter
4748 @source_branch_pattern.setter
4748 def source_branch_pattern(self, value):
4749 def source_branch_pattern(self, value):
4749 self._validate_pattern(value)
4750 self._validate_pattern(value)
4750 self._branch_pattern = value or '*'
4751 self._branch_pattern = value or '*'
4751
4752
4752 @hybrid_property
4753 @hybrid_property
4753 def target_branch_pattern(self):
4754 def target_branch_pattern(self):
4754 return self._target_branch_pattern or '*'
4755 return self._target_branch_pattern or '*'
4755
4756
4756 @target_branch_pattern.setter
4757 @target_branch_pattern.setter
4757 def target_branch_pattern(self, value):
4758 def target_branch_pattern(self, value):
4758 self._validate_pattern(value)
4759 self._validate_pattern(value)
4759 self._target_branch_pattern = value or '*'
4760 self._target_branch_pattern = value or '*'
4760
4761
4761 @hybrid_property
4762 @hybrid_property
4762 def file_pattern(self):
4763 def file_pattern(self):
4763 return self._file_pattern or '*'
4764 return self._file_pattern or '*'
4764
4765
4765 @file_pattern.setter
4766 @file_pattern.setter
4766 def file_pattern(self, value):
4767 def file_pattern(self, value):
4767 self._validate_pattern(value)
4768 self._validate_pattern(value)
4768 self._file_pattern = value or '*'
4769 self._file_pattern = value or '*'
4769
4770
4770 def matches(self, source_branch, target_branch, files_changed):
4771 def matches(self, source_branch, target_branch, files_changed):
4771 """
4772 """
4772 Check if this review rule matches a branch/files in a pull request
4773 Check if this review rule matches a branch/files in a pull request
4773
4774
4774 :param source_branch: source branch name for the commit
4775 :param source_branch: source branch name for the commit
4775 :param target_branch: target branch name for the commit
4776 :param target_branch: target branch name for the commit
4776 :param files_changed: list of file paths changed in the pull request
4777 :param files_changed: list of file paths changed in the pull request
4777 """
4778 """
4778
4779
4779 source_branch = source_branch or ''
4780 source_branch = source_branch or ''
4780 target_branch = target_branch or ''
4781 target_branch = target_branch or ''
4781 files_changed = files_changed or []
4782 files_changed = files_changed or []
4782
4783
4783 branch_matches = True
4784 branch_matches = True
4784 if source_branch or target_branch:
4785 if source_branch or target_branch:
4785 if self.source_branch_pattern == '*':
4786 if self.source_branch_pattern == '*':
4786 source_branch_match = True
4787 source_branch_match = True
4787 else:
4788 else:
4788 if self.source_branch_pattern.startswith('re:'):
4789 if self.source_branch_pattern.startswith('re:'):
4789 source_pattern = self.source_branch_pattern[3:]
4790 source_pattern = self.source_branch_pattern[3:]
4790 else:
4791 else:
4791 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4792 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4792 source_branch_regex = re.compile(source_pattern)
4793 source_branch_regex = re.compile(source_pattern)
4793 source_branch_match = bool(source_branch_regex.search(source_branch))
4794 source_branch_match = bool(source_branch_regex.search(source_branch))
4794 if self.target_branch_pattern == '*':
4795 if self.target_branch_pattern == '*':
4795 target_branch_match = True
4796 target_branch_match = True
4796 else:
4797 else:
4797 if self.target_branch_pattern.startswith('re:'):
4798 if self.target_branch_pattern.startswith('re:'):
4798 target_pattern = self.target_branch_pattern[3:]
4799 target_pattern = self.target_branch_pattern[3:]
4799 else:
4800 else:
4800 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4801 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4801 target_branch_regex = re.compile(target_pattern)
4802 target_branch_regex = re.compile(target_pattern)
4802 target_branch_match = bool(target_branch_regex.search(target_branch))
4803 target_branch_match = bool(target_branch_regex.search(target_branch))
4803
4804
4804 branch_matches = source_branch_match and target_branch_match
4805 branch_matches = source_branch_match and target_branch_match
4805
4806
4806 files_matches = True
4807 files_matches = True
4807 if self.file_pattern != '*':
4808 if self.file_pattern != '*':
4808 files_matches = False
4809 files_matches = False
4809 if self.file_pattern.startswith('re:'):
4810 if self.file_pattern.startswith('re:'):
4810 file_pattern = self.file_pattern[3:]
4811 file_pattern = self.file_pattern[3:]
4811 else:
4812 else:
4812 file_pattern = glob2re(self.file_pattern)
4813 file_pattern = glob2re(self.file_pattern)
4813 file_regex = re.compile(file_pattern)
4814 file_regex = re.compile(file_pattern)
4814 for filename in files_changed:
4815 for filename in files_changed:
4815 if file_regex.search(filename):
4816 if file_regex.search(filename):
4816 files_matches = True
4817 files_matches = True
4817 break
4818 break
4818
4819
4819 return branch_matches and files_matches
4820 return branch_matches and files_matches
4820
4821
4821 @property
4822 @property
4822 def review_users(self):
4823 def review_users(self):
4823 """ Returns the users which this rule applies to """
4824 """ Returns the users which this rule applies to """
4824
4825
4825 users = collections.OrderedDict()
4826 users = collections.OrderedDict()
4826
4827
4827 for rule_user in self.rule_users:
4828 for rule_user in self.rule_users:
4828 if rule_user.user.active:
4829 if rule_user.user.active:
4829 if rule_user.user not in users:
4830 if rule_user.user not in users:
4830 users[rule_user.user.username] = {
4831 users[rule_user.user.username] = {
4831 'user': rule_user.user,
4832 'user': rule_user.user,
4832 'source': 'user',
4833 'source': 'user',
4833 'source_data': {},
4834 'source_data': {},
4834 'data': rule_user.rule_data()
4835 'data': rule_user.rule_data()
4835 }
4836 }
4836
4837
4837 for rule_user_group in self.rule_user_groups:
4838 for rule_user_group in self.rule_user_groups:
4838 source_data = {
4839 source_data = {
4839 'user_group_id': rule_user_group.users_group.users_group_id,
4840 'user_group_id': rule_user_group.users_group.users_group_id,
4840 'name': rule_user_group.users_group.users_group_name,
4841 'name': rule_user_group.users_group.users_group_name,
4841 'members': len(rule_user_group.users_group.members)
4842 'members': len(rule_user_group.users_group.members)
4842 }
4843 }
4843 for member in rule_user_group.users_group.members:
4844 for member in rule_user_group.users_group.members:
4844 if member.user.active:
4845 if member.user.active:
4845 key = member.user.username
4846 key = member.user.username
4846 if key in users:
4847 if key in users:
4847 # skip this member as we have him already
4848 # skip this member as we have him already
4848 # this prevents from override the "first" matched
4849 # this prevents from override the "first" matched
4849 # users with duplicates in multiple groups
4850 # users with duplicates in multiple groups
4850 continue
4851 continue
4851
4852
4852 users[key] = {
4853 users[key] = {
4853 'user': member.user,
4854 'user': member.user,
4854 'source': 'user_group',
4855 'source': 'user_group',
4855 'source_data': source_data,
4856 'source_data': source_data,
4856 'data': rule_user_group.rule_data()
4857 'data': rule_user_group.rule_data()
4857 }
4858 }
4858
4859
4859 return users
4860 return users
4860
4861
4861 def user_group_vote_rule(self, user_id):
4862 def user_group_vote_rule(self, user_id):
4862
4863
4863 rules = []
4864 rules = []
4864 if not self.rule_user_groups:
4865 if not self.rule_user_groups:
4865 return rules
4866 return rules
4866
4867
4867 for user_group in self.rule_user_groups:
4868 for user_group in self.rule_user_groups:
4868 user_group_members = [x.user_id for x in user_group.users_group.members]
4869 user_group_members = [x.user_id for x in user_group.users_group.members]
4869 if user_id in user_group_members:
4870 if user_id in user_group_members:
4870 rules.append(user_group)
4871 rules.append(user_group)
4871 return rules
4872 return rules
4872
4873
4873 def __repr__(self):
4874 def __repr__(self):
4874 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4875 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4875 self.repo_review_rule_id, self.repo)
4876 self.repo_review_rule_id, self.repo)
4876
4877
4877
4878
4878 class ScheduleEntry(Base, BaseModel):
4879 class ScheduleEntry(Base, BaseModel):
4879 __tablename__ = 'schedule_entries'
4880 __tablename__ = 'schedule_entries'
4880 __table_args__ = (
4881 __table_args__ = (
4881 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4882 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4882 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4883 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4883 base_table_args,
4884 base_table_args,
4884 )
4885 )
4885
4886
4886 schedule_types = ['crontab', 'timedelta', 'integer']
4887 schedule_types = ['crontab', 'timedelta', 'integer']
4887 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4888 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4888
4889
4889 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4890 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4890 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4891 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4891 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4892 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4892
4893
4893 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4894 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4894 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4895 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4895
4896
4896 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4897 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4897 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4898 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4898
4899
4899 # task
4900 # task
4900 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4901 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4901 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4902 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4902 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4903 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4903 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4904 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4904
4905
4905 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4906 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4906 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4907 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4907
4908
4908 @hybrid_property
4909 @hybrid_property
4909 def schedule_type(self):
4910 def schedule_type(self):
4910 return self._schedule_type
4911 return self._schedule_type
4911
4912
4912 @schedule_type.setter
4913 @schedule_type.setter
4913 def schedule_type(self, val):
4914 def schedule_type(self, val):
4914 if val not in self.schedule_types:
4915 if val not in self.schedule_types:
4915 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4916 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4916 val, self.schedule_type))
4917 val, self.schedule_type))
4917
4918
4918 self._schedule_type = val
4919 self._schedule_type = val
4919
4920
4920 @classmethod
4921 @classmethod
4921 def get_uid(cls, obj):
4922 def get_uid(cls, obj):
4922 args = obj.task_args
4923 args = obj.task_args
4923 kwargs = obj.task_kwargs
4924 kwargs = obj.task_kwargs
4924 if isinstance(args, JsonRaw):
4925 if isinstance(args, JsonRaw):
4925 try:
4926 try:
4926 args = json.loads(args)
4927 args = json.loads(args)
4927 except ValueError:
4928 except ValueError:
4928 args = tuple()
4929 args = tuple()
4929
4930
4930 if isinstance(kwargs, JsonRaw):
4931 if isinstance(kwargs, JsonRaw):
4931 try:
4932 try:
4932 kwargs = json.loads(kwargs)
4933 kwargs = json.loads(kwargs)
4933 except ValueError:
4934 except ValueError:
4934 kwargs = dict()
4935 kwargs = dict()
4935
4936
4936 dot_notation = obj.task_dot_notation
4937 dot_notation = obj.task_dot_notation
4937 val = '.'.join(map(safe_str, [
4938 val = '.'.join(map(safe_str, [
4938 sorted(dot_notation), args, sorted(kwargs.items())]))
4939 sorted(dot_notation), args, sorted(kwargs.items())]))
4939 return hashlib.sha1(val).hexdigest()
4940 return hashlib.sha1(val).hexdigest()
4940
4941
4941 @classmethod
4942 @classmethod
4942 def get_by_schedule_name(cls, schedule_name):
4943 def get_by_schedule_name(cls, schedule_name):
4943 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4944 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4944
4945
4945 @classmethod
4946 @classmethod
4946 def get_by_schedule_id(cls, schedule_id):
4947 def get_by_schedule_id(cls, schedule_id):
4947 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4948 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4948
4949
4949 @property
4950 @property
4950 def task(self):
4951 def task(self):
4951 return self.task_dot_notation
4952 return self.task_dot_notation
4952
4953
4953 @property
4954 @property
4954 def schedule(self):
4955 def schedule(self):
4955 from rhodecode.lib.celerylib.utils import raw_2_schedule
4956 from rhodecode.lib.celerylib.utils import raw_2_schedule
4956 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4957 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4957 return schedule
4958 return schedule
4958
4959
4959 @property
4960 @property
4960 def args(self):
4961 def args(self):
4961 try:
4962 try:
4962 return list(self.task_args or [])
4963 return list(self.task_args or [])
4963 except ValueError:
4964 except ValueError:
4964 return list()
4965 return list()
4965
4966
4966 @property
4967 @property
4967 def kwargs(self):
4968 def kwargs(self):
4968 try:
4969 try:
4969 return dict(self.task_kwargs or {})
4970 return dict(self.task_kwargs or {})
4970 except ValueError:
4971 except ValueError:
4971 return dict()
4972 return dict()
4972
4973
4973 def _as_raw(self, val):
4974 def _as_raw(self, val):
4974 if hasattr(val, 'de_coerce'):
4975 if hasattr(val, 'de_coerce'):
4975 val = val.de_coerce()
4976 val = val.de_coerce()
4976 if val:
4977 if val:
4977 val = json.dumps(val)
4978 val = json.dumps(val)
4978
4979
4979 return val
4980 return val
4980
4981
4981 @property
4982 @property
4982 def schedule_definition_raw(self):
4983 def schedule_definition_raw(self):
4983 return self._as_raw(self.schedule_definition)
4984 return self._as_raw(self.schedule_definition)
4984
4985
4985 @property
4986 @property
4986 def args_raw(self):
4987 def args_raw(self):
4987 return self._as_raw(self.task_args)
4988 return self._as_raw(self.task_args)
4988
4989
4989 @property
4990 @property
4990 def kwargs_raw(self):
4991 def kwargs_raw(self):
4991 return self._as_raw(self.task_kwargs)
4992 return self._as_raw(self.task_kwargs)
4992
4993
4993 def __repr__(self):
4994 def __repr__(self):
4994 return '<DB:ScheduleEntry({}:{})>'.format(
4995 return '<DB:ScheduleEntry({}:{})>'.format(
4995 self.schedule_entry_id, self.schedule_name)
4996 self.schedule_entry_id, self.schedule_name)
4996
4997
4997
4998
4998 @event.listens_for(ScheduleEntry, 'before_update')
4999 @event.listens_for(ScheduleEntry, 'before_update')
4999 def update_task_uid(mapper, connection, target):
5000 def update_task_uid(mapper, connection, target):
5000 target.task_uid = ScheduleEntry.get_uid(target)
5001 target.task_uid = ScheduleEntry.get_uid(target)
5001
5002
5002
5003
5003 @event.listens_for(ScheduleEntry, 'before_insert')
5004 @event.listens_for(ScheduleEntry, 'before_insert')
5004 def set_task_uid(mapper, connection, target):
5005 def set_task_uid(mapper, connection, target):
5005 target.task_uid = ScheduleEntry.get_uid(target)
5006 target.task_uid = ScheduleEntry.get_uid(target)
5006
5007
5007
5008
5008 class _BaseBranchPerms(BaseModel):
5009 class _BaseBranchPerms(BaseModel):
5009 @classmethod
5010 @classmethod
5010 def compute_hash(cls, value):
5011 def compute_hash(cls, value):
5011 return sha1_safe(value)
5012 return sha1_safe(value)
5012
5013
5013 @hybrid_property
5014 @hybrid_property
5014 def branch_pattern(self):
5015 def branch_pattern(self):
5015 return self._branch_pattern or '*'
5016 return self._branch_pattern or '*'
5016
5017
5017 @hybrid_property
5018 @hybrid_property
5018 def branch_hash(self):
5019 def branch_hash(self):
5019 return self._branch_hash
5020 return self._branch_hash
5020
5021
5021 def _validate_glob(self, value):
5022 def _validate_glob(self, value):
5022 re.compile('^' + glob2re(value) + '$')
5023 re.compile('^' + glob2re(value) + '$')
5023
5024
5024 @branch_pattern.setter
5025 @branch_pattern.setter
5025 def branch_pattern(self, value):
5026 def branch_pattern(self, value):
5026 self._validate_glob(value)
5027 self._validate_glob(value)
5027 self._branch_pattern = value or '*'
5028 self._branch_pattern = value or '*'
5028 # set the Hash when setting the branch pattern
5029 # set the Hash when setting the branch pattern
5029 self._branch_hash = self.compute_hash(self._branch_pattern)
5030 self._branch_hash = self.compute_hash(self._branch_pattern)
5030
5031
5031 def matches(self, branch):
5032 def matches(self, branch):
5032 """
5033 """
5033 Check if this the branch matches entry
5034 Check if this the branch matches entry
5034
5035
5035 :param branch: branch name for the commit
5036 :param branch: branch name for the commit
5036 """
5037 """
5037
5038
5038 branch = branch or ''
5039 branch = branch or ''
5039
5040
5040 branch_matches = True
5041 branch_matches = True
5041 if branch:
5042 if branch:
5042 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5043 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5043 branch_matches = bool(branch_regex.search(branch))
5044 branch_matches = bool(branch_regex.search(branch))
5044
5045
5045 return branch_matches
5046 return branch_matches
5046
5047
5047
5048
5048 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5049 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5049 __tablename__ = 'user_to_repo_branch_permissions'
5050 __tablename__ = 'user_to_repo_branch_permissions'
5050 __table_args__ = (
5051 __table_args__ = (
5051 base_table_args
5052 base_table_args
5052 )
5053 )
5053
5054
5054 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5055 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5055
5056
5056 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5057 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5057 repo = relationship('Repository', backref='user_branch_perms')
5058 repo = relationship('Repository', backref='user_branch_perms')
5058
5059
5059 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5060 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5060 permission = relationship('Permission')
5061 permission = relationship('Permission')
5061
5062
5062 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5063 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5063 user_repo_to_perm = relationship('UserRepoToPerm')
5064 user_repo_to_perm = relationship('UserRepoToPerm')
5064
5065
5065 rule_order = Column('rule_order', Integer(), nullable=False)
5066 rule_order = Column('rule_order', Integer(), nullable=False)
5066 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5067 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5067 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5068 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5068
5069
5069 def __unicode__(self):
5070 def __unicode__(self):
5070 return u'<UserBranchPermission(%s => %r)>' % (
5071 return u'<UserBranchPermission(%s => %r)>' % (
5071 self.user_repo_to_perm, self.branch_pattern)
5072 self.user_repo_to_perm, self.branch_pattern)
5072
5073
5073
5074
5074 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5075 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5075 __tablename__ = 'user_group_to_repo_branch_permissions'
5076 __tablename__ = 'user_group_to_repo_branch_permissions'
5076 __table_args__ = (
5077 __table_args__ = (
5077 base_table_args
5078 base_table_args
5078 )
5079 )
5079
5080
5080 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5081 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5081
5082
5082 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5083 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5083 repo = relationship('Repository', backref='user_group_branch_perms')
5084 repo = relationship('Repository', backref='user_group_branch_perms')
5084
5085
5085 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5086 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5086 permission = relationship('Permission')
5087 permission = relationship('Permission')
5087
5088
5088 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5089 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5089 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5090 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5090
5091
5091 rule_order = Column('rule_order', Integer(), nullable=False)
5092 rule_order = Column('rule_order', Integer(), nullable=False)
5092 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5093 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5093 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5094 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5094
5095
5095 def __unicode__(self):
5096 def __unicode__(self):
5096 return u'<UserBranchPermission(%s => %r)>' % (
5097 return u'<UserBranchPermission(%s => %r)>' % (
5097 self.user_group_repo_to_perm, self.branch_pattern)
5098 self.user_group_repo_to_perm, self.branch_pattern)
5098
5099
5099
5100
5100 class UserBookmark(Base, BaseModel):
5101 class UserBookmark(Base, BaseModel):
5101 __tablename__ = 'user_bookmarks'
5102 __tablename__ = 'user_bookmarks'
5102 __table_args__ = (
5103 __table_args__ = (
5103 UniqueConstraint('user_id', 'bookmark_repo_id'),
5104 UniqueConstraint('user_id', 'bookmark_repo_id'),
5104 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5105 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5105 UniqueConstraint('user_id', 'bookmark_position'),
5106 UniqueConstraint('user_id', 'bookmark_position'),
5106 base_table_args
5107 base_table_args
5107 )
5108 )
5108
5109
5109 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5110 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5110 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5111 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5111 position = Column("bookmark_position", Integer(), nullable=False)
5112 position = Column("bookmark_position", Integer(), nullable=False)
5112 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5113 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5113 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5114 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5114 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5115 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5115
5116
5116 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5117 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5117 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5118 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5118
5119
5119 user = relationship("User")
5120 user = relationship("User")
5120
5121
5121 repository = relationship("Repository")
5122 repository = relationship("Repository")
5122 repository_group = relationship("RepoGroup")
5123 repository_group = relationship("RepoGroup")
5123
5124
5124 @classmethod
5125 @classmethod
5125 def get_by_position_for_user(cls, position, user_id):
5126 def get_by_position_for_user(cls, position, user_id):
5126 return cls.query() \
5127 return cls.query() \
5127 .filter(UserBookmark.user_id == user_id) \
5128 .filter(UserBookmark.user_id == user_id) \
5128 .filter(UserBookmark.position == position).scalar()
5129 .filter(UserBookmark.position == position).scalar()
5129
5130
5130 @classmethod
5131 @classmethod
5131 def get_bookmarks_for_user(cls, user_id):
5132 def get_bookmarks_for_user(cls, user_id):
5132 return cls.query() \
5133 return cls.query() \
5133 .filter(UserBookmark.user_id == user_id) \
5134 .filter(UserBookmark.user_id == user_id) \
5134 .options(joinedload(UserBookmark.repository)) \
5135 .options(joinedload(UserBookmark.repository)) \
5135 .options(joinedload(UserBookmark.repository_group)) \
5136 .options(joinedload(UserBookmark.repository_group)) \
5136 .order_by(UserBookmark.position.asc()) \
5137 .order_by(UserBookmark.position.asc()) \
5137 .all()
5138 .all()
5138
5139
5139 def __unicode__(self):
5140 def __unicode__(self):
5140 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5141 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5141
5142
5142
5143
5143 class FileStore(Base, BaseModel):
5144 class FileStore(Base, BaseModel):
5144 __tablename__ = 'file_store'
5145 __tablename__ = 'file_store'
5145 __table_args__ = (
5146 __table_args__ = (
5146 base_table_args
5147 base_table_args
5147 )
5148 )
5148
5149
5149 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5150 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5150 file_uid = Column('file_uid', String(1024), nullable=False)
5151 file_uid = Column('file_uid', String(1024), nullable=False)
5151 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5152 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5152 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5153 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5153 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5154 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5154
5155
5155 # sha256 hash
5156 # sha256 hash
5156 file_hash = Column('file_hash', String(512), nullable=False)
5157 file_hash = Column('file_hash', String(512), nullable=False)
5157 file_size = Column('file_size', BigInteger(), nullable=False)
5158 file_size = Column('file_size', BigInteger(), nullable=False)
5158
5159
5159 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5160 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5160 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5161 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5161 accessed_count = Column('accessed_count', Integer(), default=0)
5162 accessed_count = Column('accessed_count', Integer(), default=0)
5162
5163
5163 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5164 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5164
5165
5165 # if repo/repo_group reference is set, check for permissions
5166 # if repo/repo_group reference is set, check for permissions
5166 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5167 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5167
5168
5168 # hidden defines an attachment that should be hidden from showing in artifact listing
5169 # hidden defines an attachment that should be hidden from showing in artifact listing
5169 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5170 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5170
5171
5171 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5172 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5172 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5173 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5173
5174
5174 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5175 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5175
5176
5176 # scope limited to user, which requester have access to
5177 # scope limited to user, which requester have access to
5177 scope_user_id = Column(
5178 scope_user_id = Column(
5178 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5179 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5179 nullable=True, unique=None, default=None)
5180 nullable=True, unique=None, default=None)
5180 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5181 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5181
5182
5182 # scope limited to user group, which requester have access to
5183 # scope limited to user group, which requester have access to
5183 scope_user_group_id = Column(
5184 scope_user_group_id = Column(
5184 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5185 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5185 nullable=True, unique=None, default=None)
5186 nullable=True, unique=None, default=None)
5186 user_group = relationship('UserGroup', lazy='joined')
5187 user_group = relationship('UserGroup', lazy='joined')
5187
5188
5188 # scope limited to repo, which requester have access to
5189 # scope limited to repo, which requester have access to
5189 scope_repo_id = Column(
5190 scope_repo_id = Column(
5190 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5191 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5191 nullable=True, unique=None, default=None)
5192 nullable=True, unique=None, default=None)
5192 repo = relationship('Repository', lazy='joined')
5193 repo = relationship('Repository', lazy='joined')
5193
5194
5194 # scope limited to repo group, which requester have access to
5195 # scope limited to repo group, which requester have access to
5195 scope_repo_group_id = Column(
5196 scope_repo_group_id = Column(
5196 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5197 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5197 nullable=True, unique=None, default=None)
5198 nullable=True, unique=None, default=None)
5198 repo_group = relationship('RepoGroup', lazy='joined')
5199 repo_group = relationship('RepoGroup', lazy='joined')
5199
5200
5200 @classmethod
5201 @classmethod
5201 def get_by_store_uid(cls, file_store_uid):
5202 def get_by_store_uid(cls, file_store_uid):
5202 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5203 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5203
5204
5204 @classmethod
5205 @classmethod
5205 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5206 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5206 file_description='', enabled=True, hidden=False, check_acl=True,
5207 file_description='', enabled=True, hidden=False, check_acl=True,
5207 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5208 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5208
5209
5209 store_entry = FileStore()
5210 store_entry = FileStore()
5210 store_entry.file_uid = file_uid
5211 store_entry.file_uid = file_uid
5211 store_entry.file_display_name = file_display_name
5212 store_entry.file_display_name = file_display_name
5212 store_entry.file_org_name = filename
5213 store_entry.file_org_name = filename
5213 store_entry.file_size = file_size
5214 store_entry.file_size = file_size
5214 store_entry.file_hash = file_hash
5215 store_entry.file_hash = file_hash
5215 store_entry.file_description = file_description
5216 store_entry.file_description = file_description
5216
5217
5217 store_entry.check_acl = check_acl
5218 store_entry.check_acl = check_acl
5218 store_entry.enabled = enabled
5219 store_entry.enabled = enabled
5219 store_entry.hidden = hidden
5220 store_entry.hidden = hidden
5220
5221
5221 store_entry.user_id = user_id
5222 store_entry.user_id = user_id
5222 store_entry.scope_user_id = scope_user_id
5223 store_entry.scope_user_id = scope_user_id
5223 store_entry.scope_repo_id = scope_repo_id
5224 store_entry.scope_repo_id = scope_repo_id
5224 store_entry.scope_repo_group_id = scope_repo_group_id
5225 store_entry.scope_repo_group_id = scope_repo_group_id
5225
5226
5226 return store_entry
5227 return store_entry
5227
5228
5228 @classmethod
5229 @classmethod
5229 def store_metadata(cls, file_store_id, args, commit=True):
5230 def store_metadata(cls, file_store_id, args, commit=True):
5230 file_store = FileStore.get(file_store_id)
5231 file_store = FileStore.get(file_store_id)
5231 if file_store is None:
5232 if file_store is None:
5232 return
5233 return
5233
5234
5234 for section, key, value, value_type in args:
5235 for section, key, value, value_type in args:
5235 has_key = FileStoreMetadata().query() \
5236 has_key = FileStoreMetadata().query() \
5236 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5237 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5237 .filter(FileStoreMetadata.file_store_meta_section == section) \
5238 .filter(FileStoreMetadata.file_store_meta_section == section) \
5238 .filter(FileStoreMetadata.file_store_meta_key == key) \
5239 .filter(FileStoreMetadata.file_store_meta_key == key) \
5239 .scalar()
5240 .scalar()
5240 if has_key:
5241 if has_key:
5241 msg = 'key `{}` already defined under section `{}` for this file.'\
5242 msg = 'key `{}` already defined under section `{}` for this file.'\
5242 .format(key, section)
5243 .format(key, section)
5243 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5244 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5244
5245
5245 # NOTE(marcink): raises ArtifactMetadataBadValueType
5246 # NOTE(marcink): raises ArtifactMetadataBadValueType
5246 FileStoreMetadata.valid_value_type(value_type)
5247 FileStoreMetadata.valid_value_type(value_type)
5247
5248
5248 meta_entry = FileStoreMetadata()
5249 meta_entry = FileStoreMetadata()
5249 meta_entry.file_store = file_store
5250 meta_entry.file_store = file_store
5250 meta_entry.file_store_meta_section = section
5251 meta_entry.file_store_meta_section = section
5251 meta_entry.file_store_meta_key = key
5252 meta_entry.file_store_meta_key = key
5252 meta_entry.file_store_meta_value_type = value_type
5253 meta_entry.file_store_meta_value_type = value_type
5253 meta_entry.file_store_meta_value = value
5254 meta_entry.file_store_meta_value = value
5254
5255
5255 Session().add(meta_entry)
5256 Session().add(meta_entry)
5256
5257
5257 try:
5258 try:
5258 if commit:
5259 if commit:
5259 Session().commit()
5260 Session().commit()
5260 except IntegrityError:
5261 except IntegrityError:
5261 Session().rollback()
5262 Session().rollback()
5262 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5263 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5263
5264
5264 @classmethod
5265 @classmethod
5265 def bump_access_counter(cls, file_uid, commit=True):
5266 def bump_access_counter(cls, file_uid, commit=True):
5266 FileStore().query()\
5267 FileStore().query()\
5267 .filter(FileStore.file_uid == file_uid)\
5268 .filter(FileStore.file_uid == file_uid)\
5268 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5269 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5269 FileStore.accessed_on: datetime.datetime.now()})
5270 FileStore.accessed_on: datetime.datetime.now()})
5270 if commit:
5271 if commit:
5271 Session().commit()
5272 Session().commit()
5272
5273
5273 def __json__(self):
5274 def __json__(self):
5274 data = {
5275 data = {
5275 'filename': self.file_display_name,
5276 'filename': self.file_display_name,
5276 'filename_org': self.file_org_name,
5277 'filename_org': self.file_org_name,
5277 'file_uid': self.file_uid,
5278 'file_uid': self.file_uid,
5278 'description': self.file_description,
5279 'description': self.file_description,
5279 'hidden': self.hidden,
5280 'hidden': self.hidden,
5280 'size': self.file_size,
5281 'size': self.file_size,
5281 'created_on': self.created_on,
5282 'created_on': self.created_on,
5282 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5283 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5283 'downloaded_times': self.accessed_count,
5284 'downloaded_times': self.accessed_count,
5284 'sha256': self.file_hash,
5285 'sha256': self.file_hash,
5285 'metadata': self.file_metadata,
5286 'metadata': self.file_metadata,
5286 }
5287 }
5287
5288
5288 return data
5289 return data
5289
5290
5290 def __repr__(self):
5291 def __repr__(self):
5291 return '<FileStore({})>'.format(self.file_store_id)
5292 return '<FileStore({})>'.format(self.file_store_id)
5292
5293
5293
5294
5294 class FileStoreMetadata(Base, BaseModel):
5295 class FileStoreMetadata(Base, BaseModel):
5295 __tablename__ = 'file_store_metadata'
5296 __tablename__ = 'file_store_metadata'
5296 __table_args__ = (
5297 __table_args__ = (
5297 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5298 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5298 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5299 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5299 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5300 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5300 base_table_args
5301 base_table_args
5301 )
5302 )
5302 SETTINGS_TYPES = {
5303 SETTINGS_TYPES = {
5303 'str': safe_str,
5304 'str': safe_str,
5304 'int': safe_int,
5305 'int': safe_int,
5305 'unicode': safe_unicode,
5306 'unicode': safe_unicode,
5306 'bool': str2bool,
5307 'bool': str2bool,
5307 'list': functools.partial(aslist, sep=',')
5308 'list': functools.partial(aslist, sep=',')
5308 }
5309 }
5309
5310
5310 file_store_meta_id = Column(
5311 file_store_meta_id = Column(
5311 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5312 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5312 primary_key=True)
5313 primary_key=True)
5313 _file_store_meta_section = Column(
5314 _file_store_meta_section = Column(
5314 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5315 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5315 nullable=True, unique=None, default=None)
5316 nullable=True, unique=None, default=None)
5316 _file_store_meta_section_hash = Column(
5317 _file_store_meta_section_hash = Column(
5317 "file_store_meta_section_hash", String(255),
5318 "file_store_meta_section_hash", String(255),
5318 nullable=True, unique=None, default=None)
5319 nullable=True, unique=None, default=None)
5319 _file_store_meta_key = Column(
5320 _file_store_meta_key = Column(
5320 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5321 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5321 nullable=True, unique=None, default=None)
5322 nullable=True, unique=None, default=None)
5322 _file_store_meta_key_hash = Column(
5323 _file_store_meta_key_hash = Column(
5323 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5324 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5324 _file_store_meta_value = Column(
5325 _file_store_meta_value = Column(
5325 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5326 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5326 nullable=True, unique=None, default=None)
5327 nullable=True, unique=None, default=None)
5327 _file_store_meta_value_type = Column(
5328 _file_store_meta_value_type = Column(
5328 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5329 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5329 default='unicode')
5330 default='unicode')
5330
5331
5331 file_store_id = Column(
5332 file_store_id = Column(
5332 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5333 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5333 nullable=True, unique=None, default=None)
5334 nullable=True, unique=None, default=None)
5334
5335
5335 file_store = relationship('FileStore', lazy='joined')
5336 file_store = relationship('FileStore', lazy='joined')
5336
5337
5337 @classmethod
5338 @classmethod
5338 def valid_value_type(cls, value):
5339 def valid_value_type(cls, value):
5339 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5340 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5340 raise ArtifactMetadataBadValueType(
5341 raise ArtifactMetadataBadValueType(
5341 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5342 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5342
5343
5343 @hybrid_property
5344 @hybrid_property
5344 def file_store_meta_section(self):
5345 def file_store_meta_section(self):
5345 return self._file_store_meta_section
5346 return self._file_store_meta_section
5346
5347
5347 @file_store_meta_section.setter
5348 @file_store_meta_section.setter
5348 def file_store_meta_section(self, value):
5349 def file_store_meta_section(self, value):
5349 self._file_store_meta_section = value
5350 self._file_store_meta_section = value
5350 self._file_store_meta_section_hash = _hash_key(value)
5351 self._file_store_meta_section_hash = _hash_key(value)
5351
5352
5352 @hybrid_property
5353 @hybrid_property
5353 def file_store_meta_key(self):
5354 def file_store_meta_key(self):
5354 return self._file_store_meta_key
5355 return self._file_store_meta_key
5355
5356
5356 @file_store_meta_key.setter
5357 @file_store_meta_key.setter
5357 def file_store_meta_key(self, value):
5358 def file_store_meta_key(self, value):
5358 self._file_store_meta_key = value
5359 self._file_store_meta_key = value
5359 self._file_store_meta_key_hash = _hash_key(value)
5360 self._file_store_meta_key_hash = _hash_key(value)
5360
5361
5361 @hybrid_property
5362 @hybrid_property
5362 def file_store_meta_value(self):
5363 def file_store_meta_value(self):
5363 val = self._file_store_meta_value
5364 val = self._file_store_meta_value
5364
5365
5365 if self._file_store_meta_value_type:
5366 if self._file_store_meta_value_type:
5366 # e.g unicode.encrypted == unicode
5367 # e.g unicode.encrypted == unicode
5367 _type = self._file_store_meta_value_type.split('.')[0]
5368 _type = self._file_store_meta_value_type.split('.')[0]
5368 # decode the encrypted value if it's encrypted field type
5369 # decode the encrypted value if it's encrypted field type
5369 if '.encrypted' in self._file_store_meta_value_type:
5370 if '.encrypted' in self._file_store_meta_value_type:
5370 cipher = EncryptedTextValue()
5371 cipher = EncryptedTextValue()
5371 val = safe_unicode(cipher.process_result_value(val, None))
5372 val = safe_unicode(cipher.process_result_value(val, None))
5372 # do final type conversion
5373 # do final type conversion
5373 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5374 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5374 val = converter(val)
5375 val = converter(val)
5375
5376
5376 return val
5377 return val
5377
5378
5378 @file_store_meta_value.setter
5379 @file_store_meta_value.setter
5379 def file_store_meta_value(self, val):
5380 def file_store_meta_value(self, val):
5380 val = safe_unicode(val)
5381 val = safe_unicode(val)
5381 # encode the encrypted value
5382 # encode the encrypted value
5382 if '.encrypted' in self.file_store_meta_value_type:
5383 if '.encrypted' in self.file_store_meta_value_type:
5383 cipher = EncryptedTextValue()
5384 cipher = EncryptedTextValue()
5384 val = safe_unicode(cipher.process_bind_param(val, None))
5385 val = safe_unicode(cipher.process_bind_param(val, None))
5385 self._file_store_meta_value = val
5386 self._file_store_meta_value = val
5386
5387
5387 @hybrid_property
5388 @hybrid_property
5388 def file_store_meta_value_type(self):
5389 def file_store_meta_value_type(self):
5389 return self._file_store_meta_value_type
5390 return self._file_store_meta_value_type
5390
5391
5391 @file_store_meta_value_type.setter
5392 @file_store_meta_value_type.setter
5392 def file_store_meta_value_type(self, val):
5393 def file_store_meta_value_type(self, val):
5393 # e.g unicode.encrypted
5394 # e.g unicode.encrypted
5394 self.valid_value_type(val)
5395 self.valid_value_type(val)
5395 self._file_store_meta_value_type = val
5396 self._file_store_meta_value_type = val
5396
5397
5397 def __json__(self):
5398 def __json__(self):
5398 data = {
5399 data = {
5399 'artifact': self.file_store.file_uid,
5400 'artifact': self.file_store.file_uid,
5400 'section': self.file_store_meta_section,
5401 'section': self.file_store_meta_section,
5401 'key': self.file_store_meta_key,
5402 'key': self.file_store_meta_key,
5402 'value': self.file_store_meta_value,
5403 'value': self.file_store_meta_value,
5403 }
5404 }
5404
5405
5405 return data
5406 return data
5406
5407
5407 def __repr__(self):
5408 def __repr__(self):
5408 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5409 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5409 self.file_store_meta_key, self.file_store_meta_value)
5410 self.file_store_meta_key, self.file_store_meta_value)
5410
5411
5411
5412
5412 class DbMigrateVersion(Base, BaseModel):
5413 class DbMigrateVersion(Base, BaseModel):
5413 __tablename__ = 'db_migrate_version'
5414 __tablename__ = 'db_migrate_version'
5414 __table_args__ = (
5415 __table_args__ = (
5415 base_table_args,
5416 base_table_args,
5416 )
5417 )
5417
5418
5418 repository_id = Column('repository_id', String(250), primary_key=True)
5419 repository_id = Column('repository_id', String(250), primary_key=True)
5419 repository_path = Column('repository_path', Text)
5420 repository_path = Column('repository_path', Text)
5420 version = Column('version', Integer)
5421 version = Column('version', Integer)
5421
5422
5422 @classmethod
5423 @classmethod
5423 def set_version(cls, version):
5424 def set_version(cls, version):
5424 """
5425 """
5425 Helper for forcing a different version, usually for debugging purposes via ishell.
5426 Helper for forcing a different version, usually for debugging purposes via ishell.
5426 """
5427 """
5427 ver = DbMigrateVersion.query().first()
5428 ver = DbMigrateVersion.query().first()
5428 ver.version = version
5429 ver.version = version
5429 Session().commit()
5430 Session().commit()
5430
5431
5431
5432
5432 class DbSession(Base, BaseModel):
5433 class DbSession(Base, BaseModel):
5433 __tablename__ = 'db_session'
5434 __tablename__ = 'db_session'
5434 __table_args__ = (
5435 __table_args__ = (
5435 base_table_args,
5436 base_table_args,
5436 )
5437 )
5437
5438
5438 def __repr__(self):
5439 def __repr__(self):
5439 return '<DB:DbSession({})>'.format(self.id)
5440 return '<DB:DbSession({})>'.format(self.id)
5440
5441
5441 id = Column('id', Integer())
5442 id = Column('id', Integer())
5442 namespace = Column('namespace', String(255), primary_key=True)
5443 namespace = Column('namespace', String(255), primary_key=True)
5443 accessed = Column('accessed', DateTime, nullable=False)
5444 accessed = Column('accessed', DateTime, nullable=False)
5444 created = Column('created', DateTime, nullable=False)
5445 created = Column('created', DateTime, nullable=False)
5445 data = Column('data', PickleType, nullable=False)
5446 data = Column('data', PickleType, nullable=False)
@@ -1,1003 +1,1005 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
41 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 query = query.limit(limit)
99 query = query.limit(limit)
100 users = query.all()
100 users = query.all()
101
101
102 _users = [
102 _users = [
103 self._serialize_user(user) for user in users
103 self._serialize_user(user) for user in users
104 ]
104 ]
105 return _users
105 return _users
106
106
107 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
108
108
109 if case_insensitive:
109 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
110 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
111 else:
112 user = self.sa.query(User)\
112 user = self.sa.query(User)\
113 .filter(User.username == username)
113 .filter(User.username == username)
114 if cache:
114 if cache:
115 name_key = _hash_key(username)
115 name_key = _hash_key(username)
116 user = user.options(
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
118 return user.scalar()
119
119
120 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
121 return User.get_by_email(email, case_insensitive, cache)
122
122
123 def get_by_auth_token(self, auth_token, cache=False):
123 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
124 return User.get_by_auth_token(auth_token, cache)
125
125
126 def get_active_user_count(self, cache=False):
126 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
127 qry = User.query().filter(
128 User.active == true()).filter(
128 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
129 User.username != User.DEFAULT_USER)
130 if cache:
130 if cache:
131 qry = qry.options(
131 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
132 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
133 return qry.count()
134
134
135 def create(self, form_data, cur_user=None):
135 def create(self, form_data, cur_user=None):
136 if not cur_user:
136 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
138
139 user_data = {
139 user_data = {
140 'username': form_data['username'],
140 'username': form_data['username'],
141 'password': form_data['password'],
141 'password': form_data['password'],
142 'email': form_data['email'],
142 'email': form_data['email'],
143 'firstname': form_data['firstname'],
143 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
144 'lastname': form_data['lastname'],
145 'active': form_data['active'],
145 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
146 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
147 'extern_name': form_data['extern_name'],
148 'admin': False,
148 'admin': False,
149 'cur_user': cur_user
149 'cur_user': cur_user
150 }
150 }
151
151
152 if 'create_repo_group' in form_data:
152 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
153 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
154 form_data.get('create_repo_group'))
155
155
156 try:
156 try:
157 if form_data.get('password_change'):
157 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
158 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
159 return UserModel().create_or_update(**user_data)
160 except Exception:
160 except Exception:
161 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
162 raise
162 raise
163
163
164 def update_user(self, user, skip_attrs=None, **kwargs):
164 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
165 from rhodecode.lib.auth import get_crypt_password
166
166
167 user = self._get_user(user)
167 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
168 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
169 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
170 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
171 "crucial for entire application" % {
172 'username': user.username})
172 'username': user.username})
173
173
174 # first store only defaults
174 # first store only defaults
175 user_attrs = {
175 user_attrs = {
176 'updating_user_id': user.user_id,
176 'updating_user_id': user.user_id,
177 'username': user.username,
177 'username': user.username,
178 'password': user.password,
178 'password': user.password,
179 'email': user.email,
179 'email': user.email,
180 'firstname': user.name,
180 'firstname': user.name,
181 'lastname': user.lastname,
181 'lastname': user.lastname,
182 'description': user.description,
182 'description': user.description,
183 'active': user.active,
183 'active': user.active,
184 'admin': user.admin,
184 'admin': user.admin,
185 'extern_name': user.extern_name,
185 'extern_name': user.extern_name,
186 'extern_type': user.extern_type,
186 'extern_type': user.extern_type,
187 'language': user.user_data.get('language')
187 'language': user.user_data.get('language')
188 }
188 }
189
189
190 # in case there's new_password, that comes from form, use it to
190 # in case there's new_password, that comes from form, use it to
191 # store password
191 # store password
192 if kwargs.get('new_password'):
192 if kwargs.get('new_password'):
193 kwargs['password'] = kwargs['new_password']
193 kwargs['password'] = kwargs['new_password']
194
194
195 # cleanups, my_account password change form
195 # cleanups, my_account password change form
196 kwargs.pop('current_password', None)
196 kwargs.pop('current_password', None)
197 kwargs.pop('new_password', None)
197 kwargs.pop('new_password', None)
198
198
199 # cleanups, user edit password change form
199 # cleanups, user edit password change form
200 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_confirmation', None)
201 kwargs.pop('password_change', None)
201 kwargs.pop('password_change', None)
202
202
203 # create repo group on user creation
203 # create repo group on user creation
204 kwargs.pop('create_repo_group', None)
204 kwargs.pop('create_repo_group', None)
205
205
206 # legacy forms send name, which is the firstname
206 # legacy forms send name, which is the firstname
207 firstname = kwargs.pop('name', None)
207 firstname = kwargs.pop('name', None)
208 if firstname:
208 if firstname:
209 kwargs['firstname'] = firstname
209 kwargs['firstname'] = firstname
210
210
211 for k, v in kwargs.items():
211 for k, v in kwargs.items():
212 # skip if we don't want to update this
212 # skip if we don't want to update this
213 if skip_attrs and k in skip_attrs:
213 if skip_attrs and k in skip_attrs:
214 continue
214 continue
215
215
216 user_attrs[k] = v
216 user_attrs[k] = v
217
217
218 try:
218 try:
219 return self.create_or_update(**user_attrs)
219 return self.create_or_update(**user_attrs)
220 except Exception:
220 except Exception:
221 log.error(traceback.format_exc())
221 log.error(traceback.format_exc())
222 raise
222 raise
223
223
224 def create_or_update(
224 def create_or_update(
225 self, username, password, email, firstname='', lastname='',
225 self, username, password, email, firstname='', lastname='',
226 active=True, admin=False, extern_type=None, extern_name=None,
226 active=True, admin=False, extern_type=None, extern_name=None,
227 cur_user=None, plugin=None, force_password_change=False,
227 cur_user=None, plugin=None, force_password_change=False,
228 allow_to_create_user=True, create_repo_group=None,
228 allow_to_create_user=True, create_repo_group=None,
229 updating_user_id=None, language=None, description=None,
229 updating_user_id=None, language=None, description='',
230 strict_creation_check=True):
230 strict_creation_check=True):
231 """
231 """
232 Creates a new instance if not found, or updates current one
232 Creates a new instance if not found, or updates current one
233
233
234 :param username:
234 :param username:
235 :param password:
235 :param password:
236 :param email:
236 :param email:
237 :param firstname:
237 :param firstname:
238 :param lastname:
238 :param lastname:
239 :param active:
239 :param active:
240 :param admin:
240 :param admin:
241 :param extern_type:
241 :param extern_type:
242 :param extern_name:
242 :param extern_name:
243 :param cur_user:
243 :param cur_user:
244 :param plugin: optional plugin this method was called from
244 :param plugin: optional plugin this method was called from
245 :param force_password_change: toggles new or existing user flag
245 :param force_password_change: toggles new or existing user flag
246 for password change
246 for password change
247 :param allow_to_create_user: Defines if the method can actually create
247 :param allow_to_create_user: Defines if the method can actually create
248 new users
248 new users
249 :param create_repo_group: Defines if the method should also
249 :param create_repo_group: Defines if the method should also
250 create an repo group with user name, and owner
250 create an repo group with user name, and owner
251 :param updating_user_id: if we set it up this is the user we want to
251 :param updating_user_id: if we set it up this is the user we want to
252 update this allows to editing username.
252 update this allows to editing username.
253 :param language: language of user from interface.
253 :param language: language of user from interface.
254 :param description: user description
255 :param strict_creation_check: checks for allowed creation license wise etc.
254
256
255 :returns: new User object with injected `is_new_user` attribute.
257 :returns: new User object with injected `is_new_user` attribute.
256 """
258 """
257
259
258 if not cur_user:
260 if not cur_user:
259 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
261 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
260
262
261 from rhodecode.lib.auth import (
263 from rhodecode.lib.auth import (
262 get_crypt_password, check_password, generate_auth_token)
264 get_crypt_password, check_password, generate_auth_token)
263 from rhodecode.lib.hooks_base import (
265 from rhodecode.lib.hooks_base import (
264 log_create_user, check_allowed_create_user)
266 log_create_user, check_allowed_create_user)
265
267
266 def _password_change(new_user, password):
268 def _password_change(new_user, password):
267 old_password = new_user.password or ''
269 old_password = new_user.password or ''
268 # empty password
270 # empty password
269 if not old_password:
271 if not old_password:
270 return False
272 return False
271
273
272 # password check is only needed for RhodeCode internal auth calls
274 # password check is only needed for RhodeCode internal auth calls
273 # in case it's a plugin we don't care
275 # in case it's a plugin we don't care
274 if not plugin:
276 if not plugin:
275
277
276 # first check if we gave crypted password back, and if it
278 # first check if we gave crypted password back, and if it
277 # matches it's not password change
279 # matches it's not password change
278 if new_user.password == password:
280 if new_user.password == password:
279 return False
281 return False
280
282
281 password_match = check_password(password, old_password)
283 password_match = check_password(password, old_password)
282 if not password_match:
284 if not password_match:
283 return True
285 return True
284
286
285 return False
287 return False
286
288
287 # read settings on default personal repo group creation
289 # read settings on default personal repo group creation
288 if create_repo_group is None:
290 if create_repo_group is None:
289 default_create_repo_group = RepoGroupModel()\
291 default_create_repo_group = RepoGroupModel()\
290 .get_default_create_personal_repo_group()
292 .get_default_create_personal_repo_group()
291 create_repo_group = default_create_repo_group
293 create_repo_group = default_create_repo_group
292
294
293 user_data = {
295 user_data = {
294 'username': username,
296 'username': username,
295 'password': password,
297 'password': password,
296 'email': email,
298 'email': email,
297 'firstname': firstname,
299 'firstname': firstname,
298 'lastname': lastname,
300 'lastname': lastname,
299 'active': active,
301 'active': active,
300 'admin': admin
302 'admin': admin
301 }
303 }
302
304
303 if updating_user_id:
305 if updating_user_id:
304 log.debug('Checking for existing account in RhodeCode '
306 log.debug('Checking for existing account in RhodeCode '
305 'database with user_id `%s` ', updating_user_id)
307 'database with user_id `%s` ', updating_user_id)
306 user = User.get(updating_user_id)
308 user = User.get(updating_user_id)
307 else:
309 else:
308 log.debug('Checking for existing account in RhodeCode '
310 log.debug('Checking for existing account in RhodeCode '
309 'database with username `%s` ', username)
311 'database with username `%s` ', username)
310 user = User.get_by_username(username, case_insensitive=True)
312 user = User.get_by_username(username, case_insensitive=True)
311
313
312 if user is None:
314 if user is None:
313 # we check internal flag if this method is actually allowed to
315 # we check internal flag if this method is actually allowed to
314 # create new user
316 # create new user
315 if not allow_to_create_user:
317 if not allow_to_create_user:
316 msg = ('Method wants to create new user, but it is not '
318 msg = ('Method wants to create new user, but it is not '
317 'allowed to do so')
319 'allowed to do so')
318 log.warning(msg)
320 log.warning(msg)
319 raise NotAllowedToCreateUserError(msg)
321 raise NotAllowedToCreateUserError(msg)
320
322
321 log.debug('Creating new user %s', username)
323 log.debug('Creating new user %s', username)
322
324
323 # only if we create user that is active
325 # only if we create user that is active
324 new_active_user = active
326 new_active_user = active
325 if new_active_user and strict_creation_check:
327 if new_active_user and strict_creation_check:
326 # raises UserCreationError if it's not allowed for any reason to
328 # raises UserCreationError if it's not allowed for any reason to
327 # create new active user, this also executes pre-create hooks
329 # create new active user, this also executes pre-create hooks
328 check_allowed_create_user(user_data, cur_user, strict_check=True)
330 check_allowed_create_user(user_data, cur_user, strict_check=True)
329 events.trigger(events.UserPreCreate(user_data))
331 events.trigger(events.UserPreCreate(user_data))
330 new_user = User()
332 new_user = User()
331 edit = False
333 edit = False
332 else:
334 else:
333 log.debug('updating user `%s`', username)
335 log.debug('updating user `%s`', username)
334 events.trigger(events.UserPreUpdate(user, user_data))
336 events.trigger(events.UserPreUpdate(user, user_data))
335 new_user = user
337 new_user = user
336 edit = True
338 edit = True
337
339
338 # we're not allowed to edit default user
340 # we're not allowed to edit default user
339 if user.username == User.DEFAULT_USER:
341 if user.username == User.DEFAULT_USER:
340 raise DefaultUserException(
342 raise DefaultUserException(
341 "You can't edit this user (`%(username)s`) since it's "
343 "You can't edit this user (`%(username)s`) since it's "
342 "crucial for entire application"
344 "crucial for entire application"
343 % {'username': user.username})
345 % {'username': user.username})
344
346
345 # inject special attribute that will tell us if User is new or old
347 # inject special attribute that will tell us if User is new or old
346 new_user.is_new_user = not edit
348 new_user.is_new_user = not edit
347 # for users that didn's specify auth type, we use RhodeCode built in
349 # for users that didn's specify auth type, we use RhodeCode built in
348 from rhodecode.authentication.plugins import auth_rhodecode
350 from rhodecode.authentication.plugins import auth_rhodecode
349 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
351 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
350 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
352 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
351
353
352 try:
354 try:
353 new_user.username = username
355 new_user.username = username
354 new_user.admin = admin
356 new_user.admin = admin
355 new_user.email = email
357 new_user.email = email
356 new_user.active = active
358 new_user.active = active
357 new_user.extern_name = safe_unicode(extern_name)
359 new_user.extern_name = safe_unicode(extern_name)
358 new_user.extern_type = safe_unicode(extern_type)
360 new_user.extern_type = safe_unicode(extern_type)
359 new_user.name = firstname
361 new_user.name = firstname
360 new_user.lastname = lastname
362 new_user.lastname = lastname
361 new_user.description = description
363 new_user.description = description
362
364
363 # set password only if creating an user or password is changed
365 # set password only if creating an user or password is changed
364 if not edit or _password_change(new_user, password):
366 if not edit or _password_change(new_user, password):
365 reason = 'new password' if edit else 'new user'
367 reason = 'new password' if edit else 'new user'
366 log.debug('Updating password reason=>%s', reason)
368 log.debug('Updating password reason=>%s', reason)
367 new_user.password = get_crypt_password(password) if password else None
369 new_user.password = get_crypt_password(password) if password else None
368
370
369 if force_password_change:
371 if force_password_change:
370 new_user.update_userdata(force_password_change=True)
372 new_user.update_userdata(force_password_change=True)
371 if language:
373 if language:
372 new_user.update_userdata(language=language)
374 new_user.update_userdata(language=language)
373 new_user.update_userdata(notification_status=True)
375 new_user.update_userdata(notification_status=True)
374
376
375 self.sa.add(new_user)
377 self.sa.add(new_user)
376
378
377 if not edit and create_repo_group:
379 if not edit and create_repo_group:
378 RepoGroupModel().create_personal_repo_group(
380 RepoGroupModel().create_personal_repo_group(
379 new_user, commit_early=False)
381 new_user, commit_early=False)
380
382
381 if not edit:
383 if not edit:
382 # add the RSS token
384 # add the RSS token
383 self.add_auth_token(
385 self.add_auth_token(
384 user=username, lifetime_minutes=-1,
386 user=username, lifetime_minutes=-1,
385 role=self.auth_token_role.ROLE_FEED,
387 role=self.auth_token_role.ROLE_FEED,
386 description=u'Generated feed token')
388 description=u'Generated feed token')
387
389
388 kwargs = new_user.get_dict()
390 kwargs = new_user.get_dict()
389 # backward compat, require api_keys present
391 # backward compat, require api_keys present
390 kwargs['api_keys'] = kwargs['auth_tokens']
392 kwargs['api_keys'] = kwargs['auth_tokens']
391 log_create_user(created_by=cur_user, **kwargs)
393 log_create_user(created_by=cur_user, **kwargs)
392 events.trigger(events.UserPostCreate(user_data))
394 events.trigger(events.UserPostCreate(user_data))
393 return new_user
395 return new_user
394 except (DatabaseError,):
396 except (DatabaseError,):
395 log.error(traceback.format_exc())
397 log.error(traceback.format_exc())
396 raise
398 raise
397
399
398 def create_registration(self, form_data,
400 def create_registration(self, form_data,
399 extern_name='rhodecode', extern_type='rhodecode'):
401 extern_name='rhodecode', extern_type='rhodecode'):
400 from rhodecode.model.notification import NotificationModel
402 from rhodecode.model.notification import NotificationModel
401 from rhodecode.model.notification import EmailNotificationModel
403 from rhodecode.model.notification import EmailNotificationModel
402
404
403 try:
405 try:
404 form_data['admin'] = False
406 form_data['admin'] = False
405 form_data['extern_name'] = extern_name
407 form_data['extern_name'] = extern_name
406 form_data['extern_type'] = extern_type
408 form_data['extern_type'] = extern_type
407 new_user = self.create(form_data)
409 new_user = self.create(form_data)
408
410
409 self.sa.add(new_user)
411 self.sa.add(new_user)
410 self.sa.flush()
412 self.sa.flush()
411
413
412 user_data = new_user.get_dict()
414 user_data = new_user.get_dict()
413 kwargs = {
415 kwargs = {
414 # use SQLALCHEMY safe dump of user data
416 # use SQLALCHEMY safe dump of user data
415 'user': AttributeDict(user_data),
417 'user': AttributeDict(user_data),
416 'date': datetime.datetime.now()
418 'date': datetime.datetime.now()
417 }
419 }
418 notification_type = EmailNotificationModel.TYPE_REGISTRATION
420 notification_type = EmailNotificationModel.TYPE_REGISTRATION
419 # pre-generate the subject for notification itself
421 # pre-generate the subject for notification itself
420 (subject,
422 (subject,
421 _h, _e, # we don't care about those
423 _h, _e, # we don't care about those
422 body_plaintext) = EmailNotificationModel().render_email(
424 body_plaintext) = EmailNotificationModel().render_email(
423 notification_type, **kwargs)
425 notification_type, **kwargs)
424
426
425 # create notification objects, and emails
427 # create notification objects, and emails
426 NotificationModel().create(
428 NotificationModel().create(
427 created_by=new_user,
429 created_by=new_user,
428 notification_subject=subject,
430 notification_subject=subject,
429 notification_body=body_plaintext,
431 notification_body=body_plaintext,
430 notification_type=notification_type,
432 notification_type=notification_type,
431 recipients=None, # all admins
433 recipients=None, # all admins
432 email_kwargs=kwargs,
434 email_kwargs=kwargs,
433 )
435 )
434
436
435 return new_user
437 return new_user
436 except Exception:
438 except Exception:
437 log.error(traceback.format_exc())
439 log.error(traceback.format_exc())
438 raise
440 raise
439
441
440 def _handle_user_repos(self, username, repositories, handle_mode=None):
442 def _handle_user_repos(self, username, repositories, handle_mode=None):
441 _superadmin = self.cls.get_first_super_admin()
443 _superadmin = self.cls.get_first_super_admin()
442 left_overs = True
444 left_overs = True
443
445
444 from rhodecode.model.repo import RepoModel
446 from rhodecode.model.repo import RepoModel
445
447
446 if handle_mode == 'detach':
448 if handle_mode == 'detach':
447 for obj in repositories:
449 for obj in repositories:
448 obj.user = _superadmin
450 obj.user = _superadmin
449 # set description we know why we super admin now owns
451 # set description we know why we super admin now owns
450 # additional repositories that were orphaned !
452 # additional repositories that were orphaned !
451 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
453 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
452 self.sa.add(obj)
454 self.sa.add(obj)
453 left_overs = False
455 left_overs = False
454 elif handle_mode == 'delete':
456 elif handle_mode == 'delete':
455 for obj in repositories:
457 for obj in repositories:
456 RepoModel().delete(obj, forks='detach')
458 RepoModel().delete(obj, forks='detach')
457 left_overs = False
459 left_overs = False
458
460
459 # if nothing is done we have left overs left
461 # if nothing is done we have left overs left
460 return left_overs
462 return left_overs
461
463
462 def _handle_user_repo_groups(self, username, repository_groups,
464 def _handle_user_repo_groups(self, username, repository_groups,
463 handle_mode=None):
465 handle_mode=None):
464 _superadmin = self.cls.get_first_super_admin()
466 _superadmin = self.cls.get_first_super_admin()
465 left_overs = True
467 left_overs = True
466
468
467 from rhodecode.model.repo_group import RepoGroupModel
469 from rhodecode.model.repo_group import RepoGroupModel
468
470
469 if handle_mode == 'detach':
471 if handle_mode == 'detach':
470 for r in repository_groups:
472 for r in repository_groups:
471 r.user = _superadmin
473 r.user = _superadmin
472 # set description we know why we super admin now owns
474 # set description we know why we super admin now owns
473 # additional repositories that were orphaned !
475 # additional repositories that were orphaned !
474 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
476 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
475 r.personal = False
477 r.personal = False
476 self.sa.add(r)
478 self.sa.add(r)
477 left_overs = False
479 left_overs = False
478 elif handle_mode == 'delete':
480 elif handle_mode == 'delete':
479 for r in repository_groups:
481 for r in repository_groups:
480 RepoGroupModel().delete(r)
482 RepoGroupModel().delete(r)
481 left_overs = False
483 left_overs = False
482
484
483 # if nothing is done we have left overs left
485 # if nothing is done we have left overs left
484 return left_overs
486 return left_overs
485
487
486 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
488 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
487 _superadmin = self.cls.get_first_super_admin()
489 _superadmin = self.cls.get_first_super_admin()
488 left_overs = True
490 left_overs = True
489
491
490 from rhodecode.model.user_group import UserGroupModel
492 from rhodecode.model.user_group import UserGroupModel
491
493
492 if handle_mode == 'detach':
494 if handle_mode == 'detach':
493 for r in user_groups:
495 for r in user_groups:
494 for user_user_group_to_perm in r.user_user_group_to_perm:
496 for user_user_group_to_perm in r.user_user_group_to_perm:
495 if user_user_group_to_perm.user.username == username:
497 if user_user_group_to_perm.user.username == username:
496 user_user_group_to_perm.user = _superadmin
498 user_user_group_to_perm.user = _superadmin
497 r.user = _superadmin
499 r.user = _superadmin
498 # set description we know why we super admin now owns
500 # set description we know why we super admin now owns
499 # additional repositories that were orphaned !
501 # additional repositories that were orphaned !
500 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
502 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
501 self.sa.add(r)
503 self.sa.add(r)
502 left_overs = False
504 left_overs = False
503 elif handle_mode == 'delete':
505 elif handle_mode == 'delete':
504 for r in user_groups:
506 for r in user_groups:
505 UserGroupModel().delete(r)
507 UserGroupModel().delete(r)
506 left_overs = False
508 left_overs = False
507
509
508 # if nothing is done we have left overs left
510 # if nothing is done we have left overs left
509 return left_overs
511 return left_overs
510
512
511 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
513 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
512 _superadmin = self.cls.get_first_super_admin()
514 _superadmin = self.cls.get_first_super_admin()
513 left_overs = True
515 left_overs = True
514
516
515 if handle_mode == 'detach':
517 if handle_mode == 'detach':
516 for a in artifacts:
518 for a in artifacts:
517 a.upload_user = _superadmin
519 a.upload_user = _superadmin
518 # set description we know why we super admin now owns
520 # set description we know why we super admin now owns
519 # additional artifacts that were orphaned !
521 # additional artifacts that were orphaned !
520 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
522 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
521 self.sa.add(a)
523 self.sa.add(a)
522 left_overs = False
524 left_overs = False
523 elif handle_mode == 'delete':
525 elif handle_mode == 'delete':
524 from rhodecode.apps.file_store import utils as store_utils
526 from rhodecode.apps.file_store import utils as store_utils
525 storage = store_utils.get_file_storage(self.request.registry.settings)
527 storage = store_utils.get_file_storage(self.request.registry.settings)
526 for a in artifacts:
528 for a in artifacts:
527 file_uid = a.file_uid
529 file_uid = a.file_uid
528 storage.delete(file_uid)
530 storage.delete(file_uid)
529 self.sa.delete(a)
531 self.sa.delete(a)
530
532
531 left_overs = False
533 left_overs = False
532
534
533 # if nothing is done we have left overs left
535 # if nothing is done we have left overs left
534 return left_overs
536 return left_overs
535
537
536 def delete(self, user, cur_user=None, handle_repos=None,
538 def delete(self, user, cur_user=None, handle_repos=None,
537 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
539 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
538 from rhodecode.lib.hooks_base import log_delete_user
540 from rhodecode.lib.hooks_base import log_delete_user
539
541
540 if not cur_user:
542 if not cur_user:
541 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
543 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
542 user = self._get_user(user)
544 user = self._get_user(user)
543
545
544 try:
546 try:
545 if user.username == User.DEFAULT_USER:
547 if user.username == User.DEFAULT_USER:
546 raise DefaultUserException(
548 raise DefaultUserException(
547 u"You can't remove this user since it's"
549 u"You can't remove this user since it's"
548 u" crucial for entire application")
550 u" crucial for entire application")
549
551
550 left_overs = self._handle_user_repos(
552 left_overs = self._handle_user_repos(
551 user.username, user.repositories, handle_repos)
553 user.username, user.repositories, handle_repos)
552 if left_overs and user.repositories:
554 if left_overs and user.repositories:
553 repos = [x.repo_name for x in user.repositories]
555 repos = [x.repo_name for x in user.repositories]
554 raise UserOwnsReposException(
556 raise UserOwnsReposException(
555 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
557 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
556 u'removed. Switch owners or remove those repositories:%(list_repos)s'
558 u'removed. Switch owners or remove those repositories:%(list_repos)s'
557 % {'username': user.username, 'len_repos': len(repos),
559 % {'username': user.username, 'len_repos': len(repos),
558 'list_repos': ', '.join(repos)})
560 'list_repos': ', '.join(repos)})
559
561
560 left_overs = self._handle_user_repo_groups(
562 left_overs = self._handle_user_repo_groups(
561 user.username, user.repository_groups, handle_repo_groups)
563 user.username, user.repository_groups, handle_repo_groups)
562 if left_overs and user.repository_groups:
564 if left_overs and user.repository_groups:
563 repo_groups = [x.group_name for x in user.repository_groups]
565 repo_groups = [x.group_name for x in user.repository_groups]
564 raise UserOwnsRepoGroupsException(
566 raise UserOwnsRepoGroupsException(
565 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
567 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
566 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
568 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
567 % {'username': user.username, 'len_repo_groups': len(repo_groups),
569 % {'username': user.username, 'len_repo_groups': len(repo_groups),
568 'list_repo_groups': ', '.join(repo_groups)})
570 'list_repo_groups': ', '.join(repo_groups)})
569
571
570 left_overs = self._handle_user_user_groups(
572 left_overs = self._handle_user_user_groups(
571 user.username, user.user_groups, handle_user_groups)
573 user.username, user.user_groups, handle_user_groups)
572 if left_overs and user.user_groups:
574 if left_overs and user.user_groups:
573 user_groups = [x.users_group_name for x in user.user_groups]
575 user_groups = [x.users_group_name for x in user.user_groups]
574 raise UserOwnsUserGroupsException(
576 raise UserOwnsUserGroupsException(
575 u'user "%s" still owns %s user groups and cannot be '
577 u'user "%s" still owns %s user groups and cannot be '
576 u'removed. Switch owners or remove those user groups:%s'
578 u'removed. Switch owners or remove those user groups:%s'
577 % (user.username, len(user_groups), ', '.join(user_groups)))
579 % (user.username, len(user_groups), ', '.join(user_groups)))
578
580
579 left_overs = self._handle_user_artifacts(
581 left_overs = self._handle_user_artifacts(
580 user.username, user.artifacts, handle_artifacts)
582 user.username, user.artifacts, handle_artifacts)
581 if left_overs and user.artifacts:
583 if left_overs and user.artifacts:
582 artifacts = [x.file_uid for x in user.artifacts]
584 artifacts = [x.file_uid for x in user.artifacts]
583 raise UserOwnsArtifactsException(
585 raise UserOwnsArtifactsException(
584 u'user "%s" still owns %s artifacts and cannot be '
586 u'user "%s" still owns %s artifacts and cannot be '
585 u'removed. Switch owners or remove those artifacts:%s'
587 u'removed. Switch owners or remove those artifacts:%s'
586 % (user.username, len(artifacts), ', '.join(artifacts)))
588 % (user.username, len(artifacts), ', '.join(artifacts)))
587
589
588 user_data = user.get_dict() # fetch user data before expire
590 user_data = user.get_dict() # fetch user data before expire
589
591
590 # we might change the user data with detach/delete, make sure
592 # we might change the user data with detach/delete, make sure
591 # the object is marked as expired before actually deleting !
593 # the object is marked as expired before actually deleting !
592 self.sa.expire(user)
594 self.sa.expire(user)
593 self.sa.delete(user)
595 self.sa.delete(user)
594
596
595 log_delete_user(deleted_by=cur_user, **user_data)
597 log_delete_user(deleted_by=cur_user, **user_data)
596 except Exception:
598 except Exception:
597 log.error(traceback.format_exc())
599 log.error(traceback.format_exc())
598 raise
600 raise
599
601
600 def reset_password_link(self, data, pwd_reset_url):
602 def reset_password_link(self, data, pwd_reset_url):
601 from rhodecode.lib.celerylib import tasks, run_task
603 from rhodecode.lib.celerylib import tasks, run_task
602 from rhodecode.model.notification import EmailNotificationModel
604 from rhodecode.model.notification import EmailNotificationModel
603 user_email = data['email']
605 user_email = data['email']
604 try:
606 try:
605 user = User.get_by_email(user_email)
607 user = User.get_by_email(user_email)
606 if user:
608 if user:
607 log.debug('password reset user found %s', user)
609 log.debug('password reset user found %s', user)
608
610
609 email_kwargs = {
611 email_kwargs = {
610 'password_reset_url': pwd_reset_url,
612 'password_reset_url': pwd_reset_url,
611 'user': user,
613 'user': user,
612 'email': user_email,
614 'email': user_email,
613 'date': datetime.datetime.now()
615 'date': datetime.datetime.now()
614 }
616 }
615
617
616 (subject, headers, email_body,
618 (subject, headers, email_body,
617 email_body_plaintext) = EmailNotificationModel().render_email(
619 email_body_plaintext) = EmailNotificationModel().render_email(
618 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
620 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
619
621
620 recipients = [user_email]
622 recipients = [user_email]
621
623
622 action_logger_generic(
624 action_logger_generic(
623 'sending password reset email to user: {}'.format(
625 'sending password reset email to user: {}'.format(
624 user), namespace='security.password_reset')
626 user), namespace='security.password_reset')
625
627
626 run_task(tasks.send_email, recipients, subject,
628 run_task(tasks.send_email, recipients, subject,
627 email_body_plaintext, email_body)
629 email_body_plaintext, email_body)
628
630
629 else:
631 else:
630 log.debug("password reset email %s not found", user_email)
632 log.debug("password reset email %s not found", user_email)
631 except Exception:
633 except Exception:
632 log.error(traceback.format_exc())
634 log.error(traceback.format_exc())
633 return False
635 return False
634
636
635 return True
637 return True
636
638
637 def reset_password(self, data):
639 def reset_password(self, data):
638 from rhodecode.lib.celerylib import tasks, run_task
640 from rhodecode.lib.celerylib import tasks, run_task
639 from rhodecode.model.notification import EmailNotificationModel
641 from rhodecode.model.notification import EmailNotificationModel
640 from rhodecode.lib import auth
642 from rhodecode.lib import auth
641 user_email = data['email']
643 user_email = data['email']
642 pre_db = True
644 pre_db = True
643 try:
645 try:
644 user = User.get_by_email(user_email)
646 user = User.get_by_email(user_email)
645 new_passwd = auth.PasswordGenerator().gen_password(
647 new_passwd = auth.PasswordGenerator().gen_password(
646 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
648 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
647 if user:
649 if user:
648 user.password = auth.get_crypt_password(new_passwd)
650 user.password = auth.get_crypt_password(new_passwd)
649 # also force this user to reset his password !
651 # also force this user to reset his password !
650 user.update_userdata(force_password_change=True)
652 user.update_userdata(force_password_change=True)
651
653
652 Session().add(user)
654 Session().add(user)
653
655
654 # now delete the token in question
656 # now delete the token in question
655 UserApiKeys = AuthTokenModel.cls
657 UserApiKeys = AuthTokenModel.cls
656 UserApiKeys().query().filter(
658 UserApiKeys().query().filter(
657 UserApiKeys.api_key == data['token']).delete()
659 UserApiKeys.api_key == data['token']).delete()
658
660
659 Session().commit()
661 Session().commit()
660 log.info('successfully reset password for `%s`', user_email)
662 log.info('successfully reset password for `%s`', user_email)
661
663
662 if new_passwd is None:
664 if new_passwd is None:
663 raise Exception('unable to generate new password')
665 raise Exception('unable to generate new password')
664
666
665 pre_db = False
667 pre_db = False
666
668
667 email_kwargs = {
669 email_kwargs = {
668 'new_password': new_passwd,
670 'new_password': new_passwd,
669 'user': user,
671 'user': user,
670 'email': user_email,
672 'email': user_email,
671 'date': datetime.datetime.now()
673 'date': datetime.datetime.now()
672 }
674 }
673
675
674 (subject, headers, email_body,
676 (subject, headers, email_body,
675 email_body_plaintext) = EmailNotificationModel().render_email(
677 email_body_plaintext) = EmailNotificationModel().render_email(
676 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
678 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
677 **email_kwargs)
679 **email_kwargs)
678
680
679 recipients = [user_email]
681 recipients = [user_email]
680
682
681 action_logger_generic(
683 action_logger_generic(
682 'sent new password to user: {} with email: {}'.format(
684 'sent new password to user: {} with email: {}'.format(
683 user, user_email), namespace='security.password_reset')
685 user, user_email), namespace='security.password_reset')
684
686
685 run_task(tasks.send_email, recipients, subject,
687 run_task(tasks.send_email, recipients, subject,
686 email_body_plaintext, email_body)
688 email_body_plaintext, email_body)
687
689
688 except Exception:
690 except Exception:
689 log.error('Failed to update user password')
691 log.error('Failed to update user password')
690 log.error(traceback.format_exc())
692 log.error(traceback.format_exc())
691 if pre_db:
693 if pre_db:
692 # we rollback only if local db stuff fails. If it goes into
694 # we rollback only if local db stuff fails. If it goes into
693 # run_task, we're pass rollback state this wouldn't work then
695 # run_task, we're pass rollback state this wouldn't work then
694 Session().rollback()
696 Session().rollback()
695
697
696 return True
698 return True
697
699
698 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
700 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
699 """
701 """
700 Fetches auth_user by user_id,or api_key if present.
702 Fetches auth_user by user_id,or api_key if present.
701 Fills auth_user attributes with those taken from database.
703 Fills auth_user attributes with those taken from database.
702 Additionally set's is_authenitated if lookup fails
704 Additionally set's is_authenitated if lookup fails
703 present in database
705 present in database
704
706
705 :param auth_user: instance of user to set attributes
707 :param auth_user: instance of user to set attributes
706 :param user_id: user id to fetch by
708 :param user_id: user id to fetch by
707 :param api_key: api key to fetch by
709 :param api_key: api key to fetch by
708 :param username: username to fetch by
710 :param username: username to fetch by
709 """
711 """
710 def token_obfuscate(token):
712 def token_obfuscate(token):
711 if token:
713 if token:
712 return token[:4] + "****"
714 return token[:4] + "****"
713
715
714 if user_id is None and api_key is None and username is None:
716 if user_id is None and api_key is None and username is None:
715 raise Exception('You need to pass user_id, api_key or username')
717 raise Exception('You need to pass user_id, api_key or username')
716
718
717 log.debug(
719 log.debug(
718 'AuthUser: fill data execution based on: '
720 'AuthUser: fill data execution based on: '
719 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
721 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
720 try:
722 try:
721 dbuser = None
723 dbuser = None
722 if user_id:
724 if user_id:
723 dbuser = self.get(user_id)
725 dbuser = self.get(user_id)
724 elif api_key:
726 elif api_key:
725 dbuser = self.get_by_auth_token(api_key)
727 dbuser = self.get_by_auth_token(api_key)
726 elif username:
728 elif username:
727 dbuser = self.get_by_username(username)
729 dbuser = self.get_by_username(username)
728
730
729 if not dbuser:
731 if not dbuser:
730 log.warning(
732 log.warning(
731 'Unable to lookup user by id:%s api_key:%s username:%s',
733 'Unable to lookup user by id:%s api_key:%s username:%s',
732 user_id, token_obfuscate(api_key), username)
734 user_id, token_obfuscate(api_key), username)
733 return False
735 return False
734 if not dbuser.active:
736 if not dbuser.active:
735 log.debug('User `%s:%s` is inactive, skipping fill data',
737 log.debug('User `%s:%s` is inactive, skipping fill data',
736 username, user_id)
738 username, user_id)
737 return False
739 return False
738
740
739 log.debug('AuthUser: filling found user:%s data', dbuser)
741 log.debug('AuthUser: filling found user:%s data', dbuser)
740
742
741 attrs = {
743 attrs = {
742 'user_id': dbuser.user_id,
744 'user_id': dbuser.user_id,
743 'username': dbuser.username,
745 'username': dbuser.username,
744 'name': dbuser.name,
746 'name': dbuser.name,
745 'first_name': dbuser.first_name,
747 'first_name': dbuser.first_name,
746 'firstname': dbuser.firstname,
748 'firstname': dbuser.firstname,
747 'last_name': dbuser.last_name,
749 'last_name': dbuser.last_name,
748 'lastname': dbuser.lastname,
750 'lastname': dbuser.lastname,
749 'admin': dbuser.admin,
751 'admin': dbuser.admin,
750 'active': dbuser.active,
752 'active': dbuser.active,
751
753
752 'email': dbuser.email,
754 'email': dbuser.email,
753 'emails': dbuser.emails_cached(),
755 'emails': dbuser.emails_cached(),
754 'short_contact': dbuser.short_contact,
756 'short_contact': dbuser.short_contact,
755 'full_contact': dbuser.full_contact,
757 'full_contact': dbuser.full_contact,
756 'full_name': dbuser.full_name,
758 'full_name': dbuser.full_name,
757 'full_name_or_username': dbuser.full_name_or_username,
759 'full_name_or_username': dbuser.full_name_or_username,
758
760
759 '_api_key': dbuser._api_key,
761 '_api_key': dbuser._api_key,
760 '_user_data': dbuser._user_data,
762 '_user_data': dbuser._user_data,
761
763
762 'created_on': dbuser.created_on,
764 'created_on': dbuser.created_on,
763 'extern_name': dbuser.extern_name,
765 'extern_name': dbuser.extern_name,
764 'extern_type': dbuser.extern_type,
766 'extern_type': dbuser.extern_type,
765
767
766 'inherit_default_permissions': dbuser.inherit_default_permissions,
768 'inherit_default_permissions': dbuser.inherit_default_permissions,
767
769
768 'language': dbuser.language,
770 'language': dbuser.language,
769 'last_activity': dbuser.last_activity,
771 'last_activity': dbuser.last_activity,
770 'last_login': dbuser.last_login,
772 'last_login': dbuser.last_login,
771 'password': dbuser.password,
773 'password': dbuser.password,
772 }
774 }
773 auth_user.__dict__.update(attrs)
775 auth_user.__dict__.update(attrs)
774 except Exception:
776 except Exception:
775 log.error(traceback.format_exc())
777 log.error(traceback.format_exc())
776 auth_user.is_authenticated = False
778 auth_user.is_authenticated = False
777 return False
779 return False
778
780
779 return True
781 return True
780
782
781 def has_perm(self, user, perm):
783 def has_perm(self, user, perm):
782 perm = self._get_perm(perm)
784 perm = self._get_perm(perm)
783 user = self._get_user(user)
785 user = self._get_user(user)
784
786
785 return UserToPerm.query().filter(UserToPerm.user == user)\
787 return UserToPerm.query().filter(UserToPerm.user == user)\
786 .filter(UserToPerm.permission == perm).scalar() is not None
788 .filter(UserToPerm.permission == perm).scalar() is not None
787
789
788 def grant_perm(self, user, perm):
790 def grant_perm(self, user, perm):
789 """
791 """
790 Grant user global permissions
792 Grant user global permissions
791
793
792 :param user:
794 :param user:
793 :param perm:
795 :param perm:
794 """
796 """
795 user = self._get_user(user)
797 user = self._get_user(user)
796 perm = self._get_perm(perm)
798 perm = self._get_perm(perm)
797 # if this permission is already granted skip it
799 # if this permission is already granted skip it
798 _perm = UserToPerm.query()\
800 _perm = UserToPerm.query()\
799 .filter(UserToPerm.user == user)\
801 .filter(UserToPerm.user == user)\
800 .filter(UserToPerm.permission == perm)\
802 .filter(UserToPerm.permission == perm)\
801 .scalar()
803 .scalar()
802 if _perm:
804 if _perm:
803 return
805 return
804 new = UserToPerm()
806 new = UserToPerm()
805 new.user = user
807 new.user = user
806 new.permission = perm
808 new.permission = perm
807 self.sa.add(new)
809 self.sa.add(new)
808 return new
810 return new
809
811
810 def revoke_perm(self, user, perm):
812 def revoke_perm(self, user, perm):
811 """
813 """
812 Revoke users global permissions
814 Revoke users global permissions
813
815
814 :param user:
816 :param user:
815 :param perm:
817 :param perm:
816 """
818 """
817 user = self._get_user(user)
819 user = self._get_user(user)
818 perm = self._get_perm(perm)
820 perm = self._get_perm(perm)
819
821
820 obj = UserToPerm.query()\
822 obj = UserToPerm.query()\
821 .filter(UserToPerm.user == user)\
823 .filter(UserToPerm.user == user)\
822 .filter(UserToPerm.permission == perm)\
824 .filter(UserToPerm.permission == perm)\
823 .scalar()
825 .scalar()
824 if obj:
826 if obj:
825 self.sa.delete(obj)
827 self.sa.delete(obj)
826
828
827 def add_extra_email(self, user, email):
829 def add_extra_email(self, user, email):
828 """
830 """
829 Adds email address to UserEmailMap
831 Adds email address to UserEmailMap
830
832
831 :param user:
833 :param user:
832 :param email:
834 :param email:
833 """
835 """
834
836
835 user = self._get_user(user)
837 user = self._get_user(user)
836
838
837 obj = UserEmailMap()
839 obj = UserEmailMap()
838 obj.user = user
840 obj.user = user
839 obj.email = email
841 obj.email = email
840 self.sa.add(obj)
842 self.sa.add(obj)
841 return obj
843 return obj
842
844
843 def delete_extra_email(self, user, email_id):
845 def delete_extra_email(self, user, email_id):
844 """
846 """
845 Removes email address from UserEmailMap
847 Removes email address from UserEmailMap
846
848
847 :param user:
849 :param user:
848 :param email_id:
850 :param email_id:
849 """
851 """
850 user = self._get_user(user)
852 user = self._get_user(user)
851 obj = UserEmailMap.query().get(email_id)
853 obj = UserEmailMap.query().get(email_id)
852 if obj and obj.user_id == user.user_id:
854 if obj and obj.user_id == user.user_id:
853 self.sa.delete(obj)
855 self.sa.delete(obj)
854
856
855 def parse_ip_range(self, ip_range):
857 def parse_ip_range(self, ip_range):
856 ip_list = []
858 ip_list = []
857
859
858 def make_unique(value):
860 def make_unique(value):
859 seen = []
861 seen = []
860 return [c for c in value if not (c in seen or seen.append(c))]
862 return [c for c in value if not (c in seen or seen.append(c))]
861
863
862 # firsts split by commas
864 # firsts split by commas
863 for ip_range in ip_range.split(','):
865 for ip_range in ip_range.split(','):
864 if not ip_range:
866 if not ip_range:
865 continue
867 continue
866 ip_range = ip_range.strip()
868 ip_range = ip_range.strip()
867 if '-' in ip_range:
869 if '-' in ip_range:
868 start_ip, end_ip = ip_range.split('-', 1)
870 start_ip, end_ip = ip_range.split('-', 1)
869 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
871 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
870 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
872 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
871 parsed_ip_range = []
873 parsed_ip_range = []
872
874
873 for index in xrange(int(start_ip), int(end_ip) + 1):
875 for index in xrange(int(start_ip), int(end_ip) + 1):
874 new_ip = ipaddress.ip_address(index)
876 new_ip = ipaddress.ip_address(index)
875 parsed_ip_range.append(str(new_ip))
877 parsed_ip_range.append(str(new_ip))
876 ip_list.extend(parsed_ip_range)
878 ip_list.extend(parsed_ip_range)
877 else:
879 else:
878 ip_list.append(ip_range)
880 ip_list.append(ip_range)
879
881
880 return make_unique(ip_list)
882 return make_unique(ip_list)
881
883
882 def add_extra_ip(self, user, ip, description=None):
884 def add_extra_ip(self, user, ip, description=None):
883 """
885 """
884 Adds ip address to UserIpMap
886 Adds ip address to UserIpMap
885
887
886 :param user:
888 :param user:
887 :param ip:
889 :param ip:
888 """
890 """
889
891
890 user = self._get_user(user)
892 user = self._get_user(user)
891 obj = UserIpMap()
893 obj = UserIpMap()
892 obj.user = user
894 obj.user = user
893 obj.ip_addr = ip
895 obj.ip_addr = ip
894 obj.description = description
896 obj.description = description
895 self.sa.add(obj)
897 self.sa.add(obj)
896 return obj
898 return obj
897
899
898 auth_token_role = AuthTokenModel.cls
900 auth_token_role = AuthTokenModel.cls
899
901
900 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
902 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
901 scope_callback=None):
903 scope_callback=None):
902 """
904 """
903 Add AuthToken for user.
905 Add AuthToken for user.
904
906
905 :param user: username/user_id
907 :param user: username/user_id
906 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
908 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
907 :param role: one of AuthTokenModel.cls.ROLE_*
909 :param role: one of AuthTokenModel.cls.ROLE_*
908 :param description: optional string description
910 :param description: optional string description
909 """
911 """
910
912
911 token = AuthTokenModel().create(
913 token = AuthTokenModel().create(
912 user, description, lifetime_minutes, role)
914 user, description, lifetime_minutes, role)
913 if scope_callback and callable(scope_callback):
915 if scope_callback and callable(scope_callback):
914 # call the callback if we provide, used to attach scope for EE edition
916 # call the callback if we provide, used to attach scope for EE edition
915 scope_callback(token)
917 scope_callback(token)
916 return token
918 return token
917
919
918 def delete_extra_ip(self, user, ip_id):
920 def delete_extra_ip(self, user, ip_id):
919 """
921 """
920 Removes ip address from UserIpMap
922 Removes ip address from UserIpMap
921
923
922 :param user:
924 :param user:
923 :param ip_id:
925 :param ip_id:
924 """
926 """
925 user = self._get_user(user)
927 user = self._get_user(user)
926 obj = UserIpMap.query().get(ip_id)
928 obj = UserIpMap.query().get(ip_id)
927 if obj and obj.user_id == user.user_id:
929 if obj and obj.user_id == user.user_id:
928 self.sa.delete(obj)
930 self.sa.delete(obj)
929
931
930 def get_accounts_in_creation_order(self, current_user=None):
932 def get_accounts_in_creation_order(self, current_user=None):
931 """
933 """
932 Get accounts in order of creation for deactivation for license limits
934 Get accounts in order of creation for deactivation for license limits
933
935
934 pick currently logged in user, and append to the list in position 0
936 pick currently logged in user, and append to the list in position 0
935 pick all super-admins in order of creation date and add it to the list
937 pick all super-admins in order of creation date and add it to the list
936 pick all other accounts in order of creation and add it to the list.
938 pick all other accounts in order of creation and add it to the list.
937
939
938 Based on that list, the last accounts can be disabled as they are
940 Based on that list, the last accounts can be disabled as they are
939 created at the end and don't include any of the super admins as well
941 created at the end and don't include any of the super admins as well
940 as the current user.
942 as the current user.
941
943
942 :param current_user: optionally current user running this operation
944 :param current_user: optionally current user running this operation
943 """
945 """
944
946
945 if not current_user:
947 if not current_user:
946 current_user = get_current_rhodecode_user()
948 current_user = get_current_rhodecode_user()
947 active_super_admins = [
949 active_super_admins = [
948 x.user_id for x in User.query()
950 x.user_id for x in User.query()
949 .filter(User.user_id != current_user.user_id)
951 .filter(User.user_id != current_user.user_id)
950 .filter(User.active == true())
952 .filter(User.active == true())
951 .filter(User.admin == true())
953 .filter(User.admin == true())
952 .order_by(User.created_on.asc())]
954 .order_by(User.created_on.asc())]
953
955
954 active_regular_users = [
956 active_regular_users = [
955 x.user_id for x in User.query()
957 x.user_id for x in User.query()
956 .filter(User.user_id != current_user.user_id)
958 .filter(User.user_id != current_user.user_id)
957 .filter(User.active == true())
959 .filter(User.active == true())
958 .filter(User.admin == false())
960 .filter(User.admin == false())
959 .order_by(User.created_on.asc())]
961 .order_by(User.created_on.asc())]
960
962
961 list_of_accounts = [current_user.user_id]
963 list_of_accounts = [current_user.user_id]
962 list_of_accounts += active_super_admins
964 list_of_accounts += active_super_admins
963 list_of_accounts += active_regular_users
965 list_of_accounts += active_regular_users
964
966
965 return list_of_accounts
967 return list_of_accounts
966
968
967 def deactivate_last_users(self, expected_users, current_user=None):
969 def deactivate_last_users(self, expected_users, current_user=None):
968 """
970 """
969 Deactivate accounts that are over the license limits.
971 Deactivate accounts that are over the license limits.
970 Algorithm of which accounts to disabled is based on the formula:
972 Algorithm of which accounts to disabled is based on the formula:
971
973
972 Get current user, then super admins in creation order, then regular
974 Get current user, then super admins in creation order, then regular
973 active users in creation order.
975 active users in creation order.
974
976
975 Using that list we mark all accounts from the end of it as inactive.
977 Using that list we mark all accounts from the end of it as inactive.
976 This way we block only latest created accounts.
978 This way we block only latest created accounts.
977
979
978 :param expected_users: list of users in special order, we deactivate
980 :param expected_users: list of users in special order, we deactivate
979 the end N amount of users from that list
981 the end N amount of users from that list
980 """
982 """
981
983
982 list_of_accounts = self.get_accounts_in_creation_order(
984 list_of_accounts = self.get_accounts_in_creation_order(
983 current_user=current_user)
985 current_user=current_user)
984
986
985 for acc_id in list_of_accounts[expected_users + 1:]:
987 for acc_id in list_of_accounts[expected_users + 1:]:
986 user = User.get(acc_id)
988 user = User.get(acc_id)
987 log.info('Deactivating account %s for license unlock', user)
989 log.info('Deactivating account %s for license unlock', user)
988 user.active = False
990 user.active = False
989 Session().add(user)
991 Session().add(user)
990 Session().commit()
992 Session().commit()
991
993
992 return
994 return
993
995
994 def get_user_log(self, user, filter_term):
996 def get_user_log(self, user, filter_term):
995 user_log = UserLog.query()\
997 user_log = UserLog.query()\
996 .filter(or_(UserLog.user_id == user.user_id,
998 .filter(or_(UserLog.user_id == user.user_id,
997 UserLog.username == user.username))\
999 UserLog.username == user.username))\
998 .options(joinedload(UserLog.user))\
1000 .options(joinedload(UserLog.user))\
999 .options(joinedload(UserLog.repository))\
1001 .options(joinedload(UserLog.repository))\
1000 .order_by(UserLog.action_date.desc())
1002 .order_by(UserLog.action_date.desc())
1001
1003
1002 user_log = user_log_filter(user_log, filter_term)
1004 user_log = user_log_filter(user_log, filter_term)
1003 return user_log
1005 return user_log
@@ -1,195 +1,198 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 re
21 import re
22 import colander
22 import colander
23
23
24 from rhodecode import forms
24 from rhodecode import forms
25 from rhodecode.model.db import User, UserEmailMap
25 from rhodecode.model.db import User, UserEmailMap
26 from rhodecode.model.validation_schema import types, validators
26 from rhodecode.model.validation_schema import types, validators
27 from rhodecode.translation import _
27 from rhodecode.translation import _
28 from rhodecode.lib.auth import check_password
28 from rhodecode.lib.auth import check_password
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30
30
31
31
32 @colander.deferred
32 @colander.deferred
33 def deferred_user_password_validator(node, kw):
33 def deferred_user_password_validator(node, kw):
34 username = kw.get('username')
34 username = kw.get('username')
35 user = User.get_by_username(username)
35 user = User.get_by_username(username)
36
36
37 def _user_password_validator(node, value):
37 def _user_password_validator(node, value):
38 if not check_password(value, user.password):
38 if not check_password(value, user.password):
39 msg = _('Password is incorrect')
39 msg = _('Password is incorrect')
40 raise colander.Invalid(node, msg)
40 raise colander.Invalid(node, msg)
41 return _user_password_validator
41 return _user_password_validator
42
42
43
43
44
44
45 class ChangePasswordSchema(colander.Schema):
45 class ChangePasswordSchema(colander.Schema):
46
46
47 current_password = colander.SchemaNode(
47 current_password = colander.SchemaNode(
48 colander.String(),
48 colander.String(),
49 missing=colander.required,
49 missing=colander.required,
50 widget=forms.widget.PasswordWidget(redisplay=True),
50 widget=forms.widget.PasswordWidget(redisplay=True),
51 validator=deferred_user_password_validator)
51 validator=deferred_user_password_validator)
52
52
53 new_password = colander.SchemaNode(
53 new_password = colander.SchemaNode(
54 colander.String(),
54 colander.String(),
55 missing=colander.required,
55 missing=colander.required,
56 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
56 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
57 validator=colander.Length(min=6))
57 validator=colander.Length(min=6))
58
58
59 def validator(self, form, values):
59 def validator(self, form, values):
60 if values['current_password'] == values['new_password']:
60 if values['current_password'] == values['new_password']:
61 exc = colander.Invalid(form)
61 exc = colander.Invalid(form)
62 exc['new_password'] = _('New password must be different '
62 exc['new_password'] = _('New password must be different '
63 'to old password')
63 'to old password')
64 raise exc
64 raise exc
65
65
66
66
67 @colander.deferred
67 @colander.deferred
68 def deferred_username_validator(node, kw):
68 def deferred_username_validator(node, kw):
69
69
70 def name_validator(node, value):
70 def name_validator(node, value):
71 msg = _(
71 msg = _(
72 u'Username may only contain alphanumeric characters '
72 u'Username may only contain alphanumeric characters '
73 u'underscores, periods or dashes and must begin with '
73 u'underscores, periods or dashes and must begin with '
74 u'alphanumeric character or underscore')
74 u'alphanumeric character or underscore')
75
75
76 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
76 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
77 raise colander.Invalid(node, msg)
77 raise colander.Invalid(node, msg)
78
78
79 return name_validator
79 return name_validator
80
80
81
81
82 @colander.deferred
82 @colander.deferred
83 def deferred_email_validator(node, kw):
83 def deferred_email_validator(node, kw):
84 # NOTE(marcink): we might provide uniqueness validation later here...
84 # NOTE(marcink): we might provide uniqueness validation later here...
85 return colander.Email()
85 return colander.Email()
86
86
87
87
88 class UserSchema(colander.Schema):
88 class UserSchema(colander.Schema):
89 username = colander.SchemaNode(
89 username = colander.SchemaNode(
90 colander.String(),
90 colander.String(),
91 validator=deferred_username_validator)
91 validator=deferred_username_validator)
92
92
93 email = colander.SchemaNode(
93 email = colander.SchemaNode(
94 colander.String(),
94 colander.String(),
95 validator=deferred_email_validator)
95 validator=deferred_email_validator)
96
96
97 password = colander.SchemaNode(
97 password = colander.SchemaNode(
98 colander.String(), missing='')
98 colander.String(), missing='')
99
99
100 first_name = colander.SchemaNode(
100 first_name = colander.SchemaNode(
101 colander.String(), missing='')
101 colander.String(), missing='')
102
102
103 last_name = colander.SchemaNode(
103 last_name = colander.SchemaNode(
104 colander.String(), missing='')
104 colander.String(), missing='')
105
105
106 description = colander.SchemaNode(
107 colander.String(), missing='')
108
106 active = colander.SchemaNode(
109 active = colander.SchemaNode(
107 types.StringBooleanType(),
110 types.StringBooleanType(),
108 missing=False)
111 missing=False)
109
112
110 admin = colander.SchemaNode(
113 admin = colander.SchemaNode(
111 types.StringBooleanType(),
114 types.StringBooleanType(),
112 missing=False)
115 missing=False)
113
116
114 extern_name = colander.SchemaNode(
117 extern_name = colander.SchemaNode(
115 colander.String(), missing='')
118 colander.String(), missing='')
116
119
117 extern_type = colander.SchemaNode(
120 extern_type = colander.SchemaNode(
118 colander.String(), missing='')
121 colander.String(), missing='')
119
122
120 def deserialize(self, cstruct):
123 def deserialize(self, cstruct):
121 """
124 """
122 Custom deserialize that allows to chain validation, and verify
125 Custom deserialize that allows to chain validation, and verify
123 permissions, and as last step uniqueness
126 permissions, and as last step uniqueness
124 """
127 """
125
128
126 appstruct = super(UserSchema, self).deserialize(cstruct)
129 appstruct = super(UserSchema, self).deserialize(cstruct)
127 return appstruct
130 return appstruct
128
131
129
132
130 @colander.deferred
133 @colander.deferred
131 def deferred_user_email_in_emails_validator(node, kw):
134 def deferred_user_email_in_emails_validator(node, kw):
132 return colander.OneOf(kw.get('user_emails'))
135 return colander.OneOf(kw.get('user_emails'))
133
136
134
137
135 @colander.deferred
138 @colander.deferred
136 def deferred_additional_email_validator(node, kw):
139 def deferred_additional_email_validator(node, kw):
137 emails = kw.get('user_emails')
140 emails = kw.get('user_emails')
138
141
139 def name_validator(node, value):
142 def name_validator(node, value):
140 if value in emails:
143 if value in emails:
141 msg = _('This e-mail address is already taken')
144 msg = _('This e-mail address is already taken')
142 raise colander.Invalid(node, msg)
145 raise colander.Invalid(node, msg)
143 user = User.get_by_email(value, case_insensitive=True)
146 user = User.get_by_email(value, case_insensitive=True)
144 if user:
147 if user:
145 msg = _(u'This e-mail address is already taken')
148 msg = _(u'This e-mail address is already taken')
146 raise colander.Invalid(node, msg)
149 raise colander.Invalid(node, msg)
147 c = colander.Email()
150 c = colander.Email()
148 return c(node, value)
151 return c(node, value)
149 return name_validator
152 return name_validator
150
153
151
154
152 @colander.deferred
155 @colander.deferred
153 def deferred_user_email_in_emails_widget(node, kw):
156 def deferred_user_email_in_emails_widget(node, kw):
154 import deform.widget
157 import deform.widget
155 emails = [(email, email) for email in kw.get('user_emails')]
158 emails = [(email, email) for email in kw.get('user_emails')]
156 return deform.widget.Select2Widget(values=emails)
159 return deform.widget.Select2Widget(values=emails)
157
160
158
161
159 class UserProfileSchema(colander.Schema):
162 class UserProfileSchema(colander.Schema):
160 username = colander.SchemaNode(
163 username = colander.SchemaNode(
161 colander.String(),
164 colander.String(),
162 validator=deferred_username_validator)
165 validator=deferred_username_validator)
163
166
164 firstname = colander.SchemaNode(
167 firstname = colander.SchemaNode(
165 colander.String(), missing='', title='First name')
168 colander.String(), missing='', title='First name')
166
169
167 lastname = colander.SchemaNode(
170 lastname = colander.SchemaNode(
168 colander.String(), missing='', title='Last name')
171 colander.String(), missing='', title='Last name')
169
172
170 description = colander.SchemaNode(
173 description = colander.SchemaNode(
171 colander.String(), missing='', title='Personal Description',
174 colander.String(), missing='', title='Personal Description',
172 widget=forms.widget.TextAreaWidget(),
175 widget=forms.widget.TextAreaWidget(),
173 validator=colander.Length(max=250)
176 validator=colander.Length(max=250)
174 )
177 )
175
178
176 email = colander.SchemaNode(
179 email = colander.SchemaNode(
177 colander.String(), widget=deferred_user_email_in_emails_widget,
180 colander.String(), widget=deferred_user_email_in_emails_widget,
178 validator=deferred_user_email_in_emails_validator,
181 validator=deferred_user_email_in_emails_validator,
179 description=h.literal(
182 description=h.literal(
180 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
183 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
181 '/_admin/my_account/emails')),
184 '/_admin/my_account/emails')),
182 )
185 )
183
186
184
187
185
188
186 class AddEmailSchema(colander.Schema):
189 class AddEmailSchema(colander.Schema):
187 current_password = colander.SchemaNode(
190 current_password = colander.SchemaNode(
188 colander.String(),
191 colander.String(),
189 missing=colander.required,
192 missing=colander.required,
190 widget=forms.widget.PasswordWidget(redisplay=True),
193 widget=forms.widget.PasswordWidget(redisplay=True),
191 validator=deferred_user_password_validator)
194 validator=deferred_user_password_validator)
192
195
193 email = colander.SchemaNode(
196 email = colander.SchemaNode(
194 colander.String(), title='New Email',
197 colander.String(), title='New Email',
195 validator=deferred_additional_email_validator)
198 validator=deferred_additional_email_validator)
@@ -1,155 +1,161 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default user-profile">
3 <div class="panel panel-default user-profile">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Profile')}</h3>
5 <h3 class="panel-title">${_('User Profile')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 <div class="user-profile-content">
8 <div class="user-profile-content">
9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
10 <% readonly = None %>
10 <% readonly = None %>
11 <% disabled = "" %>
11 <% disabled = "" %>
12 %if c.extern_type != 'rhodecode':
12 %if c.extern_type != 'rhodecode':
13 <% readonly = "readonly" %>
13 <% readonly = "readonly" %>
14 <% disabled = " disabled" %>
14 <% disabled = " disabled" %>
15 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
15 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
16 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
16 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
17 </div>
17 </div>
18 %endif
18 %endif
19 <div class="form">
19 <div class="form">
20 <div class="fields">
20 <div class="fields">
21 <div class="field">
21 <div class="field">
22 <div class="label photo">
22 <div class="label photo">
23 ${_('Photo')}:
23 ${_('Photo')}:
24 </div>
24 </div>
25 <div class="input profile">
25 <div class="input profile">
26 %if c.visual.use_gravatar:
26 %if c.visual.use_gravatar:
27 ${base.gravatar(c.user.email, 100)}
27 ${base.gravatar(c.user.email, 100)}
28 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
28 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
29 %else:
29 %else:
30 ${base.gravatar(c.user.email, 100)}
30 ${base.gravatar(c.user.email, 100)}
31 %endif
31 %endif
32 </div>
32 </div>
33 </div>
33 </div>
34 <div class="field">
34 <div class="field">
35 <div class="label">
35 <div class="label">
36 ${_('Username')}:
36 ${_('Username')}:
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
39 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
40 </div>
40 </div>
41 </div>
41 </div>
42 <div class="field">
42 <div class="field">
43 <div class="label">
43 <div class="label">
44 <label for="name">${_('First Name')}:</label>
44 <label for="name">${_('First Name')}:</label>
45 </div>
45 </div>
46 <div class="input">
46 <div class="input">
47 ${h.text('firstname', class_="medium")}
47 ${h.text('firstname', class_="medium")}
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51 <div class="field">
51 <div class="field">
52 <div class="label">
52 <div class="label">
53 <label for="lastname">${_('Last Name')}:</label>
53 <label for="lastname">${_('Last Name')}:</label>
54 </div>
54 </div>
55 <div class="input">
55 <div class="input">
56 ${h.text('lastname', class_="medium")}
56 ${h.text('lastname', class_="medium")}
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <div class="field">
60 <div class="field">
61 <div class="label">
61 <div class="label">
62 <label for="email">${_('Email')}:</label>
62 <label for="email">${_('Email')}:</label>
63 </div>
63 </div>
64 <div class="input">
64 <div class="input">
65 ## we should be able to edit email !
65 ## we should be able to edit email !
66 ${h.text('email', class_="medium")}
66 ${h.text('email', class_="medium")}
67 </div>
67 </div>
68 </div>
68 </div>
69 <div class="field">
69 <div class="field">
70 <div class="label">
70 <div class="label">
71 <label for="description">${_('Description')}:</label>
71 <label for="description">${_('Description')}:</label>
72 </div>
72 </div>
73 <div class="input textarea editor">
73 <div class="input textarea editor">
74 ${h.textarea('description', class_="medium")}
74 ${h.textarea('description', rows=10, class_="medium")}
75 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
76 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
77 <span id="meta-tags-desc" style="display: none">
78 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
79 ${dt.metatags_help()}
80 </span>
75 </div>
81 </div>
76 </div>
82 </div>
77 <div class="field">
83 <div class="field">
78 <div class="label">
84 <div class="label">
79 ${_('New Password')}:
85 ${_('New Password')}:
80 </div>
86 </div>
81 <div class="input">
87 <div class="input">
82 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
88 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
83 </div>
89 </div>
84 </div>
90 </div>
85 <div class="field">
91 <div class="field">
86 <div class="label">
92 <div class="label">
87 ${_('New Password Confirmation')}:
93 ${_('New Password Confirmation')}:
88 </div>
94 </div>
89 <div class="input">
95 <div class="input">
90 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
96 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
91 </div>
97 </div>
92 </div>
98 </div>
93 <div class="field">
99 <div class="field">
94 <div class="label-text">
100 <div class="label-text">
95 ${_('Active')}:
101 ${_('Active')}:
96 </div>
102 </div>
97 <div class="input user-checkbox">
103 <div class="input user-checkbox">
98 ${h.checkbox('active',value=True)}
104 ${h.checkbox('active',value=True)}
99 </div>
105 </div>
100 </div>
106 </div>
101 <div class="field">
107 <div class="field">
102 <div class="label-text">
108 <div class="label-text">
103 ${_('Super Admin')}:
109 ${_('Super Admin')}:
104 </div>
110 </div>
105 <div class="input user-checkbox">
111 <div class="input user-checkbox">
106 ${h.checkbox('admin',value=True)}
112 ${h.checkbox('admin',value=True)}
107 </div>
113 </div>
108 </div>
114 </div>
109 <div class="field">
115 <div class="field">
110 <div class="label-text">
116 <div class="label-text">
111 ${_('Authentication type')}:
117 ${_('Authentication type')}:
112 </div>
118 </div>
113 <div class="input">
119 <div class="input">
114 ${h.select('extern_type', c.extern_type, c.allowed_extern_types)}
120 ${h.select('extern_type', c.extern_type, c.allowed_extern_types)}
115 <p class="help-block">${_('When user was created using an external source. He is bound to authentication using this method.')}</p>
121 <p class="help-block">${_('When user was created using an external source. He is bound to authentication using this method.')}</p>
116 </div>
122 </div>
117 </div>
123 </div>
118 <div class="field">
124 <div class="field">
119 <div class="label-text">
125 <div class="label-text">
120 ${_('Name in Source of Record')}:
126 ${_('Name in Source of Record')}:
121 </div>
127 </div>
122 <div class="input">
128 <div class="input">
123 <p>${c.extern_name}</p>
129 <p>${c.extern_name}</p>
124 ${h.hidden('extern_name', readonly="readonly")}
130 ${h.hidden('extern_name', readonly="readonly")}
125 </div>
131 </div>
126 </div>
132 </div>
127 <div class="field">
133 <div class="field">
128 <div class="label">
134 <div class="label">
129 ${_('Language')}:
135 ${_('Language')}:
130 </div>
136 </div>
131 <div class="input">
137 <div class="input">
132 ## allowed_languages is defined in the users.py
138 ## allowed_languages is defined in the users.py
133 ## c.language comes from base.py as a default language
139 ## c.language comes from base.py as a default language
134 ${h.select('language', c.language, c.allowed_languages)}
140 ${h.select('language', c.language, c.allowed_languages)}
135 <p class="help-block">${h.literal(_('User interface language. Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
141 <p class="help-block">${h.literal(_('User interface language. Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
136 </div>
142 </div>
137 </div>
143 </div>
138 <div class="buttons">
144 <div class="buttons">
139 ${h.submit('save', _('Save'), class_="btn")}
145 ${h.submit('save', _('Save'), class_="btn")}
140 ${h.reset('reset', _('Reset'), class_="btn")}
146 ${h.reset('reset', _('Reset'), class_="btn")}
141 </div>
147 </div>
142 </div>
148 </div>
143 </div>
149 </div>
144 ${h.end_form()}
150 ${h.end_form()}
145 </div>
151 </div>
146 </div>
152 </div>
147 </div>
153 </div>
148
154
149 <script>
155 <script>
150 $('#language').select2({
156 $('#language').select2({
151 'containerCssClass': "drop-menu",
157 'containerCssClass': "drop-menu",
152 'dropdownCssClass': "drop-menu-dropdown",
158 'dropdownCssClass': "drop-menu-dropdown",
153 'dropdownAutoWidth': true
159 'dropdownAutoWidth': true
154 });
160 });
155 </script>
161 </script>
@@ -1,415 +1,416 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Helpers for fixture generation
22 Helpers for fixture generation
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import tempfile
27 import tempfile
28 import shutil
28 import shutil
29
29
30 import configobj
30 import configobj
31
31
32 from rhodecode.tests import *
32 from rhodecode.tests import *
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.user_group import UserGroupModel
38 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
40 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.authentication.plugins.auth_rhodecode import \
41 from rhodecode.authentication.plugins.auth_rhodecode import \
42 RhodeCodeAuthPlugin
42 RhodeCodeAuthPlugin
43
43
44 dn = os.path.dirname
44 dn = os.path.dirname
45 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
45 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
46
46
47
47
48 def error_function(*args, **kwargs):
48 def error_function(*args, **kwargs):
49 raise Exception('Total Crash !')
49 raise Exception('Total Crash !')
50
50
51
51
52 class TestINI(object):
52 class TestINI(object):
53 """
53 """
54 Allows to create a new test.ini file as a copy of existing one with edited
54 Allows to create a new test.ini file as a copy of existing one with edited
55 data. Example usage::
55 data. Example usage::
56
56
57 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
57 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
58 print('paster server %s' % new_test_ini)
58 print('paster server %s' % new_test_ini)
59 """
59 """
60
60
61 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
61 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
62 destroy=True, dir=None):
62 destroy=True, dir=None):
63 self.ini_file_path = ini_file_path
63 self.ini_file_path = ini_file_path
64 self.ini_params = ini_params
64 self.ini_params = ini_params
65 self.new_path = None
65 self.new_path = None
66 self.new_path_prefix = new_file_prefix
66 self.new_path_prefix = new_file_prefix
67 self._destroy = destroy
67 self._destroy = destroy
68 self._dir = dir
68 self._dir = dir
69
69
70 def __enter__(self):
70 def __enter__(self):
71 return self.create()
71 return self.create()
72
72
73 def __exit__(self, exc_type, exc_val, exc_tb):
73 def __exit__(self, exc_type, exc_val, exc_tb):
74 self.destroy()
74 self.destroy()
75
75
76 def create(self):
76 def create(self):
77 config = configobj.ConfigObj(
77 config = configobj.ConfigObj(
78 self.ini_file_path, file_error=True, write_empty_values=True)
78 self.ini_file_path, file_error=True, write_empty_values=True)
79
79
80 for data in self.ini_params:
80 for data in self.ini_params:
81 section, ini_params = data.items()[0]
81 section, ini_params = data.items()[0]
82 for key, val in ini_params.items():
82 for key, val in ini_params.items():
83 config[section][key] = val
83 config[section][key] = val
84 with tempfile.NamedTemporaryFile(
84 with tempfile.NamedTemporaryFile(
85 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
85 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
86 delete=False) as new_ini_file:
86 delete=False) as new_ini_file:
87 config.write(new_ini_file)
87 config.write(new_ini_file)
88 self.new_path = new_ini_file.name
88 self.new_path = new_ini_file.name
89
89
90 return self.new_path
90 return self.new_path
91
91
92 def destroy(self):
92 def destroy(self):
93 if self._destroy:
93 if self._destroy:
94 os.remove(self.new_path)
94 os.remove(self.new_path)
95
95
96
96
97 class Fixture(object):
97 class Fixture(object):
98
98
99 def anon_access(self, status):
99 def anon_access(self, status):
100 """
100 """
101 Context process for disabling anonymous access. use like:
101 Context process for disabling anonymous access. use like:
102 fixture = Fixture()
102 fixture = Fixture()
103 with fixture.anon_access(False):
103 with fixture.anon_access(False):
104 #tests
104 #tests
105
105
106 after this block anon access will be set to `not status`
106 after this block anon access will be set to `not status`
107 """
107 """
108
108
109 class context(object):
109 class context(object):
110 def __enter__(self):
110 def __enter__(self):
111 anon = User.get_default_user()
111 anon = User.get_default_user()
112 anon.active = status
112 anon.active = status
113 Session().add(anon)
113 Session().add(anon)
114 Session().commit()
114 Session().commit()
115 time.sleep(1.5) # must sleep for cache (1s to expire)
115 time.sleep(1.5) # must sleep for cache (1s to expire)
116
116
117 def __exit__(self, exc_type, exc_val, exc_tb):
117 def __exit__(self, exc_type, exc_val, exc_tb):
118 anon = User.get_default_user()
118 anon = User.get_default_user()
119 anon.active = not status
119 anon.active = not status
120 Session().add(anon)
120 Session().add(anon)
121 Session().commit()
121 Session().commit()
122
122
123 return context()
123 return context()
124
124
125 def auth_restriction(self, auth_restriction):
125 def auth_restriction(self, auth_restriction):
126 """
126 """
127 Context process for changing the builtin rhodecode plugin auth restrictions.
127 Context process for changing the builtin rhodecode plugin auth restrictions.
128 Use like:
128 Use like:
129 fixture = Fixture()
129 fixture = Fixture()
130 with fixture.auth_restriction('super_admin'):
130 with fixture.auth_restriction('super_admin'):
131 #tests
131 #tests
132
132
133 after this block auth restriction will be taken off
133 after this block auth restriction will be taken off
134 """
134 """
135
135
136 class context(object):
136 class context(object):
137 def _get_pluing(self):
137 def _get_pluing(self):
138 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
138 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
139 RhodeCodeAuthPlugin.uid)
139 RhodeCodeAuthPlugin.uid)
140 plugin = RhodeCodeAuthPlugin(plugin_id)
140 plugin = RhodeCodeAuthPlugin(plugin_id)
141 return plugin
141 return plugin
142
142
143 def __enter__(self):
143 def __enter__(self):
144 plugin = self._get_pluing()
144 plugin = self._get_pluing()
145 plugin.create_or_update_setting(
145 plugin.create_or_update_setting(
146 'auth_restriction', auth_restriction)
146 'auth_restriction', auth_restriction)
147 Session().commit()
147 Session().commit()
148
148
149 def __exit__(self, exc_type, exc_val, exc_tb):
149 def __exit__(self, exc_type, exc_val, exc_tb):
150 plugin = self._get_pluing()
150 plugin = self._get_pluing()
151 plugin.create_or_update_setting(
151 plugin.create_or_update_setting(
152 'auth_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE)
152 'auth_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE)
153 Session().commit()
153 Session().commit()
154
154
155 return context()
155 return context()
156
156
157 def scope_restriction(self, scope_restriction):
157 def scope_restriction(self, scope_restriction):
158 """
158 """
159 Context process for changing the builtin rhodecode plugin scope restrictions.
159 Context process for changing the builtin rhodecode plugin scope restrictions.
160 Use like:
160 Use like:
161 fixture = Fixture()
161 fixture = Fixture()
162 with fixture.scope_restriction('scope_http'):
162 with fixture.scope_restriction('scope_http'):
163 #tests
163 #tests
164
164
165 after this block scope restriction will be taken off
165 after this block scope restriction will be taken off
166 """
166 """
167
167
168 class context(object):
168 class context(object):
169 def _get_pluing(self):
169 def _get_pluing(self):
170 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
170 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
171 RhodeCodeAuthPlugin.uid)
171 RhodeCodeAuthPlugin.uid)
172 plugin = RhodeCodeAuthPlugin(plugin_id)
172 plugin = RhodeCodeAuthPlugin(plugin_id)
173 return plugin
173 return plugin
174
174
175 def __enter__(self):
175 def __enter__(self):
176 plugin = self._get_pluing()
176 plugin = self._get_pluing()
177 plugin.create_or_update_setting(
177 plugin.create_or_update_setting(
178 'scope_restriction', scope_restriction)
178 'scope_restriction', scope_restriction)
179 Session().commit()
179 Session().commit()
180
180
181 def __exit__(self, exc_type, exc_val, exc_tb):
181 def __exit__(self, exc_type, exc_val, exc_tb):
182 plugin = self._get_pluing()
182 plugin = self._get_pluing()
183 plugin.create_or_update_setting(
183 plugin.create_or_update_setting(
184 'scope_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL)
184 'scope_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL)
185 Session().commit()
185 Session().commit()
186
186
187 return context()
187 return context()
188
188
189 def _get_repo_create_params(self, **custom):
189 def _get_repo_create_params(self, **custom):
190 defs = {
190 defs = {
191 'repo_name': None,
191 'repo_name': None,
192 'repo_type': 'hg',
192 'repo_type': 'hg',
193 'clone_uri': '',
193 'clone_uri': '',
194 'push_uri': '',
194 'push_uri': '',
195 'repo_group': '-1',
195 'repo_group': '-1',
196 'repo_description': 'DESC',
196 'repo_description': 'DESC',
197 'repo_private': False,
197 'repo_private': False,
198 'repo_landing_rev': 'rev:tip',
198 'repo_landing_rev': 'rev:tip',
199 'repo_copy_permissions': False,
199 'repo_copy_permissions': False,
200 'repo_state': Repository.STATE_CREATED,
200 'repo_state': Repository.STATE_CREATED,
201 }
201 }
202 defs.update(custom)
202 defs.update(custom)
203 if 'repo_name_full' not in custom:
203 if 'repo_name_full' not in custom:
204 defs.update({'repo_name_full': defs['repo_name']})
204 defs.update({'repo_name_full': defs['repo_name']})
205
205
206 # fix the repo name if passed as repo_name_full
206 # fix the repo name if passed as repo_name_full
207 if defs['repo_name']:
207 if defs['repo_name']:
208 defs['repo_name'] = defs['repo_name'].split('/')[-1]
208 defs['repo_name'] = defs['repo_name'].split('/')[-1]
209
209
210 return defs
210 return defs
211
211
212 def _get_group_create_params(self, **custom):
212 def _get_group_create_params(self, **custom):
213 defs = {
213 defs = {
214 'group_name': None,
214 'group_name': None,
215 'group_description': 'DESC',
215 'group_description': 'DESC',
216 'perm_updates': [],
216 'perm_updates': [],
217 'perm_additions': [],
217 'perm_additions': [],
218 'perm_deletions': [],
218 'perm_deletions': [],
219 'group_parent_id': -1,
219 'group_parent_id': -1,
220 'enable_locking': False,
220 'enable_locking': False,
221 'recursive': False,
221 'recursive': False,
222 }
222 }
223 defs.update(custom)
223 defs.update(custom)
224
224
225 return defs
225 return defs
226
226
227 def _get_user_create_params(self, name, **custom):
227 def _get_user_create_params(self, name, **custom):
228 defs = {
228 defs = {
229 'username': name,
229 'username': name,
230 'password': 'qweqwe',
230 'password': 'qweqwe',
231 'email': '%s+test@rhodecode.org' % name,
231 'email': '%s+test@rhodecode.org' % name,
232 'firstname': 'TestUser',
232 'firstname': 'TestUser',
233 'lastname': 'Test',
233 'lastname': 'Test',
234 'description': 'test description',
234 'active': True,
235 'active': True,
235 'admin': False,
236 'admin': False,
236 'extern_type': 'rhodecode',
237 'extern_type': 'rhodecode',
237 'extern_name': None,
238 'extern_name': None,
238 }
239 }
239 defs.update(custom)
240 defs.update(custom)
240
241
241 return defs
242 return defs
242
243
243 def _get_user_group_create_params(self, name, **custom):
244 def _get_user_group_create_params(self, name, **custom):
244 defs = {
245 defs = {
245 'users_group_name': name,
246 'users_group_name': name,
246 'user_group_description': 'DESC',
247 'user_group_description': 'DESC',
247 'users_group_active': True,
248 'users_group_active': True,
248 'user_group_data': {},
249 'user_group_data': {},
249 }
250 }
250 defs.update(custom)
251 defs.update(custom)
251
252
252 return defs
253 return defs
253
254
254 def create_repo(self, name, **kwargs):
255 def create_repo(self, name, **kwargs):
255 repo_group = kwargs.get('repo_group')
256 repo_group = kwargs.get('repo_group')
256 if isinstance(repo_group, RepoGroup):
257 if isinstance(repo_group, RepoGroup):
257 kwargs['repo_group'] = repo_group.group_id
258 kwargs['repo_group'] = repo_group.group_id
258 name = name.split(Repository.NAME_SEP)[-1]
259 name = name.split(Repository.NAME_SEP)[-1]
259 name = Repository.NAME_SEP.join((repo_group.group_name, name))
260 name = Repository.NAME_SEP.join((repo_group.group_name, name))
260
261
261 if 'skip_if_exists' in kwargs:
262 if 'skip_if_exists' in kwargs:
262 del kwargs['skip_if_exists']
263 del kwargs['skip_if_exists']
263 r = Repository.get_by_repo_name(name)
264 r = Repository.get_by_repo_name(name)
264 if r:
265 if r:
265 return r
266 return r
266
267
267 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
268 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
268 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
269 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
269 RepoModel().create(form_data, cur_user)
270 RepoModel().create(form_data, cur_user)
270 Session().commit()
271 Session().commit()
271 repo = Repository.get_by_repo_name(name)
272 repo = Repository.get_by_repo_name(name)
272 assert repo
273 assert repo
273 return repo
274 return repo
274
275
275 def create_fork(self, repo_to_fork, fork_name, **kwargs):
276 def create_fork(self, repo_to_fork, fork_name, **kwargs):
276 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
277 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
277
278
278 form_data = self._get_repo_create_params(repo_name=fork_name,
279 form_data = self._get_repo_create_params(repo_name=fork_name,
279 fork_parent_id=repo_to_fork.repo_id,
280 fork_parent_id=repo_to_fork.repo_id,
280 repo_type=repo_to_fork.repo_type,
281 repo_type=repo_to_fork.repo_type,
281 **kwargs)
282 **kwargs)
282 #TODO: fix it !!
283 #TODO: fix it !!
283 form_data['description'] = form_data['repo_description']
284 form_data['description'] = form_data['repo_description']
284 form_data['private'] = form_data['repo_private']
285 form_data['private'] = form_data['repo_private']
285 form_data['landing_rev'] = form_data['repo_landing_rev']
286 form_data['landing_rev'] = form_data['repo_landing_rev']
286
287
287 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
288 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
288 RepoModel().create_fork(form_data, cur_user=owner)
289 RepoModel().create_fork(form_data, cur_user=owner)
289 Session().commit()
290 Session().commit()
290 r = Repository.get_by_repo_name(fork_name)
291 r = Repository.get_by_repo_name(fork_name)
291 assert r
292 assert r
292 return r
293 return r
293
294
294 def destroy_repo(self, repo_name, **kwargs):
295 def destroy_repo(self, repo_name, **kwargs):
295 RepoModel().delete(repo_name, pull_requests='delete', **kwargs)
296 RepoModel().delete(repo_name, pull_requests='delete', **kwargs)
296 Session().commit()
297 Session().commit()
297
298
298 def destroy_repo_on_filesystem(self, repo_name):
299 def destroy_repo_on_filesystem(self, repo_name):
299 rm_path = os.path.join(RepoModel().repos_path, repo_name)
300 rm_path = os.path.join(RepoModel().repos_path, repo_name)
300 if os.path.isdir(rm_path):
301 if os.path.isdir(rm_path):
301 shutil.rmtree(rm_path)
302 shutil.rmtree(rm_path)
302
303
303 def create_repo_group(self, name, **kwargs):
304 def create_repo_group(self, name, **kwargs):
304 if 'skip_if_exists' in kwargs:
305 if 'skip_if_exists' in kwargs:
305 del kwargs['skip_if_exists']
306 del kwargs['skip_if_exists']
306 gr = RepoGroup.get_by_group_name(group_name=name)
307 gr = RepoGroup.get_by_group_name(group_name=name)
307 if gr:
308 if gr:
308 return gr
309 return gr
309 form_data = self._get_group_create_params(group_name=name, **kwargs)
310 form_data = self._get_group_create_params(group_name=name, **kwargs)
310 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
311 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
311 gr = RepoGroupModel().create(
312 gr = RepoGroupModel().create(
312 group_name=form_data['group_name'],
313 group_name=form_data['group_name'],
313 group_description=form_data['group_name'],
314 group_description=form_data['group_name'],
314 owner=owner)
315 owner=owner)
315 Session().commit()
316 Session().commit()
316 gr = RepoGroup.get_by_group_name(gr.group_name)
317 gr = RepoGroup.get_by_group_name(gr.group_name)
317 return gr
318 return gr
318
319
319 def destroy_repo_group(self, repogroupid):
320 def destroy_repo_group(self, repogroupid):
320 RepoGroupModel().delete(repogroupid)
321 RepoGroupModel().delete(repogroupid)
321 Session().commit()
322 Session().commit()
322
323
323 def create_user(self, name, **kwargs):
324 def create_user(self, name, **kwargs):
324 if 'skip_if_exists' in kwargs:
325 if 'skip_if_exists' in kwargs:
325 del kwargs['skip_if_exists']
326 del kwargs['skip_if_exists']
326 user = User.get_by_username(name)
327 user = User.get_by_username(name)
327 if user:
328 if user:
328 return user
329 return user
329 form_data = self._get_user_create_params(name, **kwargs)
330 form_data = self._get_user_create_params(name, **kwargs)
330 user = UserModel().create(form_data)
331 user = UserModel().create(form_data)
331
332
332 # create token for user
333 # create token for user
333 AuthTokenModel().create(
334 AuthTokenModel().create(
334 user=user, description=u'TEST_USER_TOKEN')
335 user=user, description=u'TEST_USER_TOKEN')
335
336
336 Session().commit()
337 Session().commit()
337 user = User.get_by_username(user.username)
338 user = User.get_by_username(user.username)
338 return user
339 return user
339
340
340 def destroy_user(self, userid):
341 def destroy_user(self, userid):
341 UserModel().delete(userid)
342 UserModel().delete(userid)
342 Session().commit()
343 Session().commit()
343
344
344 def create_additional_user_email(self, user, email):
345 def create_additional_user_email(self, user, email):
345 uem = UserEmailMap()
346 uem = UserEmailMap()
346 uem.user = user
347 uem.user = user
347 uem.email = email
348 uem.email = email
348 Session().add(uem)
349 Session().add(uem)
349 return uem
350 return uem
350
351
351 def destroy_users(self, userid_iter):
352 def destroy_users(self, userid_iter):
352 for user_id in userid_iter:
353 for user_id in userid_iter:
353 if User.get_by_username(user_id):
354 if User.get_by_username(user_id):
354 UserModel().delete(user_id)
355 UserModel().delete(user_id)
355 Session().commit()
356 Session().commit()
356
357
357 def create_user_group(self, name, **kwargs):
358 def create_user_group(self, name, **kwargs):
358 if 'skip_if_exists' in kwargs:
359 if 'skip_if_exists' in kwargs:
359 del kwargs['skip_if_exists']
360 del kwargs['skip_if_exists']
360 gr = UserGroup.get_by_group_name(group_name=name)
361 gr = UserGroup.get_by_group_name(group_name=name)
361 if gr:
362 if gr:
362 return gr
363 return gr
363 # map active flag to the real attribute. For API consistency of fixtures
364 # map active flag to the real attribute. For API consistency of fixtures
364 if 'active' in kwargs:
365 if 'active' in kwargs:
365 kwargs['users_group_active'] = kwargs['active']
366 kwargs['users_group_active'] = kwargs['active']
366 del kwargs['active']
367 del kwargs['active']
367 form_data = self._get_user_group_create_params(name, **kwargs)
368 form_data = self._get_user_group_create_params(name, **kwargs)
368 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
369 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
369 user_group = UserGroupModel().create(
370 user_group = UserGroupModel().create(
370 name=form_data['users_group_name'],
371 name=form_data['users_group_name'],
371 description=form_data['user_group_description'],
372 description=form_data['user_group_description'],
372 owner=owner, active=form_data['users_group_active'],
373 owner=owner, active=form_data['users_group_active'],
373 group_data=form_data['user_group_data'])
374 group_data=form_data['user_group_data'])
374 Session().commit()
375 Session().commit()
375 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
376 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
376 return user_group
377 return user_group
377
378
378 def destroy_user_group(self, usergroupid):
379 def destroy_user_group(self, usergroupid):
379 UserGroupModel().delete(user_group=usergroupid, force=True)
380 UserGroupModel().delete(user_group=usergroupid, force=True)
380 Session().commit()
381 Session().commit()
381
382
382 def create_gist(self, **kwargs):
383 def create_gist(self, **kwargs):
383 form_data = {
384 form_data = {
384 'description': 'new-gist',
385 'description': 'new-gist',
385 'owner': TEST_USER_ADMIN_LOGIN,
386 'owner': TEST_USER_ADMIN_LOGIN,
386 'gist_type': GistModel.cls.GIST_PUBLIC,
387 'gist_type': GistModel.cls.GIST_PUBLIC,
387 'lifetime': -1,
388 'lifetime': -1,
388 'acl_level': Gist.ACL_LEVEL_PUBLIC,
389 'acl_level': Gist.ACL_LEVEL_PUBLIC,
389 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
390 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
390 }
391 }
391 form_data.update(kwargs)
392 form_data.update(kwargs)
392 gist = GistModel().create(
393 gist = GistModel().create(
393 description=form_data['description'], owner=form_data['owner'],
394 description=form_data['description'], owner=form_data['owner'],
394 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
395 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
395 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
396 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
396 )
397 )
397 Session().commit()
398 Session().commit()
398 return gist
399 return gist
399
400
400 def destroy_gists(self, gistid=None):
401 def destroy_gists(self, gistid=None):
401 for g in GistModel.cls.get_all():
402 for g in GistModel.cls.get_all():
402 if gistid:
403 if gistid:
403 if gistid == g.gist_access_id:
404 if gistid == g.gist_access_id:
404 GistModel().delete(g)
405 GistModel().delete(g)
405 else:
406 else:
406 GistModel().delete(g)
407 GistModel().delete(g)
407 Session().commit()
408 Session().commit()
408
409
409 def load_resource(self, resource_name, strip=False):
410 def load_resource(self, resource_name, strip=False):
410 with open(os.path.join(FIXTURES, resource_name)) as f:
411 with open(os.path.join(FIXTURES, resource_name)) as f:
411 source = f.read()
412 source = f.read()
412 if strip:
413 if strip:
413 source = source.strip()
414 source = source.strip()
414
415
415 return source
416 return source
General Comments 0
You need to be logged in to leave comments. Login now