##// 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.auth import check_password
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import (
27 27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 30 from rhodecode.tests.fixture import Fixture
31 31 from rhodecode.model.db import RepoGroup
32 32
33 33
34 34 # TODO: mikhail: remove fixture from here
35 35 fixture = Fixture()
36 36
37 37
38 38 @pytest.mark.usefixtures("testuser_api", "app")
39 39 class TestCreateUser(object):
40 40 def test_api_create_existing_user(self):
41 41 id_, params = build_data(
42 42 self.apikey, 'create_user',
43 43 username=TEST_USER_ADMIN_LOGIN,
44 44 email='test@foo.com',
45 45 password='trololo')
46 46 response = api_call(self.app, params)
47 47
48 48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 49 assert_error(id_, expected, given=response.body)
50 50
51 51 def test_api_create_user_with_existing_email(self):
52 52 id_, params = build_data(
53 53 self.apikey, 'create_user',
54 54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 55 email=TEST_USER_REGULAR_EMAIL,
56 56 password='trololo')
57 57 response = api_call(self.app, params)
58 58
59 59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 60 assert_error(id_, expected, given=response.body)
61 61
62 62 def test_api_create_user_with_wrong_username(self):
63 63 bad_username = '<> HELLO WORLD <>'
64 64 id_, params = build_data(
65 65 self.apikey, 'create_user',
66 66 username=bad_username,
67 67 email='new@email.com',
68 68 password='trololo')
69 69 response = api_call(self.app, params)
70 70
71 71 expected = {'username':
72 72 "Username may only contain alphanumeric characters "
73 73 "underscores, periods or dashes and must begin with "
74 74 "alphanumeric character or underscore"}
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 def test_api_create_user(self):
78 78 username = 'test_new_api_user'
79 79 email = username + "@foo.com"
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'create_user',
83 83 username=username,
84 84 email=email,
85 description='CTO of Things',
85 86 password='example')
86 87 response = api_call(self.app, params)
87 88
88 89 usr = UserModel().get_by_username(username)
89 90 ret = {
90 91 'msg': 'created new user `%s`' % (username,),
91 92 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 93 }
93 94 try:
94 95 expected = ret
95 96 assert check_password('example', usr.password)
96 97 assert_ok(id_, expected, given=response.body)
97 98 finally:
98 99 fixture.destroy_user(usr.user_id)
99 100
100 101 def test_api_create_user_without_password(self):
101 102 username = 'test_new_api_user_passwordless'
102 103 email = username + "@foo.com"
103 104
104 105 id_, params = build_data(
105 106 self.apikey, 'create_user',
106 107 username=username,
107 108 email=email)
108 109 response = api_call(self.app, params)
109 110
110 111 usr = UserModel().get_by_username(username)
111 112 ret = {
112 113 'msg': 'created new user `%s`' % (username,),
113 114 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 115 }
115 116 try:
116 117 expected = ret
117 118 assert_ok(id_, expected, given=response.body)
118 119 finally:
119 120 fixture.destroy_user(usr.user_id)
120 121
121 122 def test_api_create_user_with_extern_name(self):
122 123 username = 'test_new_api_user_passwordless'
123 124 email = username + "@foo.com"
124 125
125 126 id_, params = build_data(
126 127 self.apikey, 'create_user',
127 128 username=username,
128 129 email=email, extern_name='rhodecode')
129 130 response = api_call(self.app, params)
130 131
131 132 usr = UserModel().get_by_username(username)
132 133 ret = {
133 134 'msg': 'created new user `%s`' % (username,),
134 135 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 136 }
136 137 try:
137 138 expected = ret
138 139 assert_ok(id_, expected, given=response.body)
139 140 finally:
140 141 fixture.destroy_user(usr.user_id)
141 142
142 143 def test_api_create_user_with_password_change(self):
143 144 username = 'test_new_api_user_password_change'
144 145 email = username + "@foo.com"
145 146
146 147 id_, params = build_data(
147 148 self.apikey, 'create_user',
148 149 username=username,
149 150 email=email, extern_name='rhodecode',
150 151 force_password_change=True)
151 152 response = api_call(self.app, params)
152 153
153 154 usr = UserModel().get_by_username(username)
154 155 ret = {
155 156 'msg': 'created new user `%s`' % (username,),
156 157 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 158 }
158 159 try:
159 160 expected = ret
160 161 assert_ok(id_, expected, given=response.body)
161 162 finally:
162 163 fixture.destroy_user(usr.user_id)
163 164
164 165 def test_api_create_user_with_personal_repo_group(self):
165 166 username = 'test_new_api_user_personal_group'
166 167 email = username + "@foo.com"
167 168
168 169 id_, params = build_data(
169 170 self.apikey, 'create_user',
170 171 username=username,
171 172 email=email, extern_name='rhodecode',
172 173 create_personal_repo_group=True)
173 174 response = api_call(self.app, params)
174 175
175 176 usr = UserModel().get_by_username(username)
176 177 ret = {
177 178 'msg': 'created new user `%s`' % (username,),
178 179 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 180 }
180 181
181 182 personal_group = RepoGroup.get_by_group_name(username)
182 183 assert personal_group
183 184 assert personal_group.personal == True
184 185 assert personal_group.user.username == username
185 186
186 187 try:
187 188 expected = ret
188 189 assert_ok(id_, expected, given=response.body)
189 190 finally:
190 191 fixture.destroy_repo_group(username)
191 192 fixture.destroy_user(usr.user_id)
192 193
193 194 @mock.patch.object(UserModel, 'create_or_update', crash)
194 195 def test_api_create_user_when_exception_happened(self):
195 196
196 197 username = 'test_new_api_user'
197 198 email = username + "@foo.com"
198 199
199 200 id_, params = build_data(
200 201 self.apikey, 'create_user',
201 202 username=username,
202 203 email=email,
203 204 password='trololo')
204 205 response = api_call(self.app, params)
205 206 expected = 'failed to create user `%s`' % (username,)
206 207 assert_error(id_, expected, given=response.body)
@@ -1,120 +1,121 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.model.db import User
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdateUser(object):
33 33 @pytest.mark.parametrize("name, expected", [
34 34 ('firstname', 'new_username'),
35 35 ('lastname', 'new_username'),
36 36 ('email', 'new_username'),
37 37 ('admin', True),
38 38 ('admin', False),
39 39 ('extern_type', 'ldap'),
40 40 ('extern_type', None),
41 41 ('extern_name', 'test'),
42 42 ('extern_name', None),
43 43 ('active', False),
44 44 ('active', True),
45 ('password', 'newpass')
45 ('password', 'newpass'),
46 ('description', 'CTO 4 Life')
46 47 ])
47 48 def test_api_update_user(self, name, expected, user_util):
48 49 usr = user_util.create_user()
49 50
50 51 kw = {name: expected, 'userid': usr.user_id}
51 52 id_, params = build_data(self.apikey, 'update_user', **kw)
52 53 response = api_call(self.app, params)
53 54
54 55 ret = {
55 56 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 57 'user': jsonify(
57 58 UserModel()
58 59 .get_by_username(usr.username)
59 60 .get_api_data(include_secrets=True)
60 61 )
61 62 }
62 63
63 64 expected = ret
64 65 assert_ok(id_, expected, given=response.body)
65 66
66 67 def test_api_update_user_no_changed_params(self):
67 68 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 69 ret = jsonify(usr.get_api_data(include_secrets=True))
69 70 id_, params = build_data(
70 71 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71 72
72 73 response = api_call(self.app, params)
73 74 ret = {
74 75 'msg': 'updated user ID:%s %s' % (
75 76 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 77 'user': ret
77 78 }
78 79 expected = ret
79 80 expected['user']['last_activity'] = response.json['result']['user'][
80 81 'last_activity']
81 82 assert_ok(id_, expected, given=response.body)
82 83
83 84 def test_api_update_user_by_user_id(self):
84 85 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
85 86 ret = jsonify(usr.get_api_data(include_secrets=True))
86 87 id_, params = build_data(
87 88 self.apikey, 'update_user', userid=usr.user_id)
88 89
89 90 response = api_call(self.app, params)
90 91 ret = {
91 92 'msg': 'updated user ID:%s %s' % (
92 93 usr.user_id, TEST_USER_ADMIN_LOGIN),
93 94 'user': ret
94 95 }
95 96 expected = ret
96 97 expected['user']['last_activity'] = response.json['result']['user'][
97 98 'last_activity']
98 99 assert_ok(id_, expected, given=response.body)
99 100
100 101 def test_api_update_user_default_user(self):
101 102 usr = User.get_default_user()
102 103 id_, params = build_data(
103 104 self.apikey, 'update_user', userid=usr.user_id)
104 105
105 106 response = api_call(self.app, params)
106 107 expected = 'editing default user is forbidden'
107 108 assert_error(id_, expected, given=response.body)
108 109
109 110 @mock.patch.object(UserModel, 'update_user', crash)
110 111 def test_api_update_user_when_exception_happens(self):
111 112 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
112 113 ret = jsonify(usr.get_api_data(include_secrets=True))
113 114 id_, params = build_data(
114 115 self.apikey, 'update_user', userid=usr.user_id)
115 116
116 117 response = api_call(self.app, params)
117 118 ret = 'failed to update user `%s`' % (usr.user_id,)
118 119
119 120 expected = ret
120 121 assert_error(id_, expected, given=response.body)
@@ -1,564 +1,573 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 from pyramid import compat
23 23
24 24 from rhodecode.api import (
25 25 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 26 from rhodecode.api.utils import (
27 27 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import AuthUser, PasswordGenerator
30 30 from rhodecode.lib.exceptions import DefaultUserException
31 31 from rhodecode.lib.utils2 import safe_int, str2bool
32 32 from rhodecode.model.db import Session, User, Repository
33 33 from rhodecode.model.user import UserModel
34 34 from rhodecode.model import validation_schema
35 35 from rhodecode.model.validation_schema.schemas import user_schema
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 @jsonrpc_method()
41 41 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
42 42 """
43 43 Returns the information associated with a username or userid.
44 44
45 45 * If the ``userid`` is not set, this command returns the information
46 46 for the ``userid`` calling the method.
47 47
48 48 .. note::
49 49
50 50 Normal users may only run this command against their ``userid``. For
51 51 full privileges you must run this command using an |authtoken| with
52 52 admin rights.
53 53
54 54 :param apiuser: This is filled automatically from the |authtoken|.
55 55 :type apiuser: AuthUser
56 56 :param userid: Sets the userid for which data will be returned.
57 57 :type userid: Optional(str or int)
58 58
59 59 Example output:
60 60
61 61 .. code-block:: bash
62 62
63 63 {
64 64 "error": null,
65 65 "id": <id>,
66 66 "result": {
67 67 "active": true,
68 68 "admin": false,
69 69 "api_keys": [ list of keys ],
70 70 "auth_tokens": [ list of tokens with details ],
71 71 "email": "user@example.com",
72 72 "emails": [
73 73 "user@example.com"
74 74 ],
75 75 "extern_name": "rhodecode",
76 76 "extern_type": "rhodecode",
77 77 "firstname": "username",
78 "description": "user description",
78 79 "ip_addresses": [],
79 80 "language": null,
80 81 "last_login": "Timestamp",
81 82 "last_activity": "Timestamp",
82 83 "lastname": "surnae",
83 84 "permissions": <deprecated>,
84 85 "permissions_summary": {
85 86 "global": [
86 87 "hg.inherit_default_perms.true",
87 88 "usergroup.read",
88 89 "hg.repogroup.create.false",
89 90 "hg.create.none",
90 91 "hg.password_reset.enabled",
91 92 "hg.extern_activate.manual",
92 93 "hg.create.write_on_repogroup.false",
93 94 "hg.usergroup.create.false",
94 95 "group.none",
95 96 "repository.none",
96 97 "hg.register.none",
97 98 "hg.fork.repository"
98 99 ],
99 100 "repositories": { "username/example": "repository.write"},
100 101 "repositories_groups": { "user-group/repo": "group.none" },
101 102 "user_groups": { "user_group_name": "usergroup.read" }
102 103 }
103 104 "user_id": 32,
104 105 "username": "username"
105 106 }
106 107 }
107 108 """
108 109
109 110 if not has_superadmin_permission(apiuser):
110 111 # make sure normal user does not pass someone else userid,
111 112 # he is not allowed to do that
112 113 if not isinstance(userid, Optional) and userid != apiuser.user_id:
113 114 raise JSONRPCError('userid is not the same as your user')
114 115
115 116 userid = Optional.extract(userid, evaluate_locals=locals())
116 117 userid = getattr(userid, 'user_id', userid)
117 118
118 119 user = get_user_or_error(userid)
119 120 data = user.get_api_data(include_secrets=True)
120 121 permissions = AuthUser(user_id=user.user_id).permissions
121 122 data['permissions'] = permissions # TODO(marcink): should be deprecated
122 123 data['permissions_summary'] = permissions
123 124 return data
124 125
125 126
126 127 @jsonrpc_method()
127 128 def get_users(request, apiuser):
128 129 """
129 130 Lists all users in the |RCE| user database.
130 131
131 132 This command can only be run using an |authtoken| with admin rights to
132 133 the specified repository.
133 134
134 135 This command takes the following options:
135 136
136 137 :param apiuser: This is filled automatically from the |authtoken|.
137 138 :type apiuser: AuthUser
138 139
139 140 Example output:
140 141
141 142 .. code-block:: bash
142 143
143 144 id : <id_given_in_input>
144 145 result: [<user_object>, ...]
145 146 error: null
146 147 """
147 148
148 149 if not has_superadmin_permission(apiuser):
149 150 raise JSONRPCForbidden()
150 151
151 152 result = []
152 153 users_list = User.query().order_by(User.username) \
153 154 .filter(User.username != User.DEFAULT_USER) \
154 155 .all()
155 156 for user in users_list:
156 157 result.append(user.get_api_data(include_secrets=True))
157 158 return result
158 159
159 160
160 161 @jsonrpc_method()
161 162 def create_user(request, apiuser, username, email, password=Optional(''),
162 firstname=Optional(''), lastname=Optional(''),
163 firstname=Optional(''), lastname=Optional(''), description=Optional(''),
163 164 active=Optional(True), admin=Optional(False),
164 165 extern_name=Optional('rhodecode'),
165 166 extern_type=Optional('rhodecode'),
166 167 force_password_change=Optional(False),
167 168 create_personal_repo_group=Optional(None)):
168 169 """
169 170 Creates a new user and returns the new user object.
170 171
171 172 This command can only be run using an |authtoken| with admin rights to
172 173 the specified repository.
173 174
174 175 This command takes the following options:
175 176
176 177 :param apiuser: This is filled automatically from the |authtoken|.
177 178 :type apiuser: AuthUser
178 179 :param username: Set the new username.
179 180 :type username: str or int
180 181 :param email: Set the user email address.
181 182 :type email: str
182 183 :param password: Set the new user password.
183 184 :type password: Optional(str)
184 185 :param firstname: Set the new user firstname.
185 186 :type firstname: Optional(str)
186 187 :param lastname: Set the new user surname.
187 188 :type lastname: Optional(str)
189 :param description: Set user description, or short bio. Metatags are allowed.
190 :type description: Optional(str)
188 191 :param active: Set the user as active.
189 192 :type active: Optional(``True`` | ``False``)
190 193 :param admin: Give the new user admin rights.
191 194 :type admin: Optional(``True`` | ``False``)
192 195 :param extern_name: Set the authentication plugin name.
193 196 Using LDAP this is filled with LDAP UID.
194 197 :type extern_name: Optional(str)
195 198 :param extern_type: Set the new user authentication plugin.
196 199 :type extern_type: Optional(str)
197 200 :param force_password_change: Force the new user to change password
198 201 on next login.
199 202 :type force_password_change: Optional(``True`` | ``False``)
200 203 :param create_personal_repo_group: Create personal repo group for this user
201 204 :type create_personal_repo_group: Optional(``True`` | ``False``)
202 205
203 206 Example output:
204 207
205 208 .. code-block:: bash
206 209
207 210 id : <id_given_in_input>
208 211 result: {
209 212 "msg" : "created new user `<username>`",
210 213 "user": <user_obj>
211 214 }
212 215 error: null
213 216
214 217 Example error output:
215 218
216 219 .. code-block:: bash
217 220
218 221 id : <id_given_in_input>
219 222 result : null
220 223 error : {
221 224 "user `<username>` already exist"
222 225 or
223 226 "email `<email>` already exist"
224 227 or
225 228 "failed to create user `<username>`"
226 229 }
227 230
228 231 """
229 232 if not has_superadmin_permission(apiuser):
230 233 raise JSONRPCForbidden()
231 234
232 235 if UserModel().get_by_username(username):
233 236 raise JSONRPCError("user `%s` already exist" % (username,))
234 237
235 238 if UserModel().get_by_email(email, case_insensitive=True):
236 239 raise JSONRPCError("email `%s` already exist" % (email,))
237 240
238 241 # generate random password if we actually given the
239 242 # extern_name and it's not rhodecode
240 243 if (not isinstance(extern_name, Optional) and
241 244 Optional.extract(extern_name) != 'rhodecode'):
242 245 # generate temporary password if user is external
243 246 password = PasswordGenerator().gen_password(length=16)
244 247 create_repo_group = Optional.extract(create_personal_repo_group)
245 248 if isinstance(create_repo_group, compat.string_types):
246 249 create_repo_group = str2bool(create_repo_group)
247 250
248 251 username = Optional.extract(username)
249 252 password = Optional.extract(password)
250 253 email = Optional.extract(email)
251 254 first_name = Optional.extract(firstname)
252 255 last_name = Optional.extract(lastname)
256 description = Optional.extract(description)
253 257 active = Optional.extract(active)
254 258 admin = Optional.extract(admin)
255 259 extern_type = Optional.extract(extern_type)
256 260 extern_name = Optional.extract(extern_name)
257 261
258 262 schema = user_schema.UserSchema().bind(
259 263 # user caller
260 264 user=apiuser)
261 265 try:
262 266 schema_data = schema.deserialize(dict(
263 267 username=username,
264 268 email=email,
265 269 password=password,
266 270 first_name=first_name,
267 271 last_name=last_name,
268 272 active=active,
269 273 admin=admin,
274 description=description,
270 275 extern_type=extern_type,
271 276 extern_name=extern_name,
272 277 ))
273 278 except validation_schema.Invalid as err:
274 279 raise JSONRPCValidationError(colander_exc=err)
275 280
276 281 try:
277 282 user = UserModel().create_or_update(
278 283 username=schema_data['username'],
279 284 password=schema_data['password'],
280 285 email=schema_data['email'],
281 286 firstname=schema_data['first_name'],
282 287 lastname=schema_data['last_name'],
288 description=schema_data['description'],
283 289 active=schema_data['active'],
284 290 admin=schema_data['admin'],
285 291 extern_type=schema_data['extern_type'],
286 292 extern_name=schema_data['extern_name'],
287 293 force_password_change=Optional.extract(force_password_change),
288 294 create_repo_group=create_repo_group
289 295 )
290 296 Session().flush()
291 297 creation_data = user.get_api_data()
292 298 audit_logger.store_api(
293 299 'user.create', action_data={'data': creation_data},
294 300 user=apiuser)
295 301
296 302 Session().commit()
297 303 return {
298 304 'msg': 'created new user `%s`' % username,
299 305 'user': user.get_api_data(include_secrets=True)
300 306 }
301 307 except Exception:
302 308 log.exception('Error occurred during creation of user')
303 309 raise JSONRPCError('failed to create user `%s`' % (username,))
304 310
305 311
306 312 @jsonrpc_method()
307 313 def update_user(request, apiuser, userid, username=Optional(None),
308 314 email=Optional(None), password=Optional(None),
309 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 317 extern_type=Optional(None), extern_name=Optional(None), ):
312 318 """
313 319 Updates the details for the specified user, if that user exists.
314 320
315 321 This command can only be run using an |authtoken| with admin rights to
316 322 the specified repository.
317 323
318 324 This command takes the following options:
319 325
320 326 :param apiuser: This is filled automatically from |authtoken|.
321 327 :type apiuser: AuthUser
322 328 :param userid: Set the ``userid`` to update.
323 329 :type userid: str or int
324 330 :param username: Set the new username.
325 331 :type username: str or int
326 332 :param email: Set the new email.
327 333 :type email: str
328 334 :param password: Set the new password.
329 335 :type password: Optional(str)
330 336 :param firstname: Set the new first name.
331 337 :type firstname: Optional(str)
332 338 :param lastname: Set the new surname.
333 339 :type lastname: Optional(str)
340 :param description: Set user description, or short bio. Metatags are allowed.
341 :type description: Optional(str)
334 342 :param active: Set the new user as active.
335 343 :type active: Optional(``True`` | ``False``)
336 344 :param admin: Give the user admin rights.
337 345 :type admin: Optional(``True`` | ``False``)
338 346 :param extern_name: Set the authentication plugin user name.
339 347 Using LDAP this is filled with LDAP UID.
340 348 :type extern_name: Optional(str)
341 349 :param extern_type: Set the authentication plugin type.
342 350 :type extern_type: Optional(str)
343 351
344 352
345 353 Example output:
346 354
347 355 .. code-block:: bash
348 356
349 357 id : <id_given_in_input>
350 358 result: {
351 359 "msg" : "updated user ID:<userid> <username>",
352 360 "user": <user_object>,
353 361 }
354 362 error: null
355 363
356 364 Example error output:
357 365
358 366 .. code-block:: bash
359 367
360 368 id : <id_given_in_input>
361 369 result : null
362 370 error : {
363 371 "failed to update user `<username>`"
364 372 }
365 373
366 374 """
367 375 if not has_superadmin_permission(apiuser):
368 376 raise JSONRPCForbidden()
369 377
370 378 user = get_user_or_error(userid)
371 379 old_data = user.get_api_data()
372 380 # only non optional arguments will be stored in updates
373 381 updates = {}
374 382
375 383 try:
376 384
377 385 store_update(updates, username, 'username')
378 386 store_update(updates, password, 'password')
379 387 store_update(updates, email, 'email')
380 388 store_update(updates, firstname, 'name')
381 389 store_update(updates, lastname, 'lastname')
390 store_update(updates, description, 'description')
382 391 store_update(updates, active, 'active')
383 392 store_update(updates, admin, 'admin')
384 393 store_update(updates, extern_name, 'extern_name')
385 394 store_update(updates, extern_type, 'extern_type')
386 395
387 396 user = UserModel().update_user(user, **updates)
388 397 audit_logger.store_api(
389 398 'user.edit', action_data={'old_data': old_data},
390 399 user=apiuser)
391 400 Session().commit()
392 401 return {
393 402 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
394 403 'user': user.get_api_data(include_secrets=True)
395 404 }
396 405 except DefaultUserException:
397 406 log.exception("Default user edit exception")
398 407 raise JSONRPCError('editing default user is forbidden')
399 408 except Exception:
400 409 log.exception("Error occurred during update of user")
401 410 raise JSONRPCError('failed to update user `%s`' % (userid,))
402 411
403 412
404 413 @jsonrpc_method()
405 414 def delete_user(request, apiuser, userid):
406 415 """
407 416 Deletes the specified user from the |RCE| user database.
408 417
409 418 This command can only be run using an |authtoken| with admin rights to
410 419 the specified repository.
411 420
412 421 .. important::
413 422
414 423 Ensure all open pull requests and open code review
415 424 requests to this user are close.
416 425
417 426 Also ensure all repositories, or repository groups owned by this
418 427 user are reassigned before deletion.
419 428
420 429 This command takes the following options:
421 430
422 431 :param apiuser: This is filled automatically from the |authtoken|.
423 432 :type apiuser: AuthUser
424 433 :param userid: Set the user to delete.
425 434 :type userid: str or int
426 435
427 436 Example output:
428 437
429 438 .. code-block:: bash
430 439
431 440 id : <id_given_in_input>
432 441 result: {
433 442 "msg" : "deleted user ID:<userid> <username>",
434 443 "user": null
435 444 }
436 445 error: null
437 446
438 447 Example error output:
439 448
440 449 .. code-block:: bash
441 450
442 451 id : <id_given_in_input>
443 452 result : null
444 453 error : {
445 454 "failed to delete user ID:<userid> <username>"
446 455 }
447 456
448 457 """
449 458 if not has_superadmin_permission(apiuser):
450 459 raise JSONRPCForbidden()
451 460
452 461 user = get_user_or_error(userid)
453 462 old_data = user.get_api_data()
454 463 try:
455 464 UserModel().delete(userid)
456 465 audit_logger.store_api(
457 466 'user.delete', action_data={'old_data': old_data},
458 467 user=apiuser)
459 468
460 469 Session().commit()
461 470 return {
462 471 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
463 472 'user': None
464 473 }
465 474 except Exception:
466 475 log.exception("Error occurred during deleting of user")
467 476 raise JSONRPCError(
468 477 'failed to delete user ID:%s %s' % (user.user_id, user.username))
469 478
470 479
471 480 @jsonrpc_method()
472 481 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
473 482 """
474 483 Displays all repositories locked by the specified user.
475 484
476 485 * If this command is run by a non-admin user, it returns
477 486 a list of |repos| locked by that user.
478 487
479 488 This command takes the following options:
480 489
481 490 :param apiuser: This is filled automatically from the |authtoken|.
482 491 :type apiuser: AuthUser
483 492 :param userid: Sets the userid whose list of locked |repos| will be
484 493 displayed.
485 494 :type userid: Optional(str or int)
486 495
487 496 Example output:
488 497
489 498 .. code-block:: bash
490 499
491 500 id : <id_given_in_input>
492 501 result : {
493 502 [repo_object, repo_object,...]
494 503 }
495 504 error : null
496 505 """
497 506
498 507 include_secrets = False
499 508 if not has_superadmin_permission(apiuser):
500 509 # make sure normal user does not pass someone else userid,
501 510 # he is not allowed to do that
502 511 if not isinstance(userid, Optional) and userid != apiuser.user_id:
503 512 raise JSONRPCError('userid is not the same as your user')
504 513 else:
505 514 include_secrets = True
506 515
507 516 userid = Optional.extract(userid, evaluate_locals=locals())
508 517 userid = getattr(userid, 'user_id', userid)
509 518 user = get_user_or_error(userid)
510 519
511 520 ret = []
512 521
513 522 # show all locks
514 523 for r in Repository.getAll():
515 524 _user_id, _time, _reason = r.locked
516 525 if _user_id and _time:
517 526 _api_data = r.get_api_data(include_secrets=include_secrets)
518 527 # if we use user filter just show the locks for this user
519 528 if safe_int(_user_id) == user.user_id:
520 529 ret.append(_api_data)
521 530
522 531 return ret
523 532
524 533
525 534 @jsonrpc_method()
526 535 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
527 536 """
528 537 Fetches all action logs made by the specified user.
529 538
530 539 This command takes the following options:
531 540
532 541 :param apiuser: This is filled automatically from the |authtoken|.
533 542 :type apiuser: AuthUser
534 543 :param userid: Sets the userid whose list of locked |repos| will be
535 544 displayed.
536 545 :type userid: Optional(str or int)
537 546
538 547 Example output:
539 548
540 549 .. code-block:: bash
541 550
542 551 id : <id_given_in_input>
543 552 result : {
544 553 [action, action,...]
545 554 }
546 555 error : null
547 556 """
548 557
549 558 if not has_superadmin_permission(apiuser):
550 559 # make sure normal user does not pass someone else userid,
551 560 # he is not allowed to do that
552 561 if not isinstance(userid, Optional) and userid != apiuser.user_id:
553 562 raise JSONRPCError('userid is not the same as your user')
554 563
555 564 userid = Optional.extract(userid, evaluate_locals=locals())
556 565 userid = getattr(userid, 'user_id', userid)
557 566 user = get_user_or_error(userid)
558 567
559 568 ret = []
560 569
561 570 # show all user actions
562 571 for entry in UserModel().get_user_log(user, filter_term=None):
563 572 ret.append(entry)
564 573 return ret
@@ -1,790 +1,793 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22 from sqlalchemy.orm.exc import NoResultFound
23 23
24 24 from rhodecode.lib import auth
25 25 from rhodecode.lib import helpers as h
26 26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.model.user import UserModel
29 29
30 30 from rhodecode.tests import (
31 31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 32 from rhodecode.tests.fixture import Fixture
33 33
34 34 fixture = Fixture()
35 35
36 36
37 37 def route_path(name, params=None, **kwargs):
38 38 import urllib
39 39 from rhodecode.apps._base import ADMIN_PREFIX
40 40
41 41 base_url = {
42 42 'users':
43 43 ADMIN_PREFIX + '/users',
44 44 'users_data':
45 45 ADMIN_PREFIX + '/users_data',
46 46 'users_create':
47 47 ADMIN_PREFIX + '/users/create',
48 48 'users_new':
49 49 ADMIN_PREFIX + '/users/new',
50 50 'user_edit':
51 51 ADMIN_PREFIX + '/users/{user_id}/edit',
52 52 'user_edit_advanced':
53 53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
54 54 'user_edit_global_perms':
55 55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
56 56 'user_edit_global_perms_update':
57 57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
58 58 'user_update':
59 59 ADMIN_PREFIX + '/users/{user_id}/update',
60 60 'user_delete':
61 61 ADMIN_PREFIX + '/users/{user_id}/delete',
62 62 'user_create_personal_repo_group':
63 63 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
64 64
65 65 'edit_user_auth_tokens':
66 66 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
67 67 'edit_user_auth_tokens_add':
68 68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
69 69 'edit_user_auth_tokens_delete':
70 70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
71 71
72 72 'edit_user_emails':
73 73 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
74 74 'edit_user_emails_add':
75 75 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
76 76 'edit_user_emails_delete':
77 77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
78 78
79 79 'edit_user_ips':
80 80 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
81 81 'edit_user_ips_add':
82 82 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
83 83 'edit_user_ips_delete':
84 84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
85 85
86 86 'edit_user_perms_summary':
87 87 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
88 88 'edit_user_perms_summary_json':
89 89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
90 90
91 91 'edit_user_audit_logs':
92 92 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
93 93
94 94 'edit_user_audit_logs_download':
95 95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
96 96
97 97 }[name].format(**kwargs)
98 98
99 99 if params:
100 100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
101 101 return base_url
102 102
103 103
104 104 class TestAdminUsersView(TestController):
105 105
106 106 def test_show_users(self):
107 107 self.log_user()
108 108 self.app.get(route_path('users'))
109 109
110 110 def test_show_users_data(self, xhr_header):
111 111 self.log_user()
112 112 response = self.app.get(route_path(
113 113 'users_data'), extra_environ=xhr_header)
114 114
115 115 all_users = User.query().filter(
116 116 User.username != User.DEFAULT_USER).count()
117 117 assert response.json['recordsTotal'] == all_users
118 118
119 119 def test_show_users_data_filtered(self, xhr_header):
120 120 self.log_user()
121 121 response = self.app.get(route_path(
122 122 'users_data', params={'search[value]': 'empty_search'}),
123 123 extra_environ=xhr_header)
124 124
125 125 all_users = User.query().filter(
126 126 User.username != User.DEFAULT_USER).count()
127 127 assert response.json['recordsTotal'] == all_users
128 128 assert response.json['recordsFiltered'] == 0
129 129
130 130 def test_auth_tokens_default_user(self):
131 131 self.log_user()
132 132 user = User.get_default_user()
133 133 response = self.app.get(
134 134 route_path('edit_user_auth_tokens', user_id=user.user_id),
135 135 status=302)
136 136
137 137 def test_auth_tokens(self):
138 138 self.log_user()
139 139
140 140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
141 141 user_id = user.user_id
142 142 auth_tokens = user.auth_tokens
143 143 response = self.app.get(
144 144 route_path('edit_user_auth_tokens', user_id=user_id))
145 145 for token in auth_tokens:
146 146 response.mustcontain(token)
147 147 response.mustcontain('never')
148 148
149 149 @pytest.mark.parametrize("desc, lifetime", [
150 150 ('forever', -1),
151 151 ('5mins', 60*5),
152 152 ('30days', 60*60*24*30),
153 153 ])
154 154 def test_add_auth_token(self, desc, lifetime, user_util):
155 155 self.log_user()
156 156 user = user_util.create_user()
157 157 user_id = user.user_id
158 158
159 159 response = self.app.post(
160 160 route_path('edit_user_auth_tokens_add', user_id=user_id),
161 161 {'description': desc, 'lifetime': lifetime,
162 162 'csrf_token': self.csrf_token})
163 163 assert_session_flash(response, 'Auth token successfully created')
164 164
165 165 response = response.follow()
166 166 user = User.get(user_id)
167 167 for auth_token in user.auth_tokens:
168 168 response.mustcontain(auth_token)
169 169
170 170 def test_delete_auth_token(self, user_util):
171 171 self.log_user()
172 172 user = user_util.create_user()
173 173 user_id = user.user_id
174 174 keys = user.auth_tokens
175 175 assert 2 == len(keys)
176 176
177 177 response = self.app.post(
178 178 route_path('edit_user_auth_tokens_add', user_id=user_id),
179 179 {'description': 'desc', 'lifetime': -1,
180 180 'csrf_token': self.csrf_token})
181 181 assert_session_flash(response, 'Auth token successfully created')
182 182 response.follow()
183 183
184 184 # now delete our key
185 185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
186 186 assert 3 == len(keys)
187 187
188 188 response = self.app.post(
189 189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
190 190 {'del_auth_token': keys[0].user_api_key_id,
191 191 'csrf_token': self.csrf_token})
192 192
193 193 assert_session_flash(response, 'Auth token successfully deleted')
194 194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
195 195 assert 2 == len(keys)
196 196
197 197 def test_ips(self):
198 198 self.log_user()
199 199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
200 200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
201 201 response.mustcontain('All IP addresses are allowed')
202 202
203 203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
204 204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
205 205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
206 206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
207 207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
208 208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
209 209 ('127_bad_ip', 'foobar', 'foobar', True),
210 210 ])
211 211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
212 212 self.log_user()
213 213 user = user_util.create_user(username=test_name)
214 214 user_id = user.user_id
215 215
216 216 response = self.app.post(
217 217 route_path('edit_user_ips_add', user_id=user_id),
218 218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
219 219
220 220 if failure:
221 221 assert_session_flash(
222 222 response, 'Please enter a valid IPv4 or IpV6 address')
223 223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
224 224
225 225 response.mustcontain(no=[ip])
226 226 response.mustcontain(no=[ip_range])
227 227
228 228 else:
229 229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
230 230 response.mustcontain(ip)
231 231 response.mustcontain(ip_range)
232 232
233 233 def test_ips_delete(self, user_util):
234 234 self.log_user()
235 235 user = user_util.create_user()
236 236 user_id = user.user_id
237 237 ip = '127.0.0.1/32'
238 238 ip_range = '127.0.0.1 - 127.0.0.1'
239 239 new_ip = UserModel().add_extra_ip(user_id, ip)
240 240 Session().commit()
241 241 new_ip_id = new_ip.ip_id
242 242
243 243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
244 244 response.mustcontain(ip)
245 245 response.mustcontain(ip_range)
246 246
247 247 self.app.post(
248 248 route_path('edit_user_ips_delete', user_id=user_id),
249 249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
250 250
251 251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
252 252 response.mustcontain('All IP addresses are allowed')
253 253 response.mustcontain(no=[ip])
254 254 response.mustcontain(no=[ip_range])
255 255
256 256 def test_emails(self):
257 257 self.log_user()
258 258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
259 259 response = self.app.get(
260 260 route_path('edit_user_emails', user_id=user.user_id))
261 261 response.mustcontain('No additional emails specified')
262 262
263 263 def test_emails_add(self, user_util):
264 264 self.log_user()
265 265 user = user_util.create_user()
266 266 user_id = user.user_id
267 267
268 268 self.app.post(
269 269 route_path('edit_user_emails_add', user_id=user_id),
270 270 params={'new_email': 'example@rhodecode.com',
271 271 'csrf_token': self.csrf_token})
272 272
273 273 response = self.app.get(
274 274 route_path('edit_user_emails', user_id=user_id))
275 275 response.mustcontain('example@rhodecode.com')
276 276
277 277 def test_emails_add_existing_email(self, user_util, user_regular):
278 278 existing_email = user_regular.email
279 279
280 280 self.log_user()
281 281 user = user_util.create_user()
282 282 user_id = user.user_id
283 283
284 284 response = self.app.post(
285 285 route_path('edit_user_emails_add', user_id=user_id),
286 286 params={'new_email': existing_email,
287 287 'csrf_token': self.csrf_token})
288 288 assert_session_flash(
289 289 response, 'This e-mail address is already taken')
290 290
291 291 response = self.app.get(
292 292 route_path('edit_user_emails', user_id=user_id))
293 293 response.mustcontain(no=[existing_email])
294 294
295 295 def test_emails_delete(self, user_util):
296 296 self.log_user()
297 297 user = user_util.create_user()
298 298 user_id = user.user_id
299 299
300 300 self.app.post(
301 301 route_path('edit_user_emails_add', user_id=user_id),
302 302 params={'new_email': 'example@rhodecode.com',
303 303 'csrf_token': self.csrf_token})
304 304
305 305 response = self.app.get(
306 306 route_path('edit_user_emails', user_id=user_id))
307 307 response.mustcontain('example@rhodecode.com')
308 308
309 309 user_email = UserEmailMap.query()\
310 310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
311 311 .filter(UserEmailMap.user_id == user_id)\
312 312 .one()
313 313
314 314 del_email_id = user_email.email_id
315 315 self.app.post(
316 316 route_path('edit_user_emails_delete', user_id=user_id),
317 317 params={'del_email_id': del_email_id,
318 318 'csrf_token': self.csrf_token})
319 319
320 320 response = self.app.get(
321 321 route_path('edit_user_emails', user_id=user_id))
322 322 response.mustcontain(no=['example@rhodecode.com'])
323 323
324 324 def test_create(self, request, xhr_header):
325 325 self.log_user()
326 326 username = 'newtestuser'
327 327 password = 'test12'
328 328 password_confirmation = password
329 329 name = 'name'
330 330 lastname = 'lastname'
331 331 email = 'mail@mail.com'
332 332
333 333 self.app.get(route_path('users_new'))
334 334
335 335 response = self.app.post(route_path('users_create'), params={
336 336 'username': username,
337 337 'password': password,
338 'description': 'mr CTO',
338 339 'password_confirmation': password_confirmation,
339 340 'firstname': name,
340 341 'active': True,
341 342 'lastname': lastname,
342 343 'extern_name': 'rhodecode',
343 344 'extern_type': 'rhodecode',
344 345 'email': email,
345 346 'csrf_token': self.csrf_token,
346 347 })
347 348 user_link = h.link_to(
348 349 username,
349 350 route_path(
350 351 'user_edit', user_id=User.get_by_username(username).user_id))
351 352 assert_session_flash(response, 'Created user %s' % (user_link,))
352 353
353 354 @request.addfinalizer
354 355 def cleanup():
355 356 fixture.destroy_user(username)
356 357 Session().commit()
357 358
358 359 new_user = User.query().filter(User.username == username).one()
359 360
360 361 assert new_user.username == username
361 362 assert auth.check_password(password, new_user.password)
362 363 assert new_user.name == name
363 364 assert new_user.lastname == lastname
364 365 assert new_user.email == email
365 366
366 367 response = self.app.get(route_path('users_data'),
367 368 extra_environ=xhr_header)
368 369 response.mustcontain(username)
369 370
370 371 def test_create_err(self):
371 372 self.log_user()
372 373 username = 'new_user'
373 374 password = ''
374 375 name = 'name'
375 376 lastname = 'lastname'
376 377 email = 'errmail.com'
377 378
378 379 self.app.get(route_path('users_new'))
379 380
380 381 response = self.app.post(route_path('users_create'), params={
381 382 'username': username,
382 383 'password': password,
383 384 'name': name,
384 385 'active': False,
385 386 'lastname': lastname,
387 'description': 'mr CTO',
386 388 'email': email,
387 389 'csrf_token': self.csrf_token,
388 390 })
389 391
390 392 msg = u'Username "%(username)s" is forbidden'
391 393 msg = h.html_escape(msg % {'username': 'new_user'})
392 394 response.mustcontain('<span class="error-message">%s</span>' % msg)
393 395 response.mustcontain(
394 396 '<span class="error-message">Please enter a value</span>')
395 397 response.mustcontain(
396 398 '<span class="error-message">An email address must contain a'
397 399 ' single @</span>')
398 400
399 401 def get_user():
400 402 Session().query(User).filter(User.username == username).one()
401 403
402 404 with pytest.raises(NoResultFound):
403 405 get_user()
404 406
405 407 def test_new(self):
406 408 self.log_user()
407 409 self.app.get(route_path('users_new'))
408 410
409 411 @pytest.mark.parametrize("name, attrs", [
410 412 ('firstname', {'firstname': 'new_username'}),
411 413 ('lastname', {'lastname': 'new_username'}),
412 414 ('admin', {'admin': True}),
413 415 ('admin', {'admin': False}),
414 416 ('extern_type', {'extern_type': 'ldap'}),
415 417 ('extern_type', {'extern_type': None}),
416 418 ('extern_name', {'extern_name': 'test'}),
417 419 ('extern_name', {'extern_name': None}),
418 420 ('active', {'active': False}),
419 421 ('active', {'active': True}),
420 422 ('email', {'email': 'some@email.com'}),
421 423 ('language', {'language': 'de'}),
422 424 ('language', {'language': 'en'}),
425 ('description', {'description': 'hello CTO'}),
423 426 # ('new_password', {'new_password': 'foobar123',
424 427 # 'password_confirmation': 'foobar123'})
425 428 ])
426 429 def test_update(self, name, attrs, user_util):
427 430 self.log_user()
428 431 usr = user_util.create_user(
429 432 password='qweqwe',
430 433 email='testme@rhodecode.org',
431 434 extern_type='rhodecode',
432 435 extern_name='xxx',
433 436 )
434 437 user_id = usr.user_id
435 438 Session().commit()
436 439
437 440 params = usr.get_api_data()
438 441 cur_lang = params['language'] or 'en'
439 442 params.update({
440 443 'password_confirmation': '',
441 444 'new_password': '',
442 445 'language': cur_lang,
443 446 'csrf_token': self.csrf_token,
444 447 })
445 448 params.update({'new_password': ''})
446 449 params.update(attrs)
447 450 if name == 'email':
448 451 params['emails'] = [attrs['email']]
449 452 elif name == 'extern_type':
450 453 # cannot update this via form, expected value is original one
451 454 params['extern_type'] = "rhodecode"
452 455 elif name == 'extern_name':
453 456 # cannot update this via form, expected value is original one
454 457 params['extern_name'] = 'xxx'
455 458 # special case since this user is not
456 459 # logged in yet his data is not filled
457 460 # so we use creation data
458 461
459 462 response = self.app.post(
460 463 route_path('user_update', user_id=usr.user_id), params)
461 464 assert response.status_int == 302
462 465 assert_session_flash(response, 'User updated successfully')
463 466
464 467 updated_user = User.get(user_id)
465 468 updated_params = updated_user.get_api_data()
466 469 updated_params.update({'password_confirmation': ''})
467 470 updated_params.update({'new_password': ''})
468 471
469 472 del params['csrf_token']
470 473 assert params == updated_params
471 474
472 475 def test_update_and_migrate_password(
473 476 self, autologin_user, real_crypto_backend, user_util):
474 477
475 478 user = user_util.create_user()
476 479 temp_user = user.username
477 480 user.password = auth._RhodeCodeCryptoSha256().hash_create(
478 481 b'test123')
479 482 Session().add(user)
480 483 Session().commit()
481 484
482 485 params = user.get_api_data()
483 486
484 487 params.update({
485 488 'password_confirmation': 'qweqwe123',
486 489 'new_password': 'qweqwe123',
487 490 'language': 'en',
488 491 'csrf_token': autologin_user.csrf_token,
489 492 })
490 493
491 494 response = self.app.post(
492 495 route_path('user_update', user_id=user.user_id), params)
493 496 assert response.status_int == 302
494 497 assert_session_flash(response, 'User updated successfully')
495 498
496 499 # new password should be bcrypted, after log-in and transfer
497 500 user = User.get_by_username(temp_user)
498 501 assert user.password.startswith('$')
499 502
500 503 updated_user = User.get_by_username(temp_user)
501 504 updated_params = updated_user.get_api_data()
502 505 updated_params.update({'password_confirmation': 'qweqwe123'})
503 506 updated_params.update({'new_password': 'qweqwe123'})
504 507
505 508 del params['csrf_token']
506 509 assert params == updated_params
507 510
508 511 def test_delete(self):
509 512 self.log_user()
510 513 username = 'newtestuserdeleteme'
511 514
512 515 fixture.create_user(name=username)
513 516
514 517 new_user = Session().query(User)\
515 518 .filter(User.username == username).one()
516 519 response = self.app.post(
517 520 route_path('user_delete', user_id=new_user.user_id),
518 521 params={'csrf_token': self.csrf_token})
519 522
520 523 assert_session_flash(response, 'Successfully deleted user `{}`'.format(username))
521 524
522 525 def test_delete_owner_of_repository(self, request, user_util):
523 526 self.log_user()
524 527 obj_name = 'test_repo'
525 528 usr = user_util.create_user()
526 529 username = usr.username
527 530 fixture.create_repo(obj_name, cur_user=usr.username)
528 531
529 532 new_user = Session().query(User)\
530 533 .filter(User.username == username).one()
531 534 response = self.app.post(
532 535 route_path('user_delete', user_id=new_user.user_id),
533 536 params={'csrf_token': self.csrf_token})
534 537
535 538 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
536 539 'Switch owners or remove those repositories:%s' % (username, obj_name)
537 540 assert_session_flash(response, msg)
538 541 fixture.destroy_repo(obj_name)
539 542
540 543 def test_delete_owner_of_repository_detaching(self, request, user_util):
541 544 self.log_user()
542 545 obj_name = 'test_repo'
543 546 usr = user_util.create_user(auto_cleanup=False)
544 547 username = usr.username
545 548 fixture.create_repo(obj_name, cur_user=usr.username)
546 549
547 550 new_user = Session().query(User)\
548 551 .filter(User.username == username).one()
549 552 response = self.app.post(
550 553 route_path('user_delete', user_id=new_user.user_id),
551 554 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
552 555
553 556 msg = 'Detached 1 repositories'
554 557 assert_session_flash(response, msg)
555 558 fixture.destroy_repo(obj_name)
556 559
557 560 def test_delete_owner_of_repository_deleting(self, request, user_util):
558 561 self.log_user()
559 562 obj_name = 'test_repo'
560 563 usr = user_util.create_user(auto_cleanup=False)
561 564 username = usr.username
562 565 fixture.create_repo(obj_name, cur_user=usr.username)
563 566
564 567 new_user = Session().query(User)\
565 568 .filter(User.username == username).one()
566 569 response = self.app.post(
567 570 route_path('user_delete', user_id=new_user.user_id),
568 571 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
569 572
570 573 msg = 'Deleted 1 repositories'
571 574 assert_session_flash(response, msg)
572 575
573 576 def test_delete_owner_of_repository_group(self, request, user_util):
574 577 self.log_user()
575 578 obj_name = 'test_group'
576 579 usr = user_util.create_user()
577 580 username = usr.username
578 581 fixture.create_repo_group(obj_name, cur_user=usr.username)
579 582
580 583 new_user = Session().query(User)\
581 584 .filter(User.username == username).one()
582 585 response = self.app.post(
583 586 route_path('user_delete', user_id=new_user.user_id),
584 587 params={'csrf_token': self.csrf_token})
585 588
586 589 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
587 590 'Switch owners or remove those repository groups:%s' % (username, obj_name)
588 591 assert_session_flash(response, msg)
589 592 fixture.destroy_repo_group(obj_name)
590 593
591 594 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
592 595 self.log_user()
593 596 obj_name = 'test_group'
594 597 usr = user_util.create_user(auto_cleanup=False)
595 598 username = usr.username
596 599 fixture.create_repo_group(obj_name, cur_user=usr.username)
597 600
598 601 new_user = Session().query(User)\
599 602 .filter(User.username == username).one()
600 603 response = self.app.post(
601 604 route_path('user_delete', user_id=new_user.user_id),
602 605 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
603 606
604 607 msg = 'Deleted 1 repository groups'
605 608 assert_session_flash(response, msg)
606 609
607 610 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
608 611 self.log_user()
609 612 obj_name = 'test_group'
610 613 usr = user_util.create_user(auto_cleanup=False)
611 614 username = usr.username
612 615 fixture.create_repo_group(obj_name, cur_user=usr.username)
613 616
614 617 new_user = Session().query(User)\
615 618 .filter(User.username == username).one()
616 619 response = self.app.post(
617 620 route_path('user_delete', user_id=new_user.user_id),
618 621 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
619 622
620 623 msg = 'Detached 1 repository groups'
621 624 assert_session_flash(response, msg)
622 625 fixture.destroy_repo_group(obj_name)
623 626
624 627 def test_delete_owner_of_user_group(self, request, user_util):
625 628 self.log_user()
626 629 obj_name = 'test_user_group'
627 630 usr = user_util.create_user()
628 631 username = usr.username
629 632 fixture.create_user_group(obj_name, cur_user=usr.username)
630 633
631 634 new_user = Session().query(User)\
632 635 .filter(User.username == username).one()
633 636 response = self.app.post(
634 637 route_path('user_delete', user_id=new_user.user_id),
635 638 params={'csrf_token': self.csrf_token})
636 639
637 640 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
638 641 'Switch owners or remove those user groups:%s' % (username, obj_name)
639 642 assert_session_flash(response, msg)
640 643 fixture.destroy_user_group(obj_name)
641 644
642 645 def test_delete_owner_of_user_group_detaching(self, request, user_util):
643 646 self.log_user()
644 647 obj_name = 'test_user_group'
645 648 usr = user_util.create_user(auto_cleanup=False)
646 649 username = usr.username
647 650 fixture.create_user_group(obj_name, cur_user=usr.username)
648 651
649 652 new_user = Session().query(User)\
650 653 .filter(User.username == username).one()
651 654 try:
652 655 response = self.app.post(
653 656 route_path('user_delete', user_id=new_user.user_id),
654 657 params={'user_user_groups': 'detach',
655 658 'csrf_token': self.csrf_token})
656 659
657 660 msg = 'Detached 1 user groups'
658 661 assert_session_flash(response, msg)
659 662 finally:
660 663 fixture.destroy_user_group(obj_name)
661 664
662 665 def test_delete_owner_of_user_group_deleting(self, request, user_util):
663 666 self.log_user()
664 667 obj_name = 'test_user_group'
665 668 usr = user_util.create_user(auto_cleanup=False)
666 669 username = usr.username
667 670 fixture.create_user_group(obj_name, cur_user=usr.username)
668 671
669 672 new_user = Session().query(User)\
670 673 .filter(User.username == username).one()
671 674 response = self.app.post(
672 675 route_path('user_delete', user_id=new_user.user_id),
673 676 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
674 677
675 678 msg = 'Deleted 1 user groups'
676 679 assert_session_flash(response, msg)
677 680
678 681 def test_edit(self, user_util):
679 682 self.log_user()
680 683 user = user_util.create_user()
681 684 self.app.get(route_path('user_edit', user_id=user.user_id))
682 685
683 686 def test_edit_default_user_redirect(self):
684 687 self.log_user()
685 688 user = User.get_default_user()
686 689 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
687 690
688 691 @pytest.mark.parametrize(
689 692 'repo_create, repo_create_write, user_group_create, repo_group_create,'
690 693 'fork_create, inherit_default_permissions, expect_error,'
691 694 'expect_form_error', [
692 695 ('hg.create.none', 'hg.create.write_on_repogroup.false',
693 696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
694 697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
695 698 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
696 699 'hg.usergroup.create.false', 'hg.repogroup.create.false',
697 700 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
698 701 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
699 702 'hg.usergroup.create.true', 'hg.repogroup.create.true',
700 703 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
701 704 False),
702 705 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
703 706 'hg.usergroup.create.true', 'hg.repogroup.create.true',
704 707 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
705 708 True),
706 709 ('', '', '', '', '', '', True, False),
707 710 ])
708 711 def test_global_perms_on_user(
709 712 self, repo_create, repo_create_write, user_group_create,
710 713 repo_group_create, fork_create, expect_error, expect_form_error,
711 714 inherit_default_permissions, user_util):
712 715 self.log_user()
713 716 user = user_util.create_user()
714 717 uid = user.user_id
715 718
716 719 # ENABLE REPO CREATE ON A GROUP
717 720 perm_params = {
718 721 'inherit_default_permissions': False,
719 722 'default_repo_create': repo_create,
720 723 'default_repo_create_on_write': repo_create_write,
721 724 'default_user_group_create': user_group_create,
722 725 'default_repo_group_create': repo_group_create,
723 726 'default_fork_create': fork_create,
724 727 'default_inherit_default_permissions': inherit_default_permissions,
725 728 'csrf_token': self.csrf_token,
726 729 }
727 730 response = self.app.post(
728 731 route_path('user_edit_global_perms_update', user_id=uid),
729 732 params=perm_params)
730 733
731 734 if expect_form_error:
732 735 assert response.status_int == 200
733 736 response.mustcontain('Value must be one of')
734 737 else:
735 738 if expect_error:
736 739 msg = 'An error occurred during permissions saving'
737 740 else:
738 741 msg = 'User global permissions updated successfully'
739 742 ug = User.get(uid)
740 743 del perm_params['inherit_default_permissions']
741 744 del perm_params['csrf_token']
742 745 assert perm_params == ug.get_default_perms()
743 746 assert_session_flash(response, msg)
744 747
745 748 def test_global_permissions_initial_values(self, user_util):
746 749 self.log_user()
747 750 user = user_util.create_user()
748 751 uid = user.user_id
749 752 response = self.app.get(
750 753 route_path('user_edit_global_perms', user_id=uid))
751 754 default_user = User.get_default_user()
752 755 default_permissions = default_user.get_default_perms()
753 756 assert_response = response.assert_response()
754 757 expected_permissions = (
755 758 'default_repo_create', 'default_repo_create_on_write',
756 759 'default_fork_create', 'default_repo_group_create',
757 760 'default_user_group_create', 'default_inherit_default_permissions')
758 761 for permission in expected_permissions:
759 762 css_selector = '[name={}][checked=checked]'.format(permission)
760 763 element = assert_response.get_element(css_selector)
761 764 assert element.value == default_permissions[permission]
762 765
763 766 def test_perms_summary_page(self):
764 767 user = self.log_user()
765 768 response = self.app.get(
766 769 route_path('edit_user_perms_summary', user_id=user['user_id']))
767 770 for repo in Repository.query().all():
768 771 response.mustcontain(repo.repo_name)
769 772
770 773 def test_perms_summary_page_json(self):
771 774 user = self.log_user()
772 775 response = self.app.get(
773 776 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
774 777 for repo in Repository.query().all():
775 778 response.mustcontain(repo.repo_name)
776 779
777 780 def test_audit_log_page(self):
778 781 user = self.log_user()
779 782 self.app.get(
780 783 route_path('edit_user_audit_logs', user_id=user['user_id']))
781 784
782 785 def test_audit_log_page_download(self):
783 786 user = self.log_user()
784 787 user_id = user['user_id']
785 788 response = self.app.get(
786 789 route_path('edit_user_audit_logs_download', user_id=user_id))
787 790
788 791 assert response.content_disposition == \
789 792 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
790 793 assert response.content_type == "application/json"
1 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28 import ipaddress
29 29
30 30 from pyramid.threadlocal import get_current_request
31 31 from sqlalchemy.exc import DatabaseError
32 32
33 33 from rhodecode import events
34 34 from rhodecode.lib.user_log_filter import user_log_filter
35 35 from rhodecode.lib.utils2 import (
36 36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 37 AttributeDict, str2bool)
38 38 from rhodecode.lib.exceptions import (
39 39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
41 41 from rhodecode.lib.caching_query import FromCache
42 42 from rhodecode.model import BaseModel
43 43 from rhodecode.model.auth_token import AuthTokenModel
44 44 from rhodecode.model.db import (
45 45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 46 UserEmailMap, UserIpMap, UserLog)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.repo_group import RepoGroupModel
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class UserModel(BaseModel):
55 55 cls = User
56 56
57 57 def get(self, user_id, cache=False):
58 58 user = self.sa.query(User)
59 59 if cache:
60 60 user = user.options(
61 61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 62 return user.get(user_id)
63 63
64 64 def get_user(self, user):
65 65 return self._get_user(user)
66 66
67 67 def _serialize_user(self, user):
68 68 import rhodecode.lib.helpers as h
69 69
70 70 return {
71 71 'id': user.user_id,
72 72 'first_name': user.first_name,
73 73 'last_name': user.last_name,
74 74 'username': user.username,
75 75 'email': user.email,
76 76 'icon_link': h.gravatar_url(user.email, 30),
77 77 'profile_link': h.link_to_user(user),
78 78 'value_display': h.escape(h.person(user)),
79 79 'value': user.username,
80 80 'value_type': 'user',
81 81 'active': user.active,
82 82 }
83 83
84 84 def get_users(self, name_contains=None, limit=20, only_active=True):
85 85
86 86 query = self.sa.query(User)
87 87 if only_active:
88 88 query = query.filter(User.active == true())
89 89
90 90 if name_contains:
91 91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 92 query = query.filter(
93 93 or_(
94 94 User.name.ilike(ilike_expression),
95 95 User.lastname.ilike(ilike_expression),
96 96 User.username.ilike(ilike_expression)
97 97 )
98 98 )
99 99 query = query.limit(limit)
100 100 users = query.all()
101 101
102 102 _users = [
103 103 self._serialize_user(user) for user in users
104 104 ]
105 105 return _users
106 106
107 107 def get_by_username(self, username, cache=False, case_insensitive=False):
108 108
109 109 if case_insensitive:
110 110 user = self.sa.query(User).filter(User.username.ilike(username))
111 111 else:
112 112 user = self.sa.query(User)\
113 113 .filter(User.username == username)
114 114 if cache:
115 115 name_key = _hash_key(username)
116 116 user = user.options(
117 117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 118 return user.scalar()
119 119
120 120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 121 return User.get_by_email(email, case_insensitive, cache)
122 122
123 123 def get_by_auth_token(self, auth_token, cache=False):
124 124 return User.get_by_auth_token(auth_token, cache)
125 125
126 126 def get_active_user_count(self, cache=False):
127 127 qry = User.query().filter(
128 128 User.active == true()).filter(
129 129 User.username != User.DEFAULT_USER)
130 130 if cache:
131 131 qry = qry.options(
132 132 FromCache("sql_cache_short", "get_active_users"))
133 133 return qry.count()
134 134
135 135 def create(self, form_data, cur_user=None):
136 136 if not cur_user:
137 137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138 138
139 139 user_data = {
140 140 'username': form_data['username'],
141 141 'password': form_data['password'],
142 142 'email': form_data['email'],
143 143 'firstname': form_data['firstname'],
144 144 'lastname': form_data['lastname'],
145 145 'active': form_data['active'],
146 146 'extern_type': form_data['extern_type'],
147 147 'extern_name': form_data['extern_name'],
148 148 'admin': False,
149 149 'cur_user': cur_user
150 150 }
151 151
152 152 if 'create_repo_group' in form_data:
153 153 user_data['create_repo_group'] = str2bool(
154 154 form_data.get('create_repo_group'))
155 155
156 156 try:
157 157 if form_data.get('password_change'):
158 158 user_data['force_password_change'] = True
159 159 return UserModel().create_or_update(**user_data)
160 160 except Exception:
161 161 log.error(traceback.format_exc())
162 162 raise
163 163
164 164 def update_user(self, user, skip_attrs=None, **kwargs):
165 165 from rhodecode.lib.auth import get_crypt_password
166 166
167 167 user = self._get_user(user)
168 168 if user.username == User.DEFAULT_USER:
169 169 raise DefaultUserException(
170 170 "You can't edit this user (`%(username)s`) since it's "
171 171 "crucial for entire application" % {
172 172 'username': user.username})
173 173
174 174 # first store only defaults
175 175 user_attrs = {
176 176 'updating_user_id': user.user_id,
177 177 'username': user.username,
178 178 'password': user.password,
179 179 'email': user.email,
180 180 'firstname': user.name,
181 181 'lastname': user.lastname,
182 182 'description': user.description,
183 183 'active': user.active,
184 184 'admin': user.admin,
185 185 'extern_name': user.extern_name,
186 186 'extern_type': user.extern_type,
187 187 'language': user.user_data.get('language')
188 188 }
189 189
190 190 # in case there's new_password, that comes from form, use it to
191 191 # store password
192 192 if kwargs.get('new_password'):
193 193 kwargs['password'] = kwargs['new_password']
194 194
195 195 # cleanups, my_account password change form
196 196 kwargs.pop('current_password', None)
197 197 kwargs.pop('new_password', None)
198 198
199 199 # cleanups, user edit password change form
200 200 kwargs.pop('password_confirmation', None)
201 201 kwargs.pop('password_change', None)
202 202
203 203 # create repo group on user creation
204 204 kwargs.pop('create_repo_group', None)
205 205
206 206 # legacy forms send name, which is the firstname
207 207 firstname = kwargs.pop('name', None)
208 208 if firstname:
209 209 kwargs['firstname'] = firstname
210 210
211 211 for k, v in kwargs.items():
212 212 # skip if we don't want to update this
213 213 if skip_attrs and k in skip_attrs:
214 214 continue
215 215
216 216 user_attrs[k] = v
217 217
218 218 try:
219 219 return self.create_or_update(**user_attrs)
220 220 except Exception:
221 221 log.error(traceback.format_exc())
222 222 raise
223 223
224 224 def create_or_update(
225 225 self, username, password, email, firstname='', lastname='',
226 226 active=True, admin=False, extern_type=None, extern_name=None,
227 227 cur_user=None, plugin=None, force_password_change=False,
228 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 230 strict_creation_check=True):
231 231 """
232 232 Creates a new instance if not found, or updates current one
233 233
234 234 :param username:
235 235 :param password:
236 236 :param email:
237 237 :param firstname:
238 238 :param lastname:
239 239 :param active:
240 240 :param admin:
241 241 :param extern_type:
242 242 :param extern_name:
243 243 :param cur_user:
244 244 :param plugin: optional plugin this method was called from
245 245 :param force_password_change: toggles new or existing user flag
246 246 for password change
247 247 :param allow_to_create_user: Defines if the method can actually create
248 248 new users
249 249 :param create_repo_group: Defines if the method should also
250 250 create an repo group with user name, and owner
251 251 :param updating_user_id: if we set it up this is the user we want to
252 252 update this allows to editing username.
253 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 257 :returns: new User object with injected `is_new_user` attribute.
256 258 """
257 259
258 260 if not cur_user:
259 261 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
260 262
261 263 from rhodecode.lib.auth import (
262 264 get_crypt_password, check_password, generate_auth_token)
263 265 from rhodecode.lib.hooks_base import (
264 266 log_create_user, check_allowed_create_user)
265 267
266 268 def _password_change(new_user, password):
267 269 old_password = new_user.password or ''
268 270 # empty password
269 271 if not old_password:
270 272 return False
271 273
272 274 # password check is only needed for RhodeCode internal auth calls
273 275 # in case it's a plugin we don't care
274 276 if not plugin:
275 277
276 278 # first check if we gave crypted password back, and if it
277 279 # matches it's not password change
278 280 if new_user.password == password:
279 281 return False
280 282
281 283 password_match = check_password(password, old_password)
282 284 if not password_match:
283 285 return True
284 286
285 287 return False
286 288
287 289 # read settings on default personal repo group creation
288 290 if create_repo_group is None:
289 291 default_create_repo_group = RepoGroupModel()\
290 292 .get_default_create_personal_repo_group()
291 293 create_repo_group = default_create_repo_group
292 294
293 295 user_data = {
294 296 'username': username,
295 297 'password': password,
296 298 'email': email,
297 299 'firstname': firstname,
298 300 'lastname': lastname,
299 301 'active': active,
300 302 'admin': admin
301 303 }
302 304
303 305 if updating_user_id:
304 306 log.debug('Checking for existing account in RhodeCode '
305 307 'database with user_id `%s` ', updating_user_id)
306 308 user = User.get(updating_user_id)
307 309 else:
308 310 log.debug('Checking for existing account in RhodeCode '
309 311 'database with username `%s` ', username)
310 312 user = User.get_by_username(username, case_insensitive=True)
311 313
312 314 if user is None:
313 315 # we check internal flag if this method is actually allowed to
314 316 # create new user
315 317 if not allow_to_create_user:
316 318 msg = ('Method wants to create new user, but it is not '
317 319 'allowed to do so')
318 320 log.warning(msg)
319 321 raise NotAllowedToCreateUserError(msg)
320 322
321 323 log.debug('Creating new user %s', username)
322 324
323 325 # only if we create user that is active
324 326 new_active_user = active
325 327 if new_active_user and strict_creation_check:
326 328 # raises UserCreationError if it's not allowed for any reason to
327 329 # create new active user, this also executes pre-create hooks
328 330 check_allowed_create_user(user_data, cur_user, strict_check=True)
329 331 events.trigger(events.UserPreCreate(user_data))
330 332 new_user = User()
331 333 edit = False
332 334 else:
333 335 log.debug('updating user `%s`', username)
334 336 events.trigger(events.UserPreUpdate(user, user_data))
335 337 new_user = user
336 338 edit = True
337 339
338 340 # we're not allowed to edit default user
339 341 if user.username == User.DEFAULT_USER:
340 342 raise DefaultUserException(
341 343 "You can't edit this user (`%(username)s`) since it's "
342 344 "crucial for entire application"
343 345 % {'username': user.username})
344 346
345 347 # inject special attribute that will tell us if User is new or old
346 348 new_user.is_new_user = not edit
347 349 # for users that didn's specify auth type, we use RhodeCode built in
348 350 from rhodecode.authentication.plugins import auth_rhodecode
349 351 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
350 352 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
351 353
352 354 try:
353 355 new_user.username = username
354 356 new_user.admin = admin
355 357 new_user.email = email
356 358 new_user.active = active
357 359 new_user.extern_name = safe_unicode(extern_name)
358 360 new_user.extern_type = safe_unicode(extern_type)
359 361 new_user.name = firstname
360 362 new_user.lastname = lastname
361 363 new_user.description = description
362 364
363 365 # set password only if creating an user or password is changed
364 366 if not edit or _password_change(new_user, password):
365 367 reason = 'new password' if edit else 'new user'
366 368 log.debug('Updating password reason=>%s', reason)
367 369 new_user.password = get_crypt_password(password) if password else None
368 370
369 371 if force_password_change:
370 372 new_user.update_userdata(force_password_change=True)
371 373 if language:
372 374 new_user.update_userdata(language=language)
373 375 new_user.update_userdata(notification_status=True)
374 376
375 377 self.sa.add(new_user)
376 378
377 379 if not edit and create_repo_group:
378 380 RepoGroupModel().create_personal_repo_group(
379 381 new_user, commit_early=False)
380 382
381 383 if not edit:
382 384 # add the RSS token
383 385 self.add_auth_token(
384 386 user=username, lifetime_minutes=-1,
385 387 role=self.auth_token_role.ROLE_FEED,
386 388 description=u'Generated feed token')
387 389
388 390 kwargs = new_user.get_dict()
389 391 # backward compat, require api_keys present
390 392 kwargs['api_keys'] = kwargs['auth_tokens']
391 393 log_create_user(created_by=cur_user, **kwargs)
392 394 events.trigger(events.UserPostCreate(user_data))
393 395 return new_user
394 396 except (DatabaseError,):
395 397 log.error(traceback.format_exc())
396 398 raise
397 399
398 400 def create_registration(self, form_data,
399 401 extern_name='rhodecode', extern_type='rhodecode'):
400 402 from rhodecode.model.notification import NotificationModel
401 403 from rhodecode.model.notification import EmailNotificationModel
402 404
403 405 try:
404 406 form_data['admin'] = False
405 407 form_data['extern_name'] = extern_name
406 408 form_data['extern_type'] = extern_type
407 409 new_user = self.create(form_data)
408 410
409 411 self.sa.add(new_user)
410 412 self.sa.flush()
411 413
412 414 user_data = new_user.get_dict()
413 415 kwargs = {
414 416 # use SQLALCHEMY safe dump of user data
415 417 'user': AttributeDict(user_data),
416 418 'date': datetime.datetime.now()
417 419 }
418 420 notification_type = EmailNotificationModel.TYPE_REGISTRATION
419 421 # pre-generate the subject for notification itself
420 422 (subject,
421 423 _h, _e, # we don't care about those
422 424 body_plaintext) = EmailNotificationModel().render_email(
423 425 notification_type, **kwargs)
424 426
425 427 # create notification objects, and emails
426 428 NotificationModel().create(
427 429 created_by=new_user,
428 430 notification_subject=subject,
429 431 notification_body=body_plaintext,
430 432 notification_type=notification_type,
431 433 recipients=None, # all admins
432 434 email_kwargs=kwargs,
433 435 )
434 436
435 437 return new_user
436 438 except Exception:
437 439 log.error(traceback.format_exc())
438 440 raise
439 441
440 442 def _handle_user_repos(self, username, repositories, handle_mode=None):
441 443 _superadmin = self.cls.get_first_super_admin()
442 444 left_overs = True
443 445
444 446 from rhodecode.model.repo import RepoModel
445 447
446 448 if handle_mode == 'detach':
447 449 for obj in repositories:
448 450 obj.user = _superadmin
449 451 # set description we know why we super admin now owns
450 452 # additional repositories that were orphaned !
451 453 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
452 454 self.sa.add(obj)
453 455 left_overs = False
454 456 elif handle_mode == 'delete':
455 457 for obj in repositories:
456 458 RepoModel().delete(obj, forks='detach')
457 459 left_overs = False
458 460
459 461 # if nothing is done we have left overs left
460 462 return left_overs
461 463
462 464 def _handle_user_repo_groups(self, username, repository_groups,
463 465 handle_mode=None):
464 466 _superadmin = self.cls.get_first_super_admin()
465 467 left_overs = True
466 468
467 469 from rhodecode.model.repo_group import RepoGroupModel
468 470
469 471 if handle_mode == 'detach':
470 472 for r in repository_groups:
471 473 r.user = _superadmin
472 474 # set description we know why we super admin now owns
473 475 # additional repositories that were orphaned !
474 476 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
475 477 r.personal = False
476 478 self.sa.add(r)
477 479 left_overs = False
478 480 elif handle_mode == 'delete':
479 481 for r in repository_groups:
480 482 RepoGroupModel().delete(r)
481 483 left_overs = False
482 484
483 485 # if nothing is done we have left overs left
484 486 return left_overs
485 487
486 488 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
487 489 _superadmin = self.cls.get_first_super_admin()
488 490 left_overs = True
489 491
490 492 from rhodecode.model.user_group import UserGroupModel
491 493
492 494 if handle_mode == 'detach':
493 495 for r in user_groups:
494 496 for user_user_group_to_perm in r.user_user_group_to_perm:
495 497 if user_user_group_to_perm.user.username == username:
496 498 user_user_group_to_perm.user = _superadmin
497 499 r.user = _superadmin
498 500 # set description we know why we super admin now owns
499 501 # additional repositories that were orphaned !
500 502 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
501 503 self.sa.add(r)
502 504 left_overs = False
503 505 elif handle_mode == 'delete':
504 506 for r in user_groups:
505 507 UserGroupModel().delete(r)
506 508 left_overs = False
507 509
508 510 # if nothing is done we have left overs left
509 511 return left_overs
510 512
511 513 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
512 514 _superadmin = self.cls.get_first_super_admin()
513 515 left_overs = True
514 516
515 517 if handle_mode == 'detach':
516 518 for a in artifacts:
517 519 a.upload_user = _superadmin
518 520 # set description we know why we super admin now owns
519 521 # additional artifacts that were orphaned !
520 522 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
521 523 self.sa.add(a)
522 524 left_overs = False
523 525 elif handle_mode == 'delete':
524 526 from rhodecode.apps.file_store import utils as store_utils
525 527 storage = store_utils.get_file_storage(self.request.registry.settings)
526 528 for a in artifacts:
527 529 file_uid = a.file_uid
528 530 storage.delete(file_uid)
529 531 self.sa.delete(a)
530 532
531 533 left_overs = False
532 534
533 535 # if nothing is done we have left overs left
534 536 return left_overs
535 537
536 538 def delete(self, user, cur_user=None, handle_repos=None,
537 539 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
538 540 from rhodecode.lib.hooks_base import log_delete_user
539 541
540 542 if not cur_user:
541 543 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
542 544 user = self._get_user(user)
543 545
544 546 try:
545 547 if user.username == User.DEFAULT_USER:
546 548 raise DefaultUserException(
547 549 u"You can't remove this user since it's"
548 550 u" crucial for entire application")
549 551
550 552 left_overs = self._handle_user_repos(
551 553 user.username, user.repositories, handle_repos)
552 554 if left_overs and user.repositories:
553 555 repos = [x.repo_name for x in user.repositories]
554 556 raise UserOwnsReposException(
555 557 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
556 558 u'removed. Switch owners or remove those repositories:%(list_repos)s'
557 559 % {'username': user.username, 'len_repos': len(repos),
558 560 'list_repos': ', '.join(repos)})
559 561
560 562 left_overs = self._handle_user_repo_groups(
561 563 user.username, user.repository_groups, handle_repo_groups)
562 564 if left_overs and user.repository_groups:
563 565 repo_groups = [x.group_name for x in user.repository_groups]
564 566 raise UserOwnsRepoGroupsException(
565 567 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
566 568 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
567 569 % {'username': user.username, 'len_repo_groups': len(repo_groups),
568 570 'list_repo_groups': ', '.join(repo_groups)})
569 571
570 572 left_overs = self._handle_user_user_groups(
571 573 user.username, user.user_groups, handle_user_groups)
572 574 if left_overs and user.user_groups:
573 575 user_groups = [x.users_group_name for x in user.user_groups]
574 576 raise UserOwnsUserGroupsException(
575 577 u'user "%s" still owns %s user groups and cannot be '
576 578 u'removed. Switch owners or remove those user groups:%s'
577 579 % (user.username, len(user_groups), ', '.join(user_groups)))
578 580
579 581 left_overs = self._handle_user_artifacts(
580 582 user.username, user.artifacts, handle_artifacts)
581 583 if left_overs and user.artifacts:
582 584 artifacts = [x.file_uid for x in user.artifacts]
583 585 raise UserOwnsArtifactsException(
584 586 u'user "%s" still owns %s artifacts and cannot be '
585 587 u'removed. Switch owners or remove those artifacts:%s'
586 588 % (user.username, len(artifacts), ', '.join(artifacts)))
587 589
588 590 user_data = user.get_dict() # fetch user data before expire
589 591
590 592 # we might change the user data with detach/delete, make sure
591 593 # the object is marked as expired before actually deleting !
592 594 self.sa.expire(user)
593 595 self.sa.delete(user)
594 596
595 597 log_delete_user(deleted_by=cur_user, **user_data)
596 598 except Exception:
597 599 log.error(traceback.format_exc())
598 600 raise
599 601
600 602 def reset_password_link(self, data, pwd_reset_url):
601 603 from rhodecode.lib.celerylib import tasks, run_task
602 604 from rhodecode.model.notification import EmailNotificationModel
603 605 user_email = data['email']
604 606 try:
605 607 user = User.get_by_email(user_email)
606 608 if user:
607 609 log.debug('password reset user found %s', user)
608 610
609 611 email_kwargs = {
610 612 'password_reset_url': pwd_reset_url,
611 613 'user': user,
612 614 'email': user_email,
613 615 'date': datetime.datetime.now()
614 616 }
615 617
616 618 (subject, headers, email_body,
617 619 email_body_plaintext) = EmailNotificationModel().render_email(
618 620 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
619 621
620 622 recipients = [user_email]
621 623
622 624 action_logger_generic(
623 625 'sending password reset email to user: {}'.format(
624 626 user), namespace='security.password_reset')
625 627
626 628 run_task(tasks.send_email, recipients, subject,
627 629 email_body_plaintext, email_body)
628 630
629 631 else:
630 632 log.debug("password reset email %s not found", user_email)
631 633 except Exception:
632 634 log.error(traceback.format_exc())
633 635 return False
634 636
635 637 return True
636 638
637 639 def reset_password(self, data):
638 640 from rhodecode.lib.celerylib import tasks, run_task
639 641 from rhodecode.model.notification import EmailNotificationModel
640 642 from rhodecode.lib import auth
641 643 user_email = data['email']
642 644 pre_db = True
643 645 try:
644 646 user = User.get_by_email(user_email)
645 647 new_passwd = auth.PasswordGenerator().gen_password(
646 648 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
647 649 if user:
648 650 user.password = auth.get_crypt_password(new_passwd)
649 651 # also force this user to reset his password !
650 652 user.update_userdata(force_password_change=True)
651 653
652 654 Session().add(user)
653 655
654 656 # now delete the token in question
655 657 UserApiKeys = AuthTokenModel.cls
656 658 UserApiKeys().query().filter(
657 659 UserApiKeys.api_key == data['token']).delete()
658 660
659 661 Session().commit()
660 662 log.info('successfully reset password for `%s`', user_email)
661 663
662 664 if new_passwd is None:
663 665 raise Exception('unable to generate new password')
664 666
665 667 pre_db = False
666 668
667 669 email_kwargs = {
668 670 'new_password': new_passwd,
669 671 'user': user,
670 672 'email': user_email,
671 673 'date': datetime.datetime.now()
672 674 }
673 675
674 676 (subject, headers, email_body,
675 677 email_body_plaintext) = EmailNotificationModel().render_email(
676 678 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
677 679 **email_kwargs)
678 680
679 681 recipients = [user_email]
680 682
681 683 action_logger_generic(
682 684 'sent new password to user: {} with email: {}'.format(
683 685 user, user_email), namespace='security.password_reset')
684 686
685 687 run_task(tasks.send_email, recipients, subject,
686 688 email_body_plaintext, email_body)
687 689
688 690 except Exception:
689 691 log.error('Failed to update user password')
690 692 log.error(traceback.format_exc())
691 693 if pre_db:
692 694 # we rollback only if local db stuff fails. If it goes into
693 695 # run_task, we're pass rollback state this wouldn't work then
694 696 Session().rollback()
695 697
696 698 return True
697 699
698 700 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
699 701 """
700 702 Fetches auth_user by user_id,or api_key if present.
701 703 Fills auth_user attributes with those taken from database.
702 704 Additionally set's is_authenitated if lookup fails
703 705 present in database
704 706
705 707 :param auth_user: instance of user to set attributes
706 708 :param user_id: user id to fetch by
707 709 :param api_key: api key to fetch by
708 710 :param username: username to fetch by
709 711 """
710 712 def token_obfuscate(token):
711 713 if token:
712 714 return token[:4] + "****"
713 715
714 716 if user_id is None and api_key is None and username is None:
715 717 raise Exception('You need to pass user_id, api_key or username')
716 718
717 719 log.debug(
718 720 'AuthUser: fill data execution based on: '
719 721 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
720 722 try:
721 723 dbuser = None
722 724 if user_id:
723 725 dbuser = self.get(user_id)
724 726 elif api_key:
725 727 dbuser = self.get_by_auth_token(api_key)
726 728 elif username:
727 729 dbuser = self.get_by_username(username)
728 730
729 731 if not dbuser:
730 732 log.warning(
731 733 'Unable to lookup user by id:%s api_key:%s username:%s',
732 734 user_id, token_obfuscate(api_key), username)
733 735 return False
734 736 if not dbuser.active:
735 737 log.debug('User `%s:%s` is inactive, skipping fill data',
736 738 username, user_id)
737 739 return False
738 740
739 741 log.debug('AuthUser: filling found user:%s data', dbuser)
740 742
741 743 attrs = {
742 744 'user_id': dbuser.user_id,
743 745 'username': dbuser.username,
744 746 'name': dbuser.name,
745 747 'first_name': dbuser.first_name,
746 748 'firstname': dbuser.firstname,
747 749 'last_name': dbuser.last_name,
748 750 'lastname': dbuser.lastname,
749 751 'admin': dbuser.admin,
750 752 'active': dbuser.active,
751 753
752 754 'email': dbuser.email,
753 755 'emails': dbuser.emails_cached(),
754 756 'short_contact': dbuser.short_contact,
755 757 'full_contact': dbuser.full_contact,
756 758 'full_name': dbuser.full_name,
757 759 'full_name_or_username': dbuser.full_name_or_username,
758 760
759 761 '_api_key': dbuser._api_key,
760 762 '_user_data': dbuser._user_data,
761 763
762 764 'created_on': dbuser.created_on,
763 765 'extern_name': dbuser.extern_name,
764 766 'extern_type': dbuser.extern_type,
765 767
766 768 'inherit_default_permissions': dbuser.inherit_default_permissions,
767 769
768 770 'language': dbuser.language,
769 771 'last_activity': dbuser.last_activity,
770 772 'last_login': dbuser.last_login,
771 773 'password': dbuser.password,
772 774 }
773 775 auth_user.__dict__.update(attrs)
774 776 except Exception:
775 777 log.error(traceback.format_exc())
776 778 auth_user.is_authenticated = False
777 779 return False
778 780
779 781 return True
780 782
781 783 def has_perm(self, user, perm):
782 784 perm = self._get_perm(perm)
783 785 user = self._get_user(user)
784 786
785 787 return UserToPerm.query().filter(UserToPerm.user == user)\
786 788 .filter(UserToPerm.permission == perm).scalar() is not None
787 789
788 790 def grant_perm(self, user, perm):
789 791 """
790 792 Grant user global permissions
791 793
792 794 :param user:
793 795 :param perm:
794 796 """
795 797 user = self._get_user(user)
796 798 perm = self._get_perm(perm)
797 799 # if this permission is already granted skip it
798 800 _perm = UserToPerm.query()\
799 801 .filter(UserToPerm.user == user)\
800 802 .filter(UserToPerm.permission == perm)\
801 803 .scalar()
802 804 if _perm:
803 805 return
804 806 new = UserToPerm()
805 807 new.user = user
806 808 new.permission = perm
807 809 self.sa.add(new)
808 810 return new
809 811
810 812 def revoke_perm(self, user, perm):
811 813 """
812 814 Revoke users global permissions
813 815
814 816 :param user:
815 817 :param perm:
816 818 """
817 819 user = self._get_user(user)
818 820 perm = self._get_perm(perm)
819 821
820 822 obj = UserToPerm.query()\
821 823 .filter(UserToPerm.user == user)\
822 824 .filter(UserToPerm.permission == perm)\
823 825 .scalar()
824 826 if obj:
825 827 self.sa.delete(obj)
826 828
827 829 def add_extra_email(self, user, email):
828 830 """
829 831 Adds email address to UserEmailMap
830 832
831 833 :param user:
832 834 :param email:
833 835 """
834 836
835 837 user = self._get_user(user)
836 838
837 839 obj = UserEmailMap()
838 840 obj.user = user
839 841 obj.email = email
840 842 self.sa.add(obj)
841 843 return obj
842 844
843 845 def delete_extra_email(self, user, email_id):
844 846 """
845 847 Removes email address from UserEmailMap
846 848
847 849 :param user:
848 850 :param email_id:
849 851 """
850 852 user = self._get_user(user)
851 853 obj = UserEmailMap.query().get(email_id)
852 854 if obj and obj.user_id == user.user_id:
853 855 self.sa.delete(obj)
854 856
855 857 def parse_ip_range(self, ip_range):
856 858 ip_list = []
857 859
858 860 def make_unique(value):
859 861 seen = []
860 862 return [c for c in value if not (c in seen or seen.append(c))]
861 863
862 864 # firsts split by commas
863 865 for ip_range in ip_range.split(','):
864 866 if not ip_range:
865 867 continue
866 868 ip_range = ip_range.strip()
867 869 if '-' in ip_range:
868 870 start_ip, end_ip = ip_range.split('-', 1)
869 871 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
870 872 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
871 873 parsed_ip_range = []
872 874
873 875 for index in xrange(int(start_ip), int(end_ip) + 1):
874 876 new_ip = ipaddress.ip_address(index)
875 877 parsed_ip_range.append(str(new_ip))
876 878 ip_list.extend(parsed_ip_range)
877 879 else:
878 880 ip_list.append(ip_range)
879 881
880 882 return make_unique(ip_list)
881 883
882 884 def add_extra_ip(self, user, ip, description=None):
883 885 """
884 886 Adds ip address to UserIpMap
885 887
886 888 :param user:
887 889 :param ip:
888 890 """
889 891
890 892 user = self._get_user(user)
891 893 obj = UserIpMap()
892 894 obj.user = user
893 895 obj.ip_addr = ip
894 896 obj.description = description
895 897 self.sa.add(obj)
896 898 return obj
897 899
898 900 auth_token_role = AuthTokenModel.cls
899 901
900 902 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
901 903 scope_callback=None):
902 904 """
903 905 Add AuthToken for user.
904 906
905 907 :param user: username/user_id
906 908 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
907 909 :param role: one of AuthTokenModel.cls.ROLE_*
908 910 :param description: optional string description
909 911 """
910 912
911 913 token = AuthTokenModel().create(
912 914 user, description, lifetime_minutes, role)
913 915 if scope_callback and callable(scope_callback):
914 916 # call the callback if we provide, used to attach scope for EE edition
915 917 scope_callback(token)
916 918 return token
917 919
918 920 def delete_extra_ip(self, user, ip_id):
919 921 """
920 922 Removes ip address from UserIpMap
921 923
922 924 :param user:
923 925 :param ip_id:
924 926 """
925 927 user = self._get_user(user)
926 928 obj = UserIpMap.query().get(ip_id)
927 929 if obj and obj.user_id == user.user_id:
928 930 self.sa.delete(obj)
929 931
930 932 def get_accounts_in_creation_order(self, current_user=None):
931 933 """
932 934 Get accounts in order of creation for deactivation for license limits
933 935
934 936 pick currently logged in user, and append to the list in position 0
935 937 pick all super-admins in order of creation date and add it to the list
936 938 pick all other accounts in order of creation and add it to the list.
937 939
938 940 Based on that list, the last accounts can be disabled as they are
939 941 created at the end and don't include any of the super admins as well
940 942 as the current user.
941 943
942 944 :param current_user: optionally current user running this operation
943 945 """
944 946
945 947 if not current_user:
946 948 current_user = get_current_rhodecode_user()
947 949 active_super_admins = [
948 950 x.user_id for x in User.query()
949 951 .filter(User.user_id != current_user.user_id)
950 952 .filter(User.active == true())
951 953 .filter(User.admin == true())
952 954 .order_by(User.created_on.asc())]
953 955
954 956 active_regular_users = [
955 957 x.user_id for x in User.query()
956 958 .filter(User.user_id != current_user.user_id)
957 959 .filter(User.active == true())
958 960 .filter(User.admin == false())
959 961 .order_by(User.created_on.asc())]
960 962
961 963 list_of_accounts = [current_user.user_id]
962 964 list_of_accounts += active_super_admins
963 965 list_of_accounts += active_regular_users
964 966
965 967 return list_of_accounts
966 968
967 969 def deactivate_last_users(self, expected_users, current_user=None):
968 970 """
969 971 Deactivate accounts that are over the license limits.
970 972 Algorithm of which accounts to disabled is based on the formula:
971 973
972 974 Get current user, then super admins in creation order, then regular
973 975 active users in creation order.
974 976
975 977 Using that list we mark all accounts from the end of it as inactive.
976 978 This way we block only latest created accounts.
977 979
978 980 :param expected_users: list of users in special order, we deactivate
979 981 the end N amount of users from that list
980 982 """
981 983
982 984 list_of_accounts = self.get_accounts_in_creation_order(
983 985 current_user=current_user)
984 986
985 987 for acc_id in list_of_accounts[expected_users + 1:]:
986 988 user = User.get(acc_id)
987 989 log.info('Deactivating account %s for license unlock', user)
988 990 user.active = False
989 991 Session().add(user)
990 992 Session().commit()
991 993
992 994 return
993 995
994 996 def get_user_log(self, user, filter_term):
995 997 user_log = UserLog.query()\
996 998 .filter(or_(UserLog.user_id == user.user_id,
997 999 UserLog.username == user.username))\
998 1000 .options(joinedload(UserLog.user))\
999 1001 .options(joinedload(UserLog.repository))\
1000 1002 .order_by(UserLog.action_date.desc())
1001 1003
1002 1004 user_log = user_log_filter(user_log, filter_term)
1003 1005 return user_log
@@ -1,195 +1,198 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import colander
23 23
24 24 from rhodecode import forms
25 25 from rhodecode.model.db import User, UserEmailMap
26 26 from rhodecode.model.validation_schema import types, validators
27 27 from rhodecode.translation import _
28 28 from rhodecode.lib.auth import check_password
29 29 from rhodecode.lib import helpers as h
30 30
31 31
32 32 @colander.deferred
33 33 def deferred_user_password_validator(node, kw):
34 34 username = kw.get('username')
35 35 user = User.get_by_username(username)
36 36
37 37 def _user_password_validator(node, value):
38 38 if not check_password(value, user.password):
39 39 msg = _('Password is incorrect')
40 40 raise colander.Invalid(node, msg)
41 41 return _user_password_validator
42 42
43 43
44 44
45 45 class ChangePasswordSchema(colander.Schema):
46 46
47 47 current_password = colander.SchemaNode(
48 48 colander.String(),
49 49 missing=colander.required,
50 50 widget=forms.widget.PasswordWidget(redisplay=True),
51 51 validator=deferred_user_password_validator)
52 52
53 53 new_password = colander.SchemaNode(
54 54 colander.String(),
55 55 missing=colander.required,
56 56 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
57 57 validator=colander.Length(min=6))
58 58
59 59 def validator(self, form, values):
60 60 if values['current_password'] == values['new_password']:
61 61 exc = colander.Invalid(form)
62 62 exc['new_password'] = _('New password must be different '
63 63 'to old password')
64 64 raise exc
65 65
66 66
67 67 @colander.deferred
68 68 def deferred_username_validator(node, kw):
69 69
70 70 def name_validator(node, value):
71 71 msg = _(
72 72 u'Username may only contain alphanumeric characters '
73 73 u'underscores, periods or dashes and must begin with '
74 74 u'alphanumeric character or underscore')
75 75
76 76 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
77 77 raise colander.Invalid(node, msg)
78 78
79 79 return name_validator
80 80
81 81
82 82 @colander.deferred
83 83 def deferred_email_validator(node, kw):
84 84 # NOTE(marcink): we might provide uniqueness validation later here...
85 85 return colander.Email()
86 86
87 87
88 88 class UserSchema(colander.Schema):
89 89 username = colander.SchemaNode(
90 90 colander.String(),
91 91 validator=deferred_username_validator)
92 92
93 93 email = colander.SchemaNode(
94 94 colander.String(),
95 95 validator=deferred_email_validator)
96 96
97 97 password = colander.SchemaNode(
98 98 colander.String(), missing='')
99 99
100 100 first_name = colander.SchemaNode(
101 101 colander.String(), missing='')
102 102
103 103 last_name = colander.SchemaNode(
104 104 colander.String(), missing='')
105 105
106 description = colander.SchemaNode(
107 colander.String(), missing='')
108
106 109 active = colander.SchemaNode(
107 110 types.StringBooleanType(),
108 111 missing=False)
109 112
110 113 admin = colander.SchemaNode(
111 114 types.StringBooleanType(),
112 115 missing=False)
113 116
114 117 extern_name = colander.SchemaNode(
115 118 colander.String(), missing='')
116 119
117 120 extern_type = colander.SchemaNode(
118 121 colander.String(), missing='')
119 122
120 123 def deserialize(self, cstruct):
121 124 """
122 125 Custom deserialize that allows to chain validation, and verify
123 126 permissions, and as last step uniqueness
124 127 """
125 128
126 129 appstruct = super(UserSchema, self).deserialize(cstruct)
127 130 return appstruct
128 131
129 132
130 133 @colander.deferred
131 134 def deferred_user_email_in_emails_validator(node, kw):
132 135 return colander.OneOf(kw.get('user_emails'))
133 136
134 137
135 138 @colander.deferred
136 139 def deferred_additional_email_validator(node, kw):
137 140 emails = kw.get('user_emails')
138 141
139 142 def name_validator(node, value):
140 143 if value in emails:
141 144 msg = _('This e-mail address is already taken')
142 145 raise colander.Invalid(node, msg)
143 146 user = User.get_by_email(value, case_insensitive=True)
144 147 if user:
145 148 msg = _(u'This e-mail address is already taken')
146 149 raise colander.Invalid(node, msg)
147 150 c = colander.Email()
148 151 return c(node, value)
149 152 return name_validator
150 153
151 154
152 155 @colander.deferred
153 156 def deferred_user_email_in_emails_widget(node, kw):
154 157 import deform.widget
155 158 emails = [(email, email) for email in kw.get('user_emails')]
156 159 return deform.widget.Select2Widget(values=emails)
157 160
158 161
159 162 class UserProfileSchema(colander.Schema):
160 163 username = colander.SchemaNode(
161 164 colander.String(),
162 165 validator=deferred_username_validator)
163 166
164 167 firstname = colander.SchemaNode(
165 168 colander.String(), missing='', title='First name')
166 169
167 170 lastname = colander.SchemaNode(
168 171 colander.String(), missing='', title='Last name')
169 172
170 173 description = colander.SchemaNode(
171 174 colander.String(), missing='', title='Personal Description',
172 175 widget=forms.widget.TextAreaWidget(),
173 176 validator=colander.Length(max=250)
174 177 )
175 178
176 179 email = colander.SchemaNode(
177 180 colander.String(), widget=deferred_user_email_in_emails_widget,
178 181 validator=deferred_user_email_in_emails_validator,
179 182 description=h.literal(
180 183 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
181 184 '/_admin/my_account/emails')),
182 185 )
183 186
184 187
185 188
186 189 class AddEmailSchema(colander.Schema):
187 190 current_password = colander.SchemaNode(
188 191 colander.String(),
189 192 missing=colander.required,
190 193 widget=forms.widget.PasswordWidget(redisplay=True),
191 194 validator=deferred_user_password_validator)
192 195
193 196 email = colander.SchemaNode(
194 197 colander.String(), title='New Email',
195 198 validator=deferred_additional_email_validator)
@@ -1,155 +1,161 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default user-profile">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('User Profile')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="user-profile-content">
9 9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
10 10 <% readonly = None %>
11 11 <% disabled = "" %>
12 12 %if c.extern_type != 'rhodecode':
13 13 <% readonly = "readonly" %>
14 14 <% disabled = " disabled" %>
15 15 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
16 16 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
17 17 </div>
18 18 %endif
19 19 <div class="form">
20 20 <div class="fields">
21 21 <div class="field">
22 22 <div class="label photo">
23 23 ${_('Photo')}:
24 24 </div>
25 25 <div class="input profile">
26 26 %if c.visual.use_gravatar:
27 27 ${base.gravatar(c.user.email, 100)}
28 28 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
29 29 %else:
30 30 ${base.gravatar(c.user.email, 100)}
31 31 %endif
32 32 </div>
33 33 </div>
34 34 <div class="field">
35 35 <div class="label">
36 36 ${_('Username')}:
37 37 </div>
38 38 <div class="input">
39 39 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
40 40 </div>
41 41 </div>
42 42 <div class="field">
43 43 <div class="label">
44 44 <label for="name">${_('First Name')}:</label>
45 45 </div>
46 46 <div class="input">
47 47 ${h.text('firstname', class_="medium")}
48 48 </div>
49 49 </div>
50 50
51 51 <div class="field">
52 52 <div class="label">
53 53 <label for="lastname">${_('Last Name')}:</label>
54 54 </div>
55 55 <div class="input">
56 56 ${h.text('lastname', class_="medium")}
57 57 </div>
58 58 </div>
59 59
60 60 <div class="field">
61 61 <div class="label">
62 62 <label for="email">${_('Email')}:</label>
63 63 </div>
64 64 <div class="input">
65 65 ## we should be able to edit email !
66 66 ${h.text('email', class_="medium")}
67 67 </div>
68 68 </div>
69 69 <div class="field">
70 70 <div class="label">
71 71 <label for="description">${_('Description')}:</label>
72 72 </div>
73 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 81 </div>
76 82 </div>
77 83 <div class="field">
78 84 <div class="label">
79 85 ${_('New Password')}:
80 86 </div>
81 87 <div class="input">
82 88 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
83 89 </div>
84 90 </div>
85 91 <div class="field">
86 92 <div class="label">
87 93 ${_('New Password Confirmation')}:
88 94 </div>
89 95 <div class="input">
90 96 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
91 97 </div>
92 98 </div>
93 99 <div class="field">
94 100 <div class="label-text">
95 101 ${_('Active')}:
96 102 </div>
97 103 <div class="input user-checkbox">
98 104 ${h.checkbox('active',value=True)}
99 105 </div>
100 106 </div>
101 107 <div class="field">
102 108 <div class="label-text">
103 109 ${_('Super Admin')}:
104 110 </div>
105 111 <div class="input user-checkbox">
106 112 ${h.checkbox('admin',value=True)}
107 113 </div>
108 114 </div>
109 115 <div class="field">
110 116 <div class="label-text">
111 117 ${_('Authentication type')}:
112 118 </div>
113 119 <div class="input">
114 120 ${h.select('extern_type', c.extern_type, c.allowed_extern_types)}
115 121 <p class="help-block">${_('When user was created using an external source. He is bound to authentication using this method.')}</p>
116 122 </div>
117 123 </div>
118 124 <div class="field">
119 125 <div class="label-text">
120 126 ${_('Name in Source of Record')}:
121 127 </div>
122 128 <div class="input">
123 129 <p>${c.extern_name}</p>
124 130 ${h.hidden('extern_name', readonly="readonly")}
125 131 </div>
126 132 </div>
127 133 <div class="field">
128 134 <div class="label">
129 135 ${_('Language')}:
130 136 </div>
131 137 <div class="input">
132 138 ## allowed_languages is defined in the users.py
133 139 ## c.language comes from base.py as a default language
134 140 ${h.select('language', c.language, c.allowed_languages)}
135 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 142 </div>
137 143 </div>
138 144 <div class="buttons">
139 145 ${h.submit('save', _('Save'), class_="btn")}
140 146 ${h.reset('reset', _('Reset'), class_="btn")}
141 147 </div>
142 148 </div>
143 149 </div>
144 150 ${h.end_form()}
145 151 </div>
146 152 </div>
147 153 </div>
148 154
149 155 <script>
150 156 $('#language').select2({
151 157 'containerCssClass': "drop-menu",
152 158 'dropdownCssClass': "drop-menu-dropdown",
153 159 'dropdownAutoWidth': true
154 160 });
155 161 </script>
@@ -1,415 +1,416 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helpers for fixture generation
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import tempfile
28 28 import shutil
29 29
30 30 import configobj
31 31
32 32 from rhodecode.tests import *
33 33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.model.repo_group import RepoGroupModel
38 38 from rhodecode.model.user_group import UserGroupModel
39 39 from rhodecode.model.gist import GistModel
40 40 from rhodecode.model.auth_token import AuthTokenModel
41 41 from rhodecode.authentication.plugins.auth_rhodecode import \
42 42 RhodeCodeAuthPlugin
43 43
44 44 dn = os.path.dirname
45 45 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
46 46
47 47
48 48 def error_function(*args, **kwargs):
49 49 raise Exception('Total Crash !')
50 50
51 51
52 52 class TestINI(object):
53 53 """
54 54 Allows to create a new test.ini file as a copy of existing one with edited
55 55 data. Example usage::
56 56
57 57 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
58 58 print('paster server %s' % new_test_ini)
59 59 """
60 60
61 61 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
62 62 destroy=True, dir=None):
63 63 self.ini_file_path = ini_file_path
64 64 self.ini_params = ini_params
65 65 self.new_path = None
66 66 self.new_path_prefix = new_file_prefix
67 67 self._destroy = destroy
68 68 self._dir = dir
69 69
70 70 def __enter__(self):
71 71 return self.create()
72 72
73 73 def __exit__(self, exc_type, exc_val, exc_tb):
74 74 self.destroy()
75 75
76 76 def create(self):
77 77 config = configobj.ConfigObj(
78 78 self.ini_file_path, file_error=True, write_empty_values=True)
79 79
80 80 for data in self.ini_params:
81 81 section, ini_params = data.items()[0]
82 82 for key, val in ini_params.items():
83 83 config[section][key] = val
84 84 with tempfile.NamedTemporaryFile(
85 85 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
86 86 delete=False) as new_ini_file:
87 87 config.write(new_ini_file)
88 88 self.new_path = new_ini_file.name
89 89
90 90 return self.new_path
91 91
92 92 def destroy(self):
93 93 if self._destroy:
94 94 os.remove(self.new_path)
95 95
96 96
97 97 class Fixture(object):
98 98
99 99 def anon_access(self, status):
100 100 """
101 101 Context process for disabling anonymous access. use like:
102 102 fixture = Fixture()
103 103 with fixture.anon_access(False):
104 104 #tests
105 105
106 106 after this block anon access will be set to `not status`
107 107 """
108 108
109 109 class context(object):
110 110 def __enter__(self):
111 111 anon = User.get_default_user()
112 112 anon.active = status
113 113 Session().add(anon)
114 114 Session().commit()
115 115 time.sleep(1.5) # must sleep for cache (1s to expire)
116 116
117 117 def __exit__(self, exc_type, exc_val, exc_tb):
118 118 anon = User.get_default_user()
119 119 anon.active = not status
120 120 Session().add(anon)
121 121 Session().commit()
122 122
123 123 return context()
124 124
125 125 def auth_restriction(self, auth_restriction):
126 126 """
127 127 Context process for changing the builtin rhodecode plugin auth restrictions.
128 128 Use like:
129 129 fixture = Fixture()
130 130 with fixture.auth_restriction('super_admin'):
131 131 #tests
132 132
133 133 after this block auth restriction will be taken off
134 134 """
135 135
136 136 class context(object):
137 137 def _get_pluing(self):
138 138 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
139 139 RhodeCodeAuthPlugin.uid)
140 140 plugin = RhodeCodeAuthPlugin(plugin_id)
141 141 return plugin
142 142
143 143 def __enter__(self):
144 144 plugin = self._get_pluing()
145 145 plugin.create_or_update_setting(
146 146 'auth_restriction', auth_restriction)
147 147 Session().commit()
148 148
149 149 def __exit__(self, exc_type, exc_val, exc_tb):
150 150 plugin = self._get_pluing()
151 151 plugin.create_or_update_setting(
152 152 'auth_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE)
153 153 Session().commit()
154 154
155 155 return context()
156 156
157 157 def scope_restriction(self, scope_restriction):
158 158 """
159 159 Context process for changing the builtin rhodecode plugin scope restrictions.
160 160 Use like:
161 161 fixture = Fixture()
162 162 with fixture.scope_restriction('scope_http'):
163 163 #tests
164 164
165 165 after this block scope restriction will be taken off
166 166 """
167 167
168 168 class context(object):
169 169 def _get_pluing(self):
170 170 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
171 171 RhodeCodeAuthPlugin.uid)
172 172 plugin = RhodeCodeAuthPlugin(plugin_id)
173 173 return plugin
174 174
175 175 def __enter__(self):
176 176 plugin = self._get_pluing()
177 177 plugin.create_or_update_setting(
178 178 'scope_restriction', scope_restriction)
179 179 Session().commit()
180 180
181 181 def __exit__(self, exc_type, exc_val, exc_tb):
182 182 plugin = self._get_pluing()
183 183 plugin.create_or_update_setting(
184 184 'scope_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL)
185 185 Session().commit()
186 186
187 187 return context()
188 188
189 189 def _get_repo_create_params(self, **custom):
190 190 defs = {
191 191 'repo_name': None,
192 192 'repo_type': 'hg',
193 193 'clone_uri': '',
194 194 'push_uri': '',
195 195 'repo_group': '-1',
196 196 'repo_description': 'DESC',
197 197 'repo_private': False,
198 198 'repo_landing_rev': 'rev:tip',
199 199 'repo_copy_permissions': False,
200 200 'repo_state': Repository.STATE_CREATED,
201 201 }
202 202 defs.update(custom)
203 203 if 'repo_name_full' not in custom:
204 204 defs.update({'repo_name_full': defs['repo_name']})
205 205
206 206 # fix the repo name if passed as repo_name_full
207 207 if defs['repo_name']:
208 208 defs['repo_name'] = defs['repo_name'].split('/')[-1]
209 209
210 210 return defs
211 211
212 212 def _get_group_create_params(self, **custom):
213 213 defs = {
214 214 'group_name': None,
215 215 'group_description': 'DESC',
216 216 'perm_updates': [],
217 217 'perm_additions': [],
218 218 'perm_deletions': [],
219 219 'group_parent_id': -1,
220 220 'enable_locking': False,
221 221 'recursive': False,
222 222 }
223 223 defs.update(custom)
224 224
225 225 return defs
226 226
227 227 def _get_user_create_params(self, name, **custom):
228 228 defs = {
229 229 'username': name,
230 230 'password': 'qweqwe',
231 231 'email': '%s+test@rhodecode.org' % name,
232 232 'firstname': 'TestUser',
233 233 'lastname': 'Test',
234 'description': 'test description',
234 235 'active': True,
235 236 'admin': False,
236 237 'extern_type': 'rhodecode',
237 238 'extern_name': None,
238 239 }
239 240 defs.update(custom)
240 241
241 242 return defs
242 243
243 244 def _get_user_group_create_params(self, name, **custom):
244 245 defs = {
245 246 'users_group_name': name,
246 247 'user_group_description': 'DESC',
247 248 'users_group_active': True,
248 249 'user_group_data': {},
249 250 }
250 251 defs.update(custom)
251 252
252 253 return defs
253 254
254 255 def create_repo(self, name, **kwargs):
255 256 repo_group = kwargs.get('repo_group')
256 257 if isinstance(repo_group, RepoGroup):
257 258 kwargs['repo_group'] = repo_group.group_id
258 259 name = name.split(Repository.NAME_SEP)[-1]
259 260 name = Repository.NAME_SEP.join((repo_group.group_name, name))
260 261
261 262 if 'skip_if_exists' in kwargs:
262 263 del kwargs['skip_if_exists']
263 264 r = Repository.get_by_repo_name(name)
264 265 if r:
265 266 return r
266 267
267 268 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
268 269 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
269 270 RepoModel().create(form_data, cur_user)
270 271 Session().commit()
271 272 repo = Repository.get_by_repo_name(name)
272 273 assert repo
273 274 return repo
274 275
275 276 def create_fork(self, repo_to_fork, fork_name, **kwargs):
276 277 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
277 278
278 279 form_data = self._get_repo_create_params(repo_name=fork_name,
279 280 fork_parent_id=repo_to_fork.repo_id,
280 281 repo_type=repo_to_fork.repo_type,
281 282 **kwargs)
282 283 #TODO: fix it !!
283 284 form_data['description'] = form_data['repo_description']
284 285 form_data['private'] = form_data['repo_private']
285 286 form_data['landing_rev'] = form_data['repo_landing_rev']
286 287
287 288 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
288 289 RepoModel().create_fork(form_data, cur_user=owner)
289 290 Session().commit()
290 291 r = Repository.get_by_repo_name(fork_name)
291 292 assert r
292 293 return r
293 294
294 295 def destroy_repo(self, repo_name, **kwargs):
295 296 RepoModel().delete(repo_name, pull_requests='delete', **kwargs)
296 297 Session().commit()
297 298
298 299 def destroy_repo_on_filesystem(self, repo_name):
299 300 rm_path = os.path.join(RepoModel().repos_path, repo_name)
300 301 if os.path.isdir(rm_path):
301 302 shutil.rmtree(rm_path)
302 303
303 304 def create_repo_group(self, name, **kwargs):
304 305 if 'skip_if_exists' in kwargs:
305 306 del kwargs['skip_if_exists']
306 307 gr = RepoGroup.get_by_group_name(group_name=name)
307 308 if gr:
308 309 return gr
309 310 form_data = self._get_group_create_params(group_name=name, **kwargs)
310 311 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
311 312 gr = RepoGroupModel().create(
312 313 group_name=form_data['group_name'],
313 314 group_description=form_data['group_name'],
314 315 owner=owner)
315 316 Session().commit()
316 317 gr = RepoGroup.get_by_group_name(gr.group_name)
317 318 return gr
318 319
319 320 def destroy_repo_group(self, repogroupid):
320 321 RepoGroupModel().delete(repogroupid)
321 322 Session().commit()
322 323
323 324 def create_user(self, name, **kwargs):
324 325 if 'skip_if_exists' in kwargs:
325 326 del kwargs['skip_if_exists']
326 327 user = User.get_by_username(name)
327 328 if user:
328 329 return user
329 330 form_data = self._get_user_create_params(name, **kwargs)
330 331 user = UserModel().create(form_data)
331 332
332 333 # create token for user
333 334 AuthTokenModel().create(
334 335 user=user, description=u'TEST_USER_TOKEN')
335 336
336 337 Session().commit()
337 338 user = User.get_by_username(user.username)
338 339 return user
339 340
340 341 def destroy_user(self, userid):
341 342 UserModel().delete(userid)
342 343 Session().commit()
343 344
344 345 def create_additional_user_email(self, user, email):
345 346 uem = UserEmailMap()
346 347 uem.user = user
347 348 uem.email = email
348 349 Session().add(uem)
349 350 return uem
350 351
351 352 def destroy_users(self, userid_iter):
352 353 for user_id in userid_iter:
353 354 if User.get_by_username(user_id):
354 355 UserModel().delete(user_id)
355 356 Session().commit()
356 357
357 358 def create_user_group(self, name, **kwargs):
358 359 if 'skip_if_exists' in kwargs:
359 360 del kwargs['skip_if_exists']
360 361 gr = UserGroup.get_by_group_name(group_name=name)
361 362 if gr:
362 363 return gr
363 364 # map active flag to the real attribute. For API consistency of fixtures
364 365 if 'active' in kwargs:
365 366 kwargs['users_group_active'] = kwargs['active']
366 367 del kwargs['active']
367 368 form_data = self._get_user_group_create_params(name, **kwargs)
368 369 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
369 370 user_group = UserGroupModel().create(
370 371 name=form_data['users_group_name'],
371 372 description=form_data['user_group_description'],
372 373 owner=owner, active=form_data['users_group_active'],
373 374 group_data=form_data['user_group_data'])
374 375 Session().commit()
375 376 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
376 377 return user_group
377 378
378 379 def destroy_user_group(self, usergroupid):
379 380 UserGroupModel().delete(user_group=usergroupid, force=True)
380 381 Session().commit()
381 382
382 383 def create_gist(self, **kwargs):
383 384 form_data = {
384 385 'description': 'new-gist',
385 386 'owner': TEST_USER_ADMIN_LOGIN,
386 387 'gist_type': GistModel.cls.GIST_PUBLIC,
387 388 'lifetime': -1,
388 389 'acl_level': Gist.ACL_LEVEL_PUBLIC,
389 390 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
390 391 }
391 392 form_data.update(kwargs)
392 393 gist = GistModel().create(
393 394 description=form_data['description'], owner=form_data['owner'],
394 395 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
395 396 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
396 397 )
397 398 Session().commit()
398 399 return gist
399 400
400 401 def destroy_gists(self, gistid=None):
401 402 for g in GistModel.cls.get_all():
402 403 if gistid:
403 404 if gistid == g.gist_access_id:
404 405 GistModel().delete(g)
405 406 else:
406 407 GistModel().delete(g)
407 408 Session().commit()
408 409
409 410 def load_resource(self, resource_name, strip=False):
410 411 with open(os.path.join(FIXTURES, resource_name)) as f:
411 412 source = f.read()
412 413 if strip:
413 414 source = source.strip()
414 415
415 416 return source
General Comments 0
You need to be logged in to leave comments. Login now