##// END OF EJS Templates
users: description edit fixes...
marcink -
r4024:dbba29ef default
parent child Browse files
Show More

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

@@ -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 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -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