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