##// END OF EJS Templates
settings: fix #3944 add password reset permission
lisaq -
r1034:d1b70f85 default
parent child Browse files
Show More
@@ -0,0 +1,42 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
9
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 from rhodecode.lib.utils2 import str2bool
13
14 from rhodecode.model.meta import Base
15 from rhodecode.model import meta
16 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
17
18 log = logging.getLogger(__name__)
19
20
21 def upgrade(migrate_engine):
22 """
23 Upgrade operations go here.
24 Don't create your own engine; bind migrate_engine to your metadata
25 """
26 _reset_base(migrate_engine)
27 from rhodecode.lib.dbmigrate.schema import db_4_5_0_0
28
29 fixups(db_4_5_0_0, meta.Session)
30
31 def downgrade(migrate_engine):
32 meta = MetaData()
33 meta.bind = migrate_engine
34
35 def fixups(models, _SESSION):
36 # ** create default permissions ** #
37 from rhodecode.model.permission import PermissionModel
38 PermissionModel(_SESSION()).create_permissions()
39
40 res = PermissionModel(_SESSION()).create_default_user_permissions(
41 models.User.DEFAULT_USER)
42 _SESSION().commit()
@@ -1,63 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22
23 23 RhodeCode, a web based repository management software
24 24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 25 """
26 26
27 27 import os
28 28 import sys
29 29 import platform
30 30
31 31 VERSION = tuple(open(os.path.join(
32 32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33 33
34 34 BACKENDS = {
35 35 'hg': 'Mercurial repository',
36 36 'git': 'Git repository',
37 37 'svn': 'Subversion repository',
38 38 }
39 39
40 40 CELERY_ENABLED = False
41 41 CELERY_EAGER = False
42 42
43 43 # link to config for pylons
44 44 CONFIG = {}
45 45
46 46 # Populated with the settings dictionary from application init in
47 47 # rhodecode.conf.environment.load_pyramid_environment
48 48 PYRAMID_SETTINGS = {}
49 49
50 50 # Linked module for extensions
51 51 EXTENSIONS = {}
52 52
53 53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 60 # defines current db version for migrations
54 __dbversion__ = 61 # defines current db version for migrations
55 55 __platform__ = platform.system()
56 56 __license__ = 'AGPLv3, and Commercial License'
57 57 __author__ = 'RhodeCode GmbH'
58 58 __url__ = 'http://rhodecode.com'
59 59
60 60 is_windows = __platform__ in ['Windows']
61 61 is_unix = not is_windows
62 62 is_test = False
63 63 disable_error_handler = False
@@ -1,465 +1,466 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
24 24 from rhodecode.api.utils import (
25 25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
26 26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 27 from rhodecode.lib.exceptions import DefaultUserException
28 28 from rhodecode.lib.utils2 import safe_int
29 29 from rhodecode.model.db import Session, User, Repository
30 30 from rhodecode.model.user import UserModel
31 31
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 @jsonrpc_method()
37 37 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
38 38 """
39 39 Returns the information associated with a username or userid.
40 40
41 41 * If the ``userid`` is not set, this command returns the information
42 42 for the ``userid`` calling the method.
43 43
44 44 .. note::
45 45
46 46 Normal users may only run this command against their ``userid``. For
47 47 full privileges you must run this command using an |authtoken| with
48 48 admin rights.
49 49
50 50 :param apiuser: This is filled automatically from the |authtoken|.
51 51 :type apiuser: AuthUser
52 52 :param userid: Sets the userid for which data will be returned.
53 53 :type userid: Optional(str or int)
54 54
55 55 Example output:
56 56
57 57 .. code-block:: bash
58 58
59 59 {
60 60 "error": null,
61 61 "id": <id>,
62 62 "result": {
63 63 "active": true,
64 64 "admin": false,
65 65 "api_key": "api-key",
66 66 "api_keys": [ list of keys ],
67 67 "email": "user@example.com",
68 68 "emails": [
69 69 "user@example.com"
70 70 ],
71 71 "extern_name": "rhodecode",
72 72 "extern_type": "rhodecode",
73 73 "firstname": "username",
74 74 "ip_addresses": [],
75 75 "language": null,
76 76 "last_login": "Timestamp",
77 77 "lastname": "surnae",
78 78 "permissions": {
79 79 "global": [
80 80 "hg.inherit_default_perms.true",
81 81 "usergroup.read",
82 82 "hg.repogroup.create.false",
83 83 "hg.create.none",
84 "hg.password_reset.enabled",
84 85 "hg.extern_activate.manual",
85 86 "hg.create.write_on_repogroup.false",
86 87 "hg.usergroup.create.false",
87 88 "group.none",
88 89 "repository.none",
89 90 "hg.register.none",
90 91 "hg.fork.repository"
91 92 ],
92 93 "repositories": { "username/example": "repository.write"},
93 94 "repositories_groups": { "user-group/repo": "group.none" },
94 95 "user_groups": { "user_group_name": "usergroup.read" }
95 96 },
96 97 "user_id": 32,
97 98 "username": "username"
98 99 }
99 100 }
100 101 """
101 102
102 103 if not has_superadmin_permission(apiuser):
103 104 # make sure normal user does not pass someone else userid,
104 105 # he is not allowed to do that
105 106 if not isinstance(userid, Optional) and userid != apiuser.user_id:
106 107 raise JSONRPCError('userid is not the same as your user')
107 108
108 109 userid = Optional.extract(userid, evaluate_locals=locals())
109 110 userid = getattr(userid, 'user_id', userid)
110 111
111 112 user = get_user_or_error(userid)
112 113 data = user.get_api_data(include_secrets=True)
113 114 data['permissions'] = AuthUser(user_id=user.user_id).permissions
114 115 return data
115 116
116 117
117 118 @jsonrpc_method()
118 119 def get_users(request, apiuser):
119 120 """
120 121 Lists all users in the |RCE| user database.
121 122
122 123 This command can only be run using an |authtoken| with admin rights to
123 124 the specified repository.
124 125
125 126 This command takes the following options:
126 127
127 128 :param apiuser: This is filled automatically from the |authtoken|.
128 129 :type apiuser: AuthUser
129 130
130 131 Example output:
131 132
132 133 .. code-block:: bash
133 134
134 135 id : <id_given_in_input>
135 136 result: [<user_object>, ...]
136 137 error: null
137 138 """
138 139
139 140 if not has_superadmin_permission(apiuser):
140 141 raise JSONRPCForbidden()
141 142
142 143 result = []
143 144 users_list = User.query().order_by(User.username) \
144 145 .filter(User.username != User.DEFAULT_USER) \
145 146 .all()
146 147 for user in users_list:
147 148 result.append(user.get_api_data(include_secrets=True))
148 149 return result
149 150
150 151
151 152 @jsonrpc_method()
152 153 def create_user(request, apiuser, username, email, password=Optional(''),
153 154 firstname=Optional(''), lastname=Optional(''),
154 155 active=Optional(True), admin=Optional(False),
155 156 extern_name=Optional('rhodecode'),
156 157 extern_type=Optional('rhodecode'),
157 158 force_password_change=Optional(False)):
158 159 """
159 160 Creates a new user and returns the new user object.
160 161
161 162 This command can only be run using an |authtoken| with admin rights to
162 163 the specified repository.
163 164
164 165 This command takes the following options:
165 166
166 167 :param apiuser: This is filled automatically from the |authtoken|.
167 168 :type apiuser: AuthUser
168 169 :param username: Set the new username.
169 170 :type username: str or int
170 171 :param email: Set the user email address.
171 172 :type email: str
172 173 :param password: Set the new user password.
173 174 :type password: Optional(str)
174 175 :param firstname: Set the new user firstname.
175 176 :type firstname: Optional(str)
176 177 :param lastname: Set the new user surname.
177 178 :type lastname: Optional(str)
178 179 :param active: Set the user as active.
179 180 :type active: Optional(``True`` | ``False``)
180 181 :param admin: Give the new user admin rights.
181 182 :type admin: Optional(``True`` | ``False``)
182 183 :param extern_name: Set the authentication plugin name.
183 184 Using LDAP this is filled with LDAP UID.
184 185 :type extern_name: Optional(str)
185 186 :param extern_type: Set the new user authentication plugin.
186 187 :type extern_type: Optional(str)
187 188 :param force_password_change: Force the new user to change password
188 189 on next login.
189 190 :type force_password_change: Optional(``True`` | ``False``)
190 191
191 192 Example output:
192 193
193 194 .. code-block:: bash
194 195
195 196 id : <id_given_in_input>
196 197 result: {
197 198 "msg" : "created new user `<username>`",
198 199 "user": <user_obj>
199 200 }
200 201 error: null
201 202
202 203 Example error output:
203 204
204 205 .. code-block:: bash
205 206
206 207 id : <id_given_in_input>
207 208 result : null
208 209 error : {
209 210 "user `<username>` already exist"
210 211 or
211 212 "email `<email>` already exist"
212 213 or
213 214 "failed to create user `<username>`"
214 215 }
215 216
216 217 """
217 218 if not has_superadmin_permission(apiuser):
218 219 raise JSONRPCForbidden()
219 220
220 221 if UserModel().get_by_username(username):
221 222 raise JSONRPCError("user `%s` already exist" % (username,))
222 223
223 224 if UserModel().get_by_email(email, case_insensitive=True):
224 225 raise JSONRPCError("email `%s` already exist" % (email,))
225 226
226 227 # generate random password if we actually given the
227 228 # extern_name and it's not rhodecode
228 229 if (not isinstance(extern_name, Optional) and
229 230 Optional.extract(extern_name) != 'rhodecode'):
230 231 # generate temporary password if user is external
231 232 password = PasswordGenerator().gen_password(length=16)
232 233
233 234 try:
234 235 user = UserModel().create_or_update(
235 236 username=Optional.extract(username),
236 237 password=Optional.extract(password),
237 238 email=Optional.extract(email),
238 239 firstname=Optional.extract(firstname),
239 240 lastname=Optional.extract(lastname),
240 241 active=Optional.extract(active),
241 242 admin=Optional.extract(admin),
242 243 extern_type=Optional.extract(extern_type),
243 244 extern_name=Optional.extract(extern_name),
244 245 force_password_change=Optional.extract(force_password_change),
245 246 )
246 247 Session().commit()
247 248 return {
248 249 'msg': 'created new user `%s`' % username,
249 250 'user': user.get_api_data(include_secrets=True)
250 251 }
251 252 except Exception:
252 253 log.exception('Error occurred during creation of user')
253 254 raise JSONRPCError('failed to create user `%s`' % (username,))
254 255
255 256
256 257 @jsonrpc_method()
257 258 def update_user(request, apiuser, userid, username=Optional(None),
258 259 email=Optional(None), password=Optional(None),
259 260 firstname=Optional(None), lastname=Optional(None),
260 261 active=Optional(None), admin=Optional(None),
261 262 extern_type=Optional(None), extern_name=Optional(None), ):
262 263 """
263 264 Updates the details for the specified user, if that user exists.
264 265
265 266 This command can only be run using an |authtoken| with admin rights to
266 267 the specified repository.
267 268
268 269 This command takes the following options:
269 270
270 271 :param apiuser: This is filled automatically from |authtoken|.
271 272 :type apiuser: AuthUser
272 273 :param userid: Set the ``userid`` to update.
273 274 :type userid: str or int
274 275 :param username: Set the new username.
275 276 :type username: str or int
276 277 :param email: Set the new email.
277 278 :type email: str
278 279 :param password: Set the new password.
279 280 :type password: Optional(str)
280 281 :param firstname: Set the new first name.
281 282 :type firstname: Optional(str)
282 283 :param lastname: Set the new surname.
283 284 :type lastname: Optional(str)
284 285 :param active: Set the new user as active.
285 286 :type active: Optional(``True`` | ``False``)
286 287 :param admin: Give the user admin rights.
287 288 :type admin: Optional(``True`` | ``False``)
288 289 :param extern_name: Set the authentication plugin user name.
289 290 Using LDAP this is filled with LDAP UID.
290 291 :type extern_name: Optional(str)
291 292 :param extern_type: Set the authentication plugin type.
292 293 :type extern_type: Optional(str)
293 294
294 295
295 296 Example output:
296 297
297 298 .. code-block:: bash
298 299
299 300 id : <id_given_in_input>
300 301 result: {
301 302 "msg" : "updated user ID:<userid> <username>",
302 303 "user": <user_object>,
303 304 }
304 305 error: null
305 306
306 307 Example error output:
307 308
308 309 .. code-block:: bash
309 310
310 311 id : <id_given_in_input>
311 312 result : null
312 313 error : {
313 314 "failed to update user `<username>`"
314 315 }
315 316
316 317 """
317 318 if not has_superadmin_permission(apiuser):
318 319 raise JSONRPCForbidden()
319 320
320 321 user = get_user_or_error(userid)
321 322
322 323 # only non optional arguments will be stored in updates
323 324 updates = {}
324 325
325 326 try:
326 327
327 328 store_update(updates, username, 'username')
328 329 store_update(updates, password, 'password')
329 330 store_update(updates, email, 'email')
330 331 store_update(updates, firstname, 'name')
331 332 store_update(updates, lastname, 'lastname')
332 333 store_update(updates, active, 'active')
333 334 store_update(updates, admin, 'admin')
334 335 store_update(updates, extern_name, 'extern_name')
335 336 store_update(updates, extern_type, 'extern_type')
336 337
337 338 user = UserModel().update_user(user, **updates)
338 339 Session().commit()
339 340 return {
340 341 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
341 342 'user': user.get_api_data(include_secrets=True)
342 343 }
343 344 except DefaultUserException:
344 345 log.exception("Default user edit exception")
345 346 raise JSONRPCError('editing default user is forbidden')
346 347 except Exception:
347 348 log.exception("Error occurred during update of user")
348 349 raise JSONRPCError('failed to update user `%s`' % (userid,))
349 350
350 351
351 352 @jsonrpc_method()
352 353 def delete_user(request, apiuser, userid):
353 354 """
354 355 Deletes the specified user from the |RCE| user database.
355 356
356 357 This command can only be run using an |authtoken| with admin rights to
357 358 the specified repository.
358 359
359 360 .. important::
360 361
361 362 Ensure all open pull requests and open code review
362 363 requests to this user are close.
363 364
364 365 Also ensure all repositories, or repository groups owned by this
365 366 user are reassigned before deletion.
366 367
367 368 This command takes the following options:
368 369
369 370 :param apiuser: This is filled automatically from the |authtoken|.
370 371 :type apiuser: AuthUser
371 372 :param userid: Set the user to delete.
372 373 :type userid: str or int
373 374
374 375 Example output:
375 376
376 377 .. code-block:: bash
377 378
378 379 id : <id_given_in_input>
379 380 result: {
380 381 "msg" : "deleted user ID:<userid> <username>",
381 382 "user": null
382 383 }
383 384 error: null
384 385
385 386 Example error output:
386 387
387 388 .. code-block:: bash
388 389
389 390 id : <id_given_in_input>
390 391 result : null
391 392 error : {
392 393 "failed to delete user ID:<userid> <username>"
393 394 }
394 395
395 396 """
396 397 if not has_superadmin_permission(apiuser):
397 398 raise JSONRPCForbidden()
398 399
399 400 user = get_user_or_error(userid)
400 401
401 402 try:
402 403 UserModel().delete(userid)
403 404 Session().commit()
404 405 return {
405 406 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
406 407 'user': None
407 408 }
408 409 except Exception:
409 410 log.exception("Error occurred during deleting of user")
410 411 raise JSONRPCError(
411 412 'failed to delete user ID:%s %s' % (user.user_id, user.username))
412 413
413 414
414 415 @jsonrpc_method()
415 416 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
416 417 """
417 418 Displays all repositories locked by the specified user.
418 419
419 420 * If this command is run by a non-admin user, it returns
420 421 a list of |repos| locked by that user.
421 422
422 423 This command takes the following options:
423 424
424 425 :param apiuser: This is filled automatically from the |authtoken|.
425 426 :type apiuser: AuthUser
426 427 :param userid: Sets the userid whose list of locked |repos| will be
427 428 displayed.
428 429 :type userid: Optional(str or int)
429 430
430 431 Example output:
431 432
432 433 .. code-block:: bash
433 434
434 435 id : <id_given_in_input>
435 436 result : {
436 437 [repo_object, repo_object,...]
437 438 }
438 439 error : null
439 440 """
440 441
441 442 include_secrets = False
442 443 if not has_superadmin_permission(apiuser):
443 444 # make sure normal user does not pass someone else userid,
444 445 # he is not allowed to do that
445 446 if not isinstance(userid, Optional) and userid != apiuser.user_id:
446 447 raise JSONRPCError('userid is not the same as your user')
447 448 else:
448 449 include_secrets = True
449 450
450 451 userid = Optional.extract(userid, evaluate_locals=locals())
451 452 userid = getattr(userid, 'user_id', userid)
452 453 user = get_user_or_error(userid)
453 454
454 455 ret = []
455 456
456 457 # show all locks
457 458 for r in Repository.getAll():
458 459 _user_id, _time, _reason = r.locked
459 460 if _user_id and _time:
460 461 _api_data = r.get_api_data(include_secrets=include_secrets)
461 462 # if we use user filter just show the locks for this user
462 463 if safe_int(_user_id) == user.user_id:
463 464 ret.append(_api_data)
464 465
465 466 return ret
@@ -1,248 +1,249 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 permissions controller for RhodeCode Enterprise
24 24 """
25 25
26 26
27 27 import logging
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.model.db import User, UserIpMap
40 40 from rhodecode.model.forms import (
41 41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
42 42 from rhodecode.model.meta import Session
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.settings import SettingsModel
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class PermissionsController(BaseController):
50 50 """REST Controller styled on the Atom Publishing Protocol"""
51 51 # To properly map this controller, ensure your config/routing.py
52 52 # file has a resource setup:
53 53 # map.resource('permission', 'permissions')
54 54
55 55 @LoginRequired()
56 56 def __before__(self):
57 57 super(PermissionsController, self).__before__()
58 58
59 59 def __load_data(self):
60 60 PermissionModel().set_global_permission_choices(c, translator=_)
61 61
62 62 @HasPermissionAllDecorator('hg.admin')
63 63 def permission_application(self):
64 64 c.active = 'application'
65 65 self.__load_data()
66 66
67 67 c.user = User.get_default_user()
68 68
69 69 # TODO: johbo: The default user might be based on outdated state which
70 70 # has been loaded from the cache. A call to refresh() ensures that the
71 71 # latest state from the database is used.
72 72 Session().refresh(c.user)
73 73
74 74 app_settings = SettingsModel().get_all_settings()
75 75 defaults = {
76 76 'anonymous': c.user.active,
77 77 'default_register_message': app_settings.get(
78 78 'rhodecode_register_message')
79 79 }
80 80 defaults.update(c.user.get_default_perms())
81 81
82 82 return htmlfill.render(
83 83 render('admin/permissions/permissions.html'),
84 84 defaults=defaults,
85 85 encoding="UTF-8",
86 86 force_defaults=False)
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 @auth.CSRFRequired()
90 90 def permission_application_update(self):
91 91 c.active = 'application'
92 92 self.__load_data()
93 93 _form = ApplicationPermissionsForm(
94 94 [x[0] for x in c.register_choices],
95 [x[0] for x in c.password_reset_choices],
95 96 [x[0] for x in c.extern_activate_choices])()
96 97
97 98 try:
98 99 form_result = _form.to_python(dict(request.POST))
99 100 form_result.update({'perm_user_name': User.DEFAULT_USER})
100 101 PermissionModel().update_application_permissions(form_result)
101 102
102 103 settings = [
103 104 ('register_message', 'default_register_message'),
104 105 ]
105 106 for setting, form_key in settings:
106 107 sett = SettingsModel().create_or_update_setting(
107 108 setting, form_result[form_key])
108 109 Session().add(sett)
109 110
110 111 Session().commit()
111 112 h.flash(_('Application permissions updated successfully'),
112 113 category='success')
113 114
114 115 except formencode.Invalid as errors:
115 116 defaults = errors.value
116 117
117 118 return htmlfill.render(
118 119 render('admin/permissions/permissions.html'),
119 120 defaults=defaults,
120 121 errors=errors.error_dict or {},
121 122 prefix_error=False,
122 123 encoding="UTF-8",
123 124 force_defaults=False)
124 125 except Exception:
125 126 log.exception("Exception during update of permissions")
126 127 h.flash(_('Error occurred during update of permissions'),
127 128 category='error')
128 129
129 130 return redirect(url('admin_permissions_application'))
130 131
131 132 @HasPermissionAllDecorator('hg.admin')
132 133 def permission_objects(self):
133 134 c.active = 'objects'
134 135 self.__load_data()
135 136 c.user = User.get_default_user()
136 137 defaults = {}
137 138 defaults.update(c.user.get_default_perms())
138 139 return htmlfill.render(
139 140 render('admin/permissions/permissions.html'),
140 141 defaults=defaults,
141 142 encoding="UTF-8",
142 143 force_defaults=False)
143 144
144 145 @HasPermissionAllDecorator('hg.admin')
145 146 @auth.CSRFRequired()
146 147 def permission_objects_update(self):
147 148 c.active = 'objects'
148 149 self.__load_data()
149 150 _form = ObjectPermissionsForm(
150 151 [x[0] for x in c.repo_perms_choices],
151 152 [x[0] for x in c.group_perms_choices],
152 153 [x[0] for x in c.user_group_perms_choices])()
153 154
154 155 try:
155 156 form_result = _form.to_python(dict(request.POST))
156 157 form_result.update({'perm_user_name': User.DEFAULT_USER})
157 158 PermissionModel().update_object_permissions(form_result)
158 159
159 160 Session().commit()
160 161 h.flash(_('Object permissions updated successfully'),
161 162 category='success')
162 163
163 164 except formencode.Invalid as errors:
164 165 defaults = errors.value
165 166
166 167 return htmlfill.render(
167 168 render('admin/permissions/permissions.html'),
168 169 defaults=defaults,
169 170 errors=errors.error_dict or {},
170 171 prefix_error=False,
171 172 encoding="UTF-8",
172 173 force_defaults=False)
173 174 except Exception:
174 175 log.exception("Exception during update of permissions")
175 176 h.flash(_('Error occurred during update of permissions'),
176 177 category='error')
177 178
178 179 return redirect(url('admin_permissions_object'))
179 180
180 181 @HasPermissionAllDecorator('hg.admin')
181 182 def permission_global(self):
182 183 c.active = 'global'
183 184 self.__load_data()
184 185
185 186 c.user = User.get_default_user()
186 187 defaults = {}
187 188 defaults.update(c.user.get_default_perms())
188 189
189 190 return htmlfill.render(
190 191 render('admin/permissions/permissions.html'),
191 192 defaults=defaults,
192 193 encoding="UTF-8",
193 194 force_defaults=False)
194 195
195 196 @HasPermissionAllDecorator('hg.admin')
196 197 @auth.CSRFRequired()
197 198 def permission_global_update(self):
198 199 c.active = 'global'
199 200 self.__load_data()
200 201 _form = UserPermissionsForm(
201 202 [x[0] for x in c.repo_create_choices],
202 203 [x[0] for x in c.repo_create_on_write_choices],
203 204 [x[0] for x in c.repo_group_create_choices],
204 205 [x[0] for x in c.user_group_create_choices],
205 206 [x[0] for x in c.fork_choices],
206 207 [x[0] for x in c.inherit_default_permission_choices])()
207 208
208 209 try:
209 210 form_result = _form.to_python(dict(request.POST))
210 211 form_result.update({'perm_user_name': User.DEFAULT_USER})
211 212 PermissionModel().update_user_permissions(form_result)
212 213
213 214 Session().commit()
214 215 h.flash(_('Global permissions updated successfully'),
215 216 category='success')
216 217
217 218 except formencode.Invalid as errors:
218 219 defaults = errors.value
219 220
220 221 return htmlfill.render(
221 222 render('admin/permissions/permissions.html'),
222 223 defaults=defaults,
223 224 errors=errors.error_dict or {},
224 225 prefix_error=False,
225 226 encoding="UTF-8",
226 227 force_defaults=False)
227 228 except Exception:
228 229 log.exception("Exception during update of permissions")
229 230 h.flash(_('Error occurred during update of permissions'),
230 231 category='error')
231 232
232 233 return redirect(url('admin_permissions_global'))
233 234
234 235 @HasPermissionAllDecorator('hg.admin')
235 236 def permission_ips(self):
236 237 c.active = 'ips'
237 238 c.user = User.get_default_user()
238 239 c.user_ip_map = (
239 240 UserIpMap.query().filter(UserIpMap.user == c.user).all())
240 241
241 242 return render('admin/permissions/permissions.html')
242 243
243 244 @HasPermissionAllDecorator('hg.admin')
244 245 def permission_perms(self):
245 246 c.active = 'perms'
246 247 c.user = User.get_default_user()
247 248 c.perm_user = c.user.AuthUser
248 249 return render('admin/permissions/permissions.html')
@@ -1,3658 +1,3663 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import re
26 26 import os
27 27 import time
28 28 import hashlib
29 29 import logging
30 30 import datetime
31 31 import warnings
32 32 import ipaddress
33 33 import functools
34 34 import traceback
35 35 import collections
36 36
37 37
38 38 from sqlalchemy import *
39 39 from sqlalchemy.ext.declarative import declared_attr
40 40 from sqlalchemy.ext.hybrid import hybrid_property
41 41 from sqlalchemy.orm import (
42 42 relationship, joinedload, class_mapper, validates, aliased)
43 43 from sqlalchemy.sql.expression import true
44 44 from beaker.cache import cache_region
45 45 from webob.exc import HTTPNotFound
46 46 from zope.cachedescriptors.property import Lazy as LazyProperty
47 47
48 48 from pylons import url
49 49 from pylons.i18n.translation import lazy_ugettext as _
50 50
51 51 from rhodecode.lib.vcs import get_vcs_instance
52 52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 53 from rhodecode.lib.utils2 import (
54 54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 56 glob2re)
57 57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 58 from rhodecode.lib.ext_json import json
59 59 from rhodecode.lib.caching_query import FromCache
60 60 from rhodecode.lib.encrypt import AESCipher
61 61
62 62 from rhodecode.model.meta import Base, Session
63 63
64 64 URL_SEP = '/'
65 65 log = logging.getLogger(__name__)
66 66
67 67 # =============================================================================
68 68 # BASE CLASSES
69 69 # =============================================================================
70 70
71 71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 72 # beaker.session.secret if first is not set.
73 73 # and initialized at environment.py
74 74 ENCRYPTION_KEY = None
75 75
76 76 # used to sort permissions by types, '#' used here is not allowed to be in
77 77 # usernames, and it's very early in sorted string.printable table.
78 78 PERMISSION_TYPE_SORT = {
79 79 'admin': '####',
80 80 'write': '###',
81 81 'read': '##',
82 82 'none': '#',
83 83 }
84 84
85 85
86 86 def display_sort(obj):
87 87 """
88 88 Sort function used to sort permissions in .permissions() function of
89 89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 90 of all other resources
91 91 """
92 92
93 93 if obj.username == User.DEFAULT_USER:
94 94 return '#####'
95 95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 96 return prefix + obj.username
97 97
98 98
99 99 def _hash_key(k):
100 100 return md5_safe(k)
101 101
102 102
103 103 class EncryptedTextValue(TypeDecorator):
104 104 """
105 105 Special column for encrypted long text data, use like::
106 106
107 107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108 108
109 109 This column is intelligent so if value is in unencrypted form it return
110 110 unencrypted form, but on save it always encrypts
111 111 """
112 112 impl = Text
113 113
114 114 def process_bind_param(self, value, dialect):
115 115 if not value:
116 116 return value
117 117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 118 # protect against double encrypting if someone manually starts
119 119 # doing
120 120 raise ValueError('value needs to be in unencrypted format, ie. '
121 121 'not starting with enc$aes')
122 122 return 'enc$aes_hmac$%s' % AESCipher(
123 123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124 124
125 125 def process_result_value(self, value, dialect):
126 126 import rhodecode
127 127
128 128 if not value:
129 129 return value
130 130
131 131 parts = value.split('$', 3)
132 132 if not len(parts) == 3:
133 133 # probably not encrypted values
134 134 return value
135 135 else:
136 136 if parts[0] != 'enc':
137 137 # parts ok but without our header ?
138 138 return value
139 139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 140 'rhodecode.encrypted_values.strict') or True)
141 141 # at that stage we know it's our encryption
142 142 if parts[1] == 'aes':
143 143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 144 elif parts[1] == 'aes_hmac':
145 145 decrypted_data = AESCipher(
146 146 ENCRYPTION_KEY, hmac=True,
147 147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 148 else:
149 149 raise ValueError(
150 150 'Encryption type part is wrong, must be `aes` '
151 151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 152 return decrypted_data
153 153
154 154
155 155 class BaseModel(object):
156 156 """
157 157 Base Model for all classes
158 158 """
159 159
160 160 @classmethod
161 161 def _get_keys(cls):
162 162 """return column names for this model """
163 163 return class_mapper(cls).c.keys()
164 164
165 165 def get_dict(self):
166 166 """
167 167 return dict with keys and values corresponding
168 168 to this model data """
169 169
170 170 d = {}
171 171 for k in self._get_keys():
172 172 d[k] = getattr(self, k)
173 173
174 174 # also use __json__() if present to get additional fields
175 175 _json_attr = getattr(self, '__json__', None)
176 176 if _json_attr:
177 177 # update with attributes from __json__
178 178 if callable(_json_attr):
179 179 _json_attr = _json_attr()
180 180 for k, val in _json_attr.iteritems():
181 181 d[k] = val
182 182 return d
183 183
184 184 def get_appstruct(self):
185 185 """return list with keys and values tuples corresponding
186 186 to this model data """
187 187
188 188 l = []
189 189 for k in self._get_keys():
190 190 l.append((k, getattr(self, k),))
191 191 return l
192 192
193 193 def populate_obj(self, populate_dict):
194 194 """populate model with data from given populate_dict"""
195 195
196 196 for k in self._get_keys():
197 197 if k in populate_dict:
198 198 setattr(self, k, populate_dict[k])
199 199
200 200 @classmethod
201 201 def query(cls):
202 202 return Session().query(cls)
203 203
204 204 @classmethod
205 205 def get(cls, id_):
206 206 if id_:
207 207 return cls.query().get(id_)
208 208
209 209 @classmethod
210 210 def get_or_404(cls, id_):
211 211 try:
212 212 id_ = int(id_)
213 213 except (TypeError, ValueError):
214 214 raise HTTPNotFound
215 215
216 216 res = cls.query().get(id_)
217 217 if not res:
218 218 raise HTTPNotFound
219 219 return res
220 220
221 221 @classmethod
222 222 def getAll(cls):
223 223 # deprecated and left for backward compatibility
224 224 return cls.get_all()
225 225
226 226 @classmethod
227 227 def get_all(cls):
228 228 return cls.query().all()
229 229
230 230 @classmethod
231 231 def delete(cls, id_):
232 232 obj = cls.query().get(id_)
233 233 Session().delete(obj)
234 234
235 235 @classmethod
236 236 def identity_cache(cls, session, attr_name, value):
237 237 exist_in_session = []
238 238 for (item_cls, pkey), instance in session.identity_map.items():
239 239 if cls == item_cls and getattr(instance, attr_name) == value:
240 240 exist_in_session.append(instance)
241 241 if exist_in_session:
242 242 if len(exist_in_session) == 1:
243 243 return exist_in_session[0]
244 244 log.exception(
245 245 'multiple objects with attr %s and '
246 246 'value %s found with same name: %r',
247 247 attr_name, value, exist_in_session)
248 248
249 249 def __repr__(self):
250 250 if hasattr(self, '__unicode__'):
251 251 # python repr needs to return str
252 252 try:
253 253 return safe_str(self.__unicode__())
254 254 except UnicodeDecodeError:
255 255 pass
256 256 return '<DB:%s>' % (self.__class__.__name__)
257 257
258 258
259 259 class RhodeCodeSetting(Base, BaseModel):
260 260 __tablename__ = 'rhodecode_settings'
261 261 __table_args__ = (
262 262 UniqueConstraint('app_settings_name'),
263 263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 265 )
266 266
267 267 SETTINGS_TYPES = {
268 268 'str': safe_str,
269 269 'int': safe_int,
270 270 'unicode': safe_unicode,
271 271 'bool': str2bool,
272 272 'list': functools.partial(aslist, sep=',')
273 273 }
274 274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 275 GLOBAL_CONF_KEY = 'app_settings'
276 276
277 277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281 281
282 282 def __init__(self, key='', val='', type='unicode'):
283 283 self.app_settings_name = key
284 284 self.app_settings_type = type
285 285 self.app_settings_value = val
286 286
287 287 @validates('_app_settings_value')
288 288 def validate_settings_value(self, key, val):
289 289 assert type(val) == unicode
290 290 return val
291 291
292 292 @hybrid_property
293 293 def app_settings_value(self):
294 294 v = self._app_settings_value
295 295 _type = self.app_settings_type
296 296 if _type:
297 297 _type = self.app_settings_type.split('.')[0]
298 298 # decode the encrypted value
299 299 if 'encrypted' in self.app_settings_type:
300 300 cipher = EncryptedTextValue()
301 301 v = safe_unicode(cipher.process_result_value(v, None))
302 302
303 303 converter = self.SETTINGS_TYPES.get(_type) or \
304 304 self.SETTINGS_TYPES['unicode']
305 305 return converter(v)
306 306
307 307 @app_settings_value.setter
308 308 def app_settings_value(self, val):
309 309 """
310 310 Setter that will always make sure we use unicode in app_settings_value
311 311
312 312 :param val:
313 313 """
314 314 val = safe_unicode(val)
315 315 # encode the encrypted value
316 316 if 'encrypted' in self.app_settings_type:
317 317 cipher = EncryptedTextValue()
318 318 val = safe_unicode(cipher.process_bind_param(val, None))
319 319 self._app_settings_value = val
320 320
321 321 @hybrid_property
322 322 def app_settings_type(self):
323 323 return self._app_settings_type
324 324
325 325 @app_settings_type.setter
326 326 def app_settings_type(self, val):
327 327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 328 raise Exception('type must be one of %s got %s'
329 329 % (self.SETTINGS_TYPES.keys(), val))
330 330 self._app_settings_type = val
331 331
332 332 def __unicode__(self):
333 333 return u"<%s('%s:%s[%s]')>" % (
334 334 self.__class__.__name__,
335 335 self.app_settings_name, self.app_settings_value,
336 336 self.app_settings_type
337 337 )
338 338
339 339
340 340 class RhodeCodeUi(Base, BaseModel):
341 341 __tablename__ = 'rhodecode_ui'
342 342 __table_args__ = (
343 343 UniqueConstraint('ui_key'),
344 344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 346 )
347 347
348 348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 349 # HG
350 350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 351 HOOK_PULL = 'outgoing.pull_logger'
352 352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 353 HOOK_PUSH = 'changegroup.push_logger'
354 354
355 355 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 356 # git part is currently hardcoded.
357 357
358 358 # SVN PATTERNS
359 359 SVN_BRANCH_ID = 'vcs_svn_branch'
360 360 SVN_TAG_ID = 'vcs_svn_tag'
361 361
362 362 ui_id = Column(
363 363 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 364 primary_key=True)
365 365 ui_section = Column(
366 366 "ui_section", String(255), nullable=True, unique=None, default=None)
367 367 ui_key = Column(
368 368 "ui_key", String(255), nullable=True, unique=None, default=None)
369 369 ui_value = Column(
370 370 "ui_value", String(255), nullable=True, unique=None, default=None)
371 371 ui_active = Column(
372 372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373 373
374 374 def __repr__(self):
375 375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 376 self.ui_key, self.ui_value)
377 377
378 378
379 379 class RepoRhodeCodeSetting(Base, BaseModel):
380 380 __tablename__ = 'repo_rhodecode_settings'
381 381 __table_args__ = (
382 382 UniqueConstraint(
383 383 'app_settings_name', 'repository_id',
384 384 name='uq_repo_rhodecode_setting_name_repo_id'),
385 385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 387 )
388 388
389 389 repository_id = Column(
390 390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 391 nullable=False)
392 392 app_settings_id = Column(
393 393 "app_settings_id", Integer(), nullable=False, unique=True,
394 394 default=None, primary_key=True)
395 395 app_settings_name = Column(
396 396 "app_settings_name", String(255), nullable=True, unique=None,
397 397 default=None)
398 398 _app_settings_value = Column(
399 399 "app_settings_value", String(4096), nullable=True, unique=None,
400 400 default=None)
401 401 _app_settings_type = Column(
402 402 "app_settings_type", String(255), nullable=True, unique=None,
403 403 default=None)
404 404
405 405 repository = relationship('Repository')
406 406
407 407 def __init__(self, repository_id, key='', val='', type='unicode'):
408 408 self.repository_id = repository_id
409 409 self.app_settings_name = key
410 410 self.app_settings_type = type
411 411 self.app_settings_value = val
412 412
413 413 @validates('_app_settings_value')
414 414 def validate_settings_value(self, key, val):
415 415 assert type(val) == unicode
416 416 return val
417 417
418 418 @hybrid_property
419 419 def app_settings_value(self):
420 420 v = self._app_settings_value
421 421 type_ = self.app_settings_type
422 422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 424 return converter(v)
425 425
426 426 @app_settings_value.setter
427 427 def app_settings_value(self, val):
428 428 """
429 429 Setter that will always make sure we use unicode in app_settings_value
430 430
431 431 :param val:
432 432 """
433 433 self._app_settings_value = safe_unicode(val)
434 434
435 435 @hybrid_property
436 436 def app_settings_type(self):
437 437 return self._app_settings_type
438 438
439 439 @app_settings_type.setter
440 440 def app_settings_type(self, val):
441 441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 442 if val not in SETTINGS_TYPES:
443 443 raise Exception('type must be one of %s got %s'
444 444 % (SETTINGS_TYPES.keys(), val))
445 445 self._app_settings_type = val
446 446
447 447 def __unicode__(self):
448 448 return u"<%s('%s:%s:%s[%s]')>" % (
449 449 self.__class__.__name__, self.repository.repo_name,
450 450 self.app_settings_name, self.app_settings_value,
451 451 self.app_settings_type
452 452 )
453 453
454 454
455 455 class RepoRhodeCodeUi(Base, BaseModel):
456 456 __tablename__ = 'repo_rhodecode_ui'
457 457 __table_args__ = (
458 458 UniqueConstraint(
459 459 'repository_id', 'ui_section', 'ui_key',
460 460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 463 )
464 464
465 465 repository_id = Column(
466 466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 467 nullable=False)
468 468 ui_id = Column(
469 469 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 470 primary_key=True)
471 471 ui_section = Column(
472 472 "ui_section", String(255), nullable=True, unique=None, default=None)
473 473 ui_key = Column(
474 474 "ui_key", String(255), nullable=True, unique=None, default=None)
475 475 ui_value = Column(
476 476 "ui_value", String(255), nullable=True, unique=None, default=None)
477 477 ui_active = Column(
478 478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479 479
480 480 repository = relationship('Repository')
481 481
482 482 def __repr__(self):
483 483 return '<%s[%s:%s]%s=>%s]>' % (
484 484 self.__class__.__name__, self.repository.repo_name,
485 485 self.ui_section, self.ui_key, self.ui_value)
486 486
487 487
488 488 class User(Base, BaseModel):
489 489 __tablename__ = 'users'
490 490 __table_args__ = (
491 491 UniqueConstraint('username'), UniqueConstraint('email'),
492 492 Index('u_username_idx', 'username'),
493 493 Index('u_email_idx', 'email'),
494 494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 496 )
497 497 DEFAULT_USER = 'default'
498 498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500 500
501 501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 502 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 503 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516 516
517 517 user_log = relationship('UserLog')
518 518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519 519
520 520 repositories = relationship('Repository')
521 521 repository_groups = relationship('RepoGroup')
522 522 user_groups = relationship('UserGroup')
523 523
524 524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526 526
527 527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530 530
531 531 group_member = relationship('UserGroupMember', cascade='all')
532 532
533 533 notifications = relationship('UserNotification', cascade='all')
534 534 # notifications assigned to this user
535 535 user_created_notifications = relationship('Notification', cascade='all')
536 536 # comments created by this user
537 537 user_comments = relationship('ChangesetComment', cascade='all')
538 538 # user profile extra info
539 539 user_emails = relationship('UserEmailMap', cascade='all')
540 540 user_ip_map = relationship('UserIpMap', cascade='all')
541 541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 542 # gists
543 543 user_gists = relationship('Gist', cascade='all')
544 544 # user pull requests
545 545 user_pull_requests = relationship('PullRequest', cascade='all')
546 546 # external identities
547 547 extenal_identities = relationship(
548 548 'ExternalIdentity',
549 549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 550 cascade='all')
551 551
552 552 def __unicode__(self):
553 553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 554 self.user_id, self.username)
555 555
556 556 @hybrid_property
557 557 def email(self):
558 558 return self._email
559 559
560 560 @email.setter
561 561 def email(self, val):
562 562 self._email = val.lower() if val else None
563 563
564 564 @property
565 565 def firstname(self):
566 566 # alias for future
567 567 return self.name
568 568
569 569 @property
570 570 def emails(self):
571 571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 572 return [self.email] + [x.email for x in other]
573 573
574 574 @property
575 575 def auth_tokens(self):
576 576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577 577
578 578 @property
579 579 def extra_auth_tokens(self):
580 580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581 581
582 582 @property
583 583 def feed_token(self):
584 584 feed_tokens = UserApiKeys.query()\
585 585 .filter(UserApiKeys.user == self)\
586 586 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
587 587 .all()
588 588 if feed_tokens:
589 589 return feed_tokens[0].api_key
590 590 else:
591 591 # use the main token so we don't end up with nothing...
592 592 return self.api_key
593 593
594 594 @classmethod
595 595 def extra_valid_auth_tokens(cls, user, role=None):
596 596 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
597 597 .filter(or_(UserApiKeys.expires == -1,
598 598 UserApiKeys.expires >= time.time()))
599 599 if role:
600 600 tokens = tokens.filter(or_(UserApiKeys.role == role,
601 601 UserApiKeys.role == UserApiKeys.ROLE_ALL))
602 602 return tokens.all()
603 603
604 604 @property
605 605 def ip_addresses(self):
606 606 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
607 607 return [x.ip_addr for x in ret]
608 608
609 609 @property
610 610 def username_and_name(self):
611 611 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
612 612
613 613 @property
614 614 def username_or_name_or_email(self):
615 615 full_name = self.full_name if self.full_name is not ' ' else None
616 616 return self.username or full_name or self.email
617 617
618 618 @property
619 619 def full_name(self):
620 620 return '%s %s' % (self.firstname, self.lastname)
621 621
622 622 @property
623 623 def full_name_or_username(self):
624 624 return ('%s %s' % (self.firstname, self.lastname)
625 625 if (self.firstname and self.lastname) else self.username)
626 626
627 627 @property
628 628 def full_contact(self):
629 629 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
630 630
631 631 @property
632 632 def short_contact(self):
633 633 return '%s %s' % (self.firstname, self.lastname)
634 634
635 635 @property
636 636 def is_admin(self):
637 637 return self.admin
638 638
639 639 @property
640 640 def AuthUser(self):
641 641 """
642 642 Returns instance of AuthUser for this user
643 643 """
644 644 from rhodecode.lib.auth import AuthUser
645 645 return AuthUser(user_id=self.user_id, api_key=self.api_key,
646 646 username=self.username)
647 647
648 648 @hybrid_property
649 649 def user_data(self):
650 650 if not self._user_data:
651 651 return {}
652 652
653 653 try:
654 654 return json.loads(self._user_data)
655 655 except TypeError:
656 656 return {}
657 657
658 658 @user_data.setter
659 659 def user_data(self, val):
660 660 if not isinstance(val, dict):
661 661 raise Exception('user_data must be dict, got %s' % type(val))
662 662 try:
663 663 self._user_data = json.dumps(val)
664 664 except Exception:
665 665 log.error(traceback.format_exc())
666 666
667 667 @classmethod
668 668 def get_by_username(cls, username, case_insensitive=False,
669 669 cache=False, identity_cache=False):
670 670 session = Session()
671 671
672 672 if case_insensitive:
673 673 q = cls.query().filter(
674 674 func.lower(cls.username) == func.lower(username))
675 675 else:
676 676 q = cls.query().filter(cls.username == username)
677 677
678 678 if cache:
679 679 if identity_cache:
680 680 val = cls.identity_cache(session, 'username', username)
681 681 if val:
682 682 return val
683 683 else:
684 684 q = q.options(
685 685 FromCache("sql_cache_short",
686 686 "get_user_by_name_%s" % _hash_key(username)))
687 687
688 688 return q.scalar()
689 689
690 690 @classmethod
691 691 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
692 692 q = cls.query().filter(cls.api_key == auth_token)
693 693
694 694 if cache:
695 695 q = q.options(FromCache("sql_cache_short",
696 696 "get_auth_token_%s" % auth_token))
697 697 res = q.scalar()
698 698
699 699 if fallback and not res:
700 700 #fallback to additional keys
701 701 _res = UserApiKeys.query()\
702 702 .filter(UserApiKeys.api_key == auth_token)\
703 703 .filter(or_(UserApiKeys.expires == -1,
704 704 UserApiKeys.expires >= time.time()))\
705 705 .first()
706 706 if _res:
707 707 res = _res.user
708 708 return res
709 709
710 710 @classmethod
711 711 def get_by_email(cls, email, case_insensitive=False, cache=False):
712 712
713 713 if case_insensitive:
714 714 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
715 715
716 716 else:
717 717 q = cls.query().filter(cls.email == email)
718 718
719 719 if cache:
720 720 q = q.options(FromCache("sql_cache_short",
721 721 "get_email_key_%s" % _hash_key(email)))
722 722
723 723 ret = q.scalar()
724 724 if ret is None:
725 725 q = UserEmailMap.query()
726 726 # try fetching in alternate email map
727 727 if case_insensitive:
728 728 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
729 729 else:
730 730 q = q.filter(UserEmailMap.email == email)
731 731 q = q.options(joinedload(UserEmailMap.user))
732 732 if cache:
733 733 q = q.options(FromCache("sql_cache_short",
734 734 "get_email_map_key_%s" % email))
735 735 ret = getattr(q.scalar(), 'user', None)
736 736
737 737 return ret
738 738
739 739 @classmethod
740 740 def get_from_cs_author(cls, author):
741 741 """
742 742 Tries to get User objects out of commit author string
743 743
744 744 :param author:
745 745 """
746 746 from rhodecode.lib.helpers import email, author_name
747 747 # Valid email in the attribute passed, see if they're in the system
748 748 _email = email(author)
749 749 if _email:
750 750 user = cls.get_by_email(_email, case_insensitive=True)
751 751 if user:
752 752 return user
753 753 # Maybe we can match by username?
754 754 _author = author_name(author)
755 755 user = cls.get_by_username(_author, case_insensitive=True)
756 756 if user:
757 757 return user
758 758
759 759 def update_userdata(self, **kwargs):
760 760 usr = self
761 761 old = usr.user_data
762 762 old.update(**kwargs)
763 763 usr.user_data = old
764 764 Session().add(usr)
765 765 log.debug('updated userdata with ', kwargs)
766 766
767 767 def update_lastlogin(self):
768 768 """Update user lastlogin"""
769 769 self.last_login = datetime.datetime.now()
770 770 Session().add(self)
771 771 log.debug('updated user %s lastlogin', self.username)
772 772
773 773 def update_lastactivity(self):
774 774 """Update user lastactivity"""
775 775 usr = self
776 776 old = usr.user_data
777 777 old.update({'last_activity': time.time()})
778 778 usr.user_data = old
779 779 Session().add(usr)
780 780 log.debug('updated user %s lastactivity', usr.username)
781 781
782 782 def update_password(self, new_password, change_api_key=False):
783 783 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
784 784
785 785 self.password = get_crypt_password(new_password)
786 786 if change_api_key:
787 787 self.api_key = generate_auth_token(self.username)
788 788 Session().add(self)
789 789
790 790 @classmethod
791 791 def get_first_super_admin(cls):
792 792 user = User.query().filter(User.admin == true()).first()
793 793 if user is None:
794 794 raise Exception('FATAL: Missing administrative account!')
795 795 return user
796 796
797 797 @classmethod
798 798 def get_all_super_admins(cls):
799 799 """
800 800 Returns all admin accounts sorted by username
801 801 """
802 802 return User.query().filter(User.admin == true())\
803 803 .order_by(User.username.asc()).all()
804 804
805 805 @classmethod
806 806 def get_default_user(cls, cache=False):
807 807 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
808 808 if user is None:
809 809 raise Exception('FATAL: Missing default account!')
810 810 return user
811 811
812 812 def _get_default_perms(self, user, suffix=''):
813 813 from rhodecode.model.permission import PermissionModel
814 814 return PermissionModel().get_default_perms(user.user_perms, suffix)
815 815
816 816 def get_default_perms(self, suffix=''):
817 817 return self._get_default_perms(self, suffix)
818 818
819 819 def get_api_data(self, include_secrets=False, details='full'):
820 820 """
821 821 Common function for generating user related data for API
822 822
823 823 :param include_secrets: By default secrets in the API data will be replaced
824 824 by a placeholder value to prevent exposing this data by accident. In case
825 825 this data shall be exposed, set this flag to ``True``.
826 826
827 827 :param details: details can be 'basic|full' basic gives only a subset of
828 828 the available user information that includes user_id, name and emails.
829 829 """
830 830 user = self
831 831 user_data = self.user_data
832 832 data = {
833 833 'user_id': user.user_id,
834 834 'username': user.username,
835 835 'firstname': user.name,
836 836 'lastname': user.lastname,
837 837 'email': user.email,
838 838 'emails': user.emails,
839 839 }
840 840 if details == 'basic':
841 841 return data
842 842
843 843 api_key_length = 40
844 844 api_key_replacement = '*' * api_key_length
845 845
846 846 extras = {
847 847 'api_key': api_key_replacement,
848 848 'api_keys': [api_key_replacement],
849 849 'active': user.active,
850 850 'admin': user.admin,
851 851 'extern_type': user.extern_type,
852 852 'extern_name': user.extern_name,
853 853 'last_login': user.last_login,
854 854 'ip_addresses': user.ip_addresses,
855 855 'language': user_data.get('language')
856 856 }
857 857 data.update(extras)
858 858
859 859 if include_secrets:
860 860 data['api_key'] = user.api_key
861 861 data['api_keys'] = user.auth_tokens
862 862 return data
863 863
864 864 def __json__(self):
865 865 data = {
866 866 'full_name': self.full_name,
867 867 'full_name_or_username': self.full_name_or_username,
868 868 'short_contact': self.short_contact,
869 869 'full_contact': self.full_contact,
870 870 }
871 871 data.update(self.get_api_data())
872 872 return data
873 873
874 874
875 875 class UserApiKeys(Base, BaseModel):
876 876 __tablename__ = 'user_api_keys'
877 877 __table_args__ = (
878 878 Index('uak_api_key_idx', 'api_key'),
879 879 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
880 880 UniqueConstraint('api_key'),
881 881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
882 882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
883 883 )
884 884 __mapper_args__ = {}
885 885
886 886 # ApiKey role
887 887 ROLE_ALL = 'token_role_all'
888 888 ROLE_HTTP = 'token_role_http'
889 889 ROLE_VCS = 'token_role_vcs'
890 890 ROLE_API = 'token_role_api'
891 891 ROLE_FEED = 'token_role_feed'
892 892 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
893 893
894 894 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
895 895 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
896 896 api_key = Column("api_key", String(255), nullable=False, unique=True)
897 897 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
898 898 expires = Column('expires', Float(53), nullable=False)
899 899 role = Column('role', String(255), nullable=True)
900 900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
901 901
902 902 user = relationship('User', lazy='joined')
903 903
904 904 @classmethod
905 905 def _get_role_name(cls, role):
906 906 return {
907 907 cls.ROLE_ALL: _('all'),
908 908 cls.ROLE_HTTP: _('http/web interface'),
909 909 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
910 910 cls.ROLE_API: _('api calls'),
911 911 cls.ROLE_FEED: _('feed access'),
912 912 }.get(role, role)
913 913
914 914 @property
915 915 def expired(self):
916 916 if self.expires == -1:
917 917 return False
918 918 return time.time() > self.expires
919 919
920 920 @property
921 921 def role_humanized(self):
922 922 return self._get_role_name(self.role)
923 923
924 924
925 925 class UserEmailMap(Base, BaseModel):
926 926 __tablename__ = 'user_email_map'
927 927 __table_args__ = (
928 928 Index('uem_email_idx', 'email'),
929 929 UniqueConstraint('email'),
930 930 {'extend_existing': True, 'mysql_engine': 'InnoDB',
931 931 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
932 932 )
933 933 __mapper_args__ = {}
934 934
935 935 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 936 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
937 937 _email = Column("email", String(255), nullable=True, unique=False, default=None)
938 938 user = relationship('User', lazy='joined')
939 939
940 940 @validates('_email')
941 941 def validate_email(self, key, email):
942 942 # check if this email is not main one
943 943 main_email = Session().query(User).filter(User.email == email).scalar()
944 944 if main_email is not None:
945 945 raise AttributeError('email %s is present is user table' % email)
946 946 return email
947 947
948 948 @hybrid_property
949 949 def email(self):
950 950 return self._email
951 951
952 952 @email.setter
953 953 def email(self, val):
954 954 self._email = val.lower() if val else None
955 955
956 956
957 957 class UserIpMap(Base, BaseModel):
958 958 __tablename__ = 'user_ip_map'
959 959 __table_args__ = (
960 960 UniqueConstraint('user_id', 'ip_addr'),
961 961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
962 962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
963 963 )
964 964 __mapper_args__ = {}
965 965
966 966 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 967 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
968 968 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
969 969 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
970 970 description = Column("description", String(10000), nullable=True, unique=None, default=None)
971 971 user = relationship('User', lazy='joined')
972 972
973 973 @classmethod
974 974 def _get_ip_range(cls, ip_addr):
975 975 net = ipaddress.ip_network(ip_addr, strict=False)
976 976 return [str(net.network_address), str(net.broadcast_address)]
977 977
978 978 def __json__(self):
979 979 return {
980 980 'ip_addr': self.ip_addr,
981 981 'ip_range': self._get_ip_range(self.ip_addr),
982 982 }
983 983
984 984 def __unicode__(self):
985 985 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
986 986 self.user_id, self.ip_addr)
987 987
988 988 class UserLog(Base, BaseModel):
989 989 __tablename__ = 'user_logs'
990 990 __table_args__ = (
991 991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
992 992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
993 993 )
994 994 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 995 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
996 996 username = Column("username", String(255), nullable=True, unique=None, default=None)
997 997 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
998 998 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
999 999 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1000 1000 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1001 1001 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1002 1002
1003 1003 def __unicode__(self):
1004 1004 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1005 1005 self.repository_name,
1006 1006 self.action)
1007 1007
1008 1008 @property
1009 1009 def action_as_day(self):
1010 1010 return datetime.date(*self.action_date.timetuple()[:3])
1011 1011
1012 1012 user = relationship('User')
1013 1013 repository = relationship('Repository', cascade='')
1014 1014
1015 1015
1016 1016 class UserGroup(Base, BaseModel):
1017 1017 __tablename__ = 'users_groups'
1018 1018 __table_args__ = (
1019 1019 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1020 1020 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1021 1021 )
1022 1022
1023 1023 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1024 1024 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1025 1025 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1026 1026 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1027 1027 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1028 1028 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1029 1029 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1030 1030 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1031 1031
1032 1032 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1033 1033 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1034 1034 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1035 1035 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1036 1036 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1037 1037 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1038 1038
1039 1039 user = relationship('User')
1040 1040
1041 1041 @hybrid_property
1042 1042 def group_data(self):
1043 1043 if not self._group_data:
1044 1044 return {}
1045 1045
1046 1046 try:
1047 1047 return json.loads(self._group_data)
1048 1048 except TypeError:
1049 1049 return {}
1050 1050
1051 1051 @group_data.setter
1052 1052 def group_data(self, val):
1053 1053 try:
1054 1054 self._group_data = json.dumps(val)
1055 1055 except Exception:
1056 1056 log.error(traceback.format_exc())
1057 1057
1058 1058 def __unicode__(self):
1059 1059 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1060 1060 self.users_group_id,
1061 1061 self.users_group_name)
1062 1062
1063 1063 @classmethod
1064 1064 def get_by_group_name(cls, group_name, cache=False,
1065 1065 case_insensitive=False):
1066 1066 if case_insensitive:
1067 1067 q = cls.query().filter(func.lower(cls.users_group_name) ==
1068 1068 func.lower(group_name))
1069 1069
1070 1070 else:
1071 1071 q = cls.query().filter(cls.users_group_name == group_name)
1072 1072 if cache:
1073 1073 q = q.options(FromCache(
1074 1074 "sql_cache_short",
1075 1075 "get_group_%s" % _hash_key(group_name)))
1076 1076 return q.scalar()
1077 1077
1078 1078 @classmethod
1079 1079 def get(cls, user_group_id, cache=False):
1080 1080 user_group = cls.query()
1081 1081 if cache:
1082 1082 user_group = user_group.options(FromCache("sql_cache_short",
1083 1083 "get_users_group_%s" % user_group_id))
1084 1084 return user_group.get(user_group_id)
1085 1085
1086 1086 def permissions(self, with_admins=True, with_owner=True):
1087 1087 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1088 1088 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1089 1089 joinedload(UserUserGroupToPerm.user),
1090 1090 joinedload(UserUserGroupToPerm.permission),)
1091 1091
1092 1092 # get owners and admins and permissions. We do a trick of re-writing
1093 1093 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1094 1094 # has a global reference and changing one object propagates to all
1095 1095 # others. This means if admin is also an owner admin_row that change
1096 1096 # would propagate to both objects
1097 1097 perm_rows = []
1098 1098 for _usr in q.all():
1099 1099 usr = AttributeDict(_usr.user.get_dict())
1100 1100 usr.permission = _usr.permission.permission_name
1101 1101 perm_rows.append(usr)
1102 1102
1103 1103 # filter the perm rows by 'default' first and then sort them by
1104 1104 # admin,write,read,none permissions sorted again alphabetically in
1105 1105 # each group
1106 1106 perm_rows = sorted(perm_rows, key=display_sort)
1107 1107
1108 1108 _admin_perm = 'usergroup.admin'
1109 1109 owner_row = []
1110 1110 if with_owner:
1111 1111 usr = AttributeDict(self.user.get_dict())
1112 1112 usr.owner_row = True
1113 1113 usr.permission = _admin_perm
1114 1114 owner_row.append(usr)
1115 1115
1116 1116 super_admin_rows = []
1117 1117 if with_admins:
1118 1118 for usr in User.get_all_super_admins():
1119 1119 # if this admin is also owner, don't double the record
1120 1120 if usr.user_id == owner_row[0].user_id:
1121 1121 owner_row[0].admin_row = True
1122 1122 else:
1123 1123 usr = AttributeDict(usr.get_dict())
1124 1124 usr.admin_row = True
1125 1125 usr.permission = _admin_perm
1126 1126 super_admin_rows.append(usr)
1127 1127
1128 1128 return super_admin_rows + owner_row + perm_rows
1129 1129
1130 1130 def permission_user_groups(self):
1131 1131 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1132 1132 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1133 1133 joinedload(UserGroupUserGroupToPerm.target_user_group),
1134 1134 joinedload(UserGroupUserGroupToPerm.permission),)
1135 1135
1136 1136 perm_rows = []
1137 1137 for _user_group in q.all():
1138 1138 usr = AttributeDict(_user_group.user_group.get_dict())
1139 1139 usr.permission = _user_group.permission.permission_name
1140 1140 perm_rows.append(usr)
1141 1141
1142 1142 return perm_rows
1143 1143
1144 1144 def _get_default_perms(self, user_group, suffix=''):
1145 1145 from rhodecode.model.permission import PermissionModel
1146 1146 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1147 1147
1148 1148 def get_default_perms(self, suffix=''):
1149 1149 return self._get_default_perms(self, suffix)
1150 1150
1151 1151 def get_api_data(self, with_group_members=True, include_secrets=False):
1152 1152 """
1153 1153 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1154 1154 basically forwarded.
1155 1155
1156 1156 """
1157 1157 user_group = self
1158 1158
1159 1159 data = {
1160 1160 'users_group_id': user_group.users_group_id,
1161 1161 'group_name': user_group.users_group_name,
1162 1162 'group_description': user_group.user_group_description,
1163 1163 'active': user_group.users_group_active,
1164 1164 'owner': user_group.user.username,
1165 1165 }
1166 1166 if with_group_members:
1167 1167 users = []
1168 1168 for user in user_group.members:
1169 1169 user = user.user
1170 1170 users.append(user.get_api_data(include_secrets=include_secrets))
1171 1171 data['users'] = users
1172 1172
1173 1173 return data
1174 1174
1175 1175
1176 1176 class UserGroupMember(Base, BaseModel):
1177 1177 __tablename__ = 'users_groups_members'
1178 1178 __table_args__ = (
1179 1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1180 1180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1181 1181 )
1182 1182
1183 1183 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1184 1184 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1185 1185 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1186 1186
1187 1187 user = relationship('User', lazy='joined')
1188 1188 users_group = relationship('UserGroup')
1189 1189
1190 1190 def __init__(self, gr_id='', u_id=''):
1191 1191 self.users_group_id = gr_id
1192 1192 self.user_id = u_id
1193 1193
1194 1194
1195 1195 class RepositoryField(Base, BaseModel):
1196 1196 __tablename__ = 'repositories_fields'
1197 1197 __table_args__ = (
1198 1198 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1199 1199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1200 1200 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1201 1201 )
1202 1202 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1203 1203
1204 1204 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1205 1205 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1206 1206 field_key = Column("field_key", String(250))
1207 1207 field_label = Column("field_label", String(1024), nullable=False)
1208 1208 field_value = Column("field_value", String(10000), nullable=False)
1209 1209 field_desc = Column("field_desc", String(1024), nullable=False)
1210 1210 field_type = Column("field_type", String(255), nullable=False, unique=None)
1211 1211 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1212 1212
1213 1213 repository = relationship('Repository')
1214 1214
1215 1215 @property
1216 1216 def field_key_prefixed(self):
1217 1217 return 'ex_%s' % self.field_key
1218 1218
1219 1219 @classmethod
1220 1220 def un_prefix_key(cls, key):
1221 1221 if key.startswith(cls.PREFIX):
1222 1222 return key[len(cls.PREFIX):]
1223 1223 return key
1224 1224
1225 1225 @classmethod
1226 1226 def get_by_key_name(cls, key, repo):
1227 1227 row = cls.query()\
1228 1228 .filter(cls.repository == repo)\
1229 1229 .filter(cls.field_key == key).scalar()
1230 1230 return row
1231 1231
1232 1232
1233 1233 class Repository(Base, BaseModel):
1234 1234 __tablename__ = 'repositories'
1235 1235 __table_args__ = (
1236 1236 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1237 1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1238 1238 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1239 1239 )
1240 1240 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1241 1241 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1242 1242
1243 1243 STATE_CREATED = 'repo_state_created'
1244 1244 STATE_PENDING = 'repo_state_pending'
1245 1245 STATE_ERROR = 'repo_state_error'
1246 1246
1247 1247 LOCK_AUTOMATIC = 'lock_auto'
1248 1248 LOCK_API = 'lock_api'
1249 1249 LOCK_WEB = 'lock_web'
1250 1250 LOCK_PULL = 'lock_pull'
1251 1251
1252 1252 NAME_SEP = URL_SEP
1253 1253
1254 1254 repo_id = Column(
1255 1255 "repo_id", Integer(), nullable=False, unique=True, default=None,
1256 1256 primary_key=True)
1257 1257 _repo_name = Column(
1258 1258 "repo_name", Text(), nullable=False, default=None)
1259 1259 _repo_name_hash = Column(
1260 1260 "repo_name_hash", String(255), nullable=False, unique=True)
1261 1261 repo_state = Column("repo_state", String(255), nullable=True)
1262 1262
1263 1263 clone_uri = Column(
1264 1264 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1265 1265 default=None)
1266 1266 repo_type = Column(
1267 1267 "repo_type", String(255), nullable=False, unique=False, default=None)
1268 1268 user_id = Column(
1269 1269 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1270 1270 unique=False, default=None)
1271 1271 private = Column(
1272 1272 "private", Boolean(), nullable=True, unique=None, default=None)
1273 1273 enable_statistics = Column(
1274 1274 "statistics", Boolean(), nullable=True, unique=None, default=True)
1275 1275 enable_downloads = Column(
1276 1276 "downloads", Boolean(), nullable=True, unique=None, default=True)
1277 1277 description = Column(
1278 1278 "description", String(10000), nullable=True, unique=None, default=None)
1279 1279 created_on = Column(
1280 1280 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1281 1281 default=datetime.datetime.now)
1282 1282 updated_on = Column(
1283 1283 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1284 1284 default=datetime.datetime.now)
1285 1285 _landing_revision = Column(
1286 1286 "landing_revision", String(255), nullable=False, unique=False,
1287 1287 default=None)
1288 1288 enable_locking = Column(
1289 1289 "enable_locking", Boolean(), nullable=False, unique=None,
1290 1290 default=False)
1291 1291 _locked = Column(
1292 1292 "locked", String(255), nullable=True, unique=False, default=None)
1293 1293 _changeset_cache = Column(
1294 1294 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1295 1295
1296 1296 fork_id = Column(
1297 1297 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1298 1298 nullable=True, unique=False, default=None)
1299 1299 group_id = Column(
1300 1300 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1301 1301 unique=False, default=None)
1302 1302
1303 1303 user = relationship('User', lazy='joined')
1304 1304 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1305 1305 group = relationship('RepoGroup', lazy='joined')
1306 1306 repo_to_perm = relationship(
1307 1307 'UserRepoToPerm', cascade='all',
1308 1308 order_by='UserRepoToPerm.repo_to_perm_id')
1309 1309 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 1310 stats = relationship('Statistics', cascade='all', uselist=False)
1311 1311
1312 1312 followers = relationship(
1313 1313 'UserFollowing',
1314 1314 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1315 1315 cascade='all')
1316 1316 extra_fields = relationship(
1317 1317 'RepositoryField', cascade="all, delete, delete-orphan")
1318 1318 logs = relationship('UserLog')
1319 1319 comments = relationship(
1320 1320 'ChangesetComment', cascade="all, delete, delete-orphan")
1321 1321 pull_requests_source = relationship(
1322 1322 'PullRequest',
1323 1323 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1324 1324 cascade="all, delete, delete-orphan")
1325 1325 pull_requests_target = relationship(
1326 1326 'PullRequest',
1327 1327 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1328 1328 cascade="all, delete, delete-orphan")
1329 1329 ui = relationship('RepoRhodeCodeUi', cascade="all")
1330 1330 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1331 1331 integrations = relationship('Integration',
1332 1332 cascade="all, delete, delete-orphan")
1333 1333
1334 1334 def __unicode__(self):
1335 1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1336 1336 safe_unicode(self.repo_name))
1337 1337
1338 1338 @hybrid_property
1339 1339 def landing_rev(self):
1340 1340 # always should return [rev_type, rev]
1341 1341 if self._landing_revision:
1342 1342 _rev_info = self._landing_revision.split(':')
1343 1343 if len(_rev_info) < 2:
1344 1344 _rev_info.insert(0, 'rev')
1345 1345 return [_rev_info[0], _rev_info[1]]
1346 1346 return [None, None]
1347 1347
1348 1348 @landing_rev.setter
1349 1349 def landing_rev(self, val):
1350 1350 if ':' not in val:
1351 1351 raise ValueError('value must be delimited with `:` and consist '
1352 1352 'of <rev_type>:<rev>, got %s instead' % val)
1353 1353 self._landing_revision = val
1354 1354
1355 1355 @hybrid_property
1356 1356 def locked(self):
1357 1357 if self._locked:
1358 1358 user_id, timelocked, reason = self._locked.split(':')
1359 1359 lock_values = int(user_id), timelocked, reason
1360 1360 else:
1361 1361 lock_values = [None, None, None]
1362 1362 return lock_values
1363 1363
1364 1364 @locked.setter
1365 1365 def locked(self, val):
1366 1366 if val and isinstance(val, (list, tuple)):
1367 1367 self._locked = ':'.join(map(str, val))
1368 1368 else:
1369 1369 self._locked = None
1370 1370
1371 1371 @hybrid_property
1372 1372 def changeset_cache(self):
1373 1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1374 1374 dummy = EmptyCommit().__json__()
1375 1375 if not self._changeset_cache:
1376 1376 return dummy
1377 1377 try:
1378 1378 return json.loads(self._changeset_cache)
1379 1379 except TypeError:
1380 1380 return dummy
1381 1381 except Exception:
1382 1382 log.error(traceback.format_exc())
1383 1383 return dummy
1384 1384
1385 1385 @changeset_cache.setter
1386 1386 def changeset_cache(self, val):
1387 1387 try:
1388 1388 self._changeset_cache = json.dumps(val)
1389 1389 except Exception:
1390 1390 log.error(traceback.format_exc())
1391 1391
1392 1392 @hybrid_property
1393 1393 def repo_name(self):
1394 1394 return self._repo_name
1395 1395
1396 1396 @repo_name.setter
1397 1397 def repo_name(self, value):
1398 1398 self._repo_name = value
1399 1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1400 1400
1401 1401 @classmethod
1402 1402 def normalize_repo_name(cls, repo_name):
1403 1403 """
1404 1404 Normalizes os specific repo_name to the format internally stored inside
1405 1405 database using URL_SEP
1406 1406
1407 1407 :param cls:
1408 1408 :param repo_name:
1409 1409 """
1410 1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1411 1411
1412 1412 @classmethod
1413 1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1414 1414 session = Session()
1415 1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1416 1416
1417 1417 if cache:
1418 1418 if identity_cache:
1419 1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1420 1420 if val:
1421 1421 return val
1422 1422 else:
1423 1423 q = q.options(
1424 1424 FromCache("sql_cache_short",
1425 1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1426 1426
1427 1427 return q.scalar()
1428 1428
1429 1429 @classmethod
1430 1430 def get_by_full_path(cls, repo_full_path):
1431 1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1432 1432 repo_name = cls.normalize_repo_name(repo_name)
1433 1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1434 1434
1435 1435 @classmethod
1436 1436 def get_repo_forks(cls, repo_id):
1437 1437 return cls.query().filter(Repository.fork_id == repo_id)
1438 1438
1439 1439 @classmethod
1440 1440 def base_path(cls):
1441 1441 """
1442 1442 Returns base path when all repos are stored
1443 1443
1444 1444 :param cls:
1445 1445 """
1446 1446 q = Session().query(RhodeCodeUi)\
1447 1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1448 1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1449 1449 return q.one().ui_value
1450 1450
1451 1451 @classmethod
1452 1452 def is_valid(cls, repo_name):
1453 1453 """
1454 1454 returns True if given repo name is a valid filesystem repository
1455 1455
1456 1456 :param cls:
1457 1457 :param repo_name:
1458 1458 """
1459 1459 from rhodecode.lib.utils import is_valid_repo
1460 1460
1461 1461 return is_valid_repo(repo_name, cls.base_path())
1462 1462
1463 1463 @classmethod
1464 1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1465 1465 case_insensitive=True):
1466 1466 q = Repository.query()
1467 1467
1468 1468 if not isinstance(user_id, Optional):
1469 1469 q = q.filter(Repository.user_id == user_id)
1470 1470
1471 1471 if not isinstance(group_id, Optional):
1472 1472 q = q.filter(Repository.group_id == group_id)
1473 1473
1474 1474 if case_insensitive:
1475 1475 q = q.order_by(func.lower(Repository.repo_name))
1476 1476 else:
1477 1477 q = q.order_by(Repository.repo_name)
1478 1478 return q.all()
1479 1479
1480 1480 @property
1481 1481 def forks(self):
1482 1482 """
1483 1483 Return forks of this repo
1484 1484 """
1485 1485 return Repository.get_repo_forks(self.repo_id)
1486 1486
1487 1487 @property
1488 1488 def parent(self):
1489 1489 """
1490 1490 Returns fork parent
1491 1491 """
1492 1492 return self.fork
1493 1493
1494 1494 @property
1495 1495 def just_name(self):
1496 1496 return self.repo_name.split(self.NAME_SEP)[-1]
1497 1497
1498 1498 @property
1499 1499 def groups_with_parents(self):
1500 1500 groups = []
1501 1501 if self.group is None:
1502 1502 return groups
1503 1503
1504 1504 cur_gr = self.group
1505 1505 groups.insert(0, cur_gr)
1506 1506 while 1:
1507 1507 gr = getattr(cur_gr, 'parent_group', None)
1508 1508 cur_gr = cur_gr.parent_group
1509 1509 if gr is None:
1510 1510 break
1511 1511 groups.insert(0, gr)
1512 1512
1513 1513 return groups
1514 1514
1515 1515 @property
1516 1516 def groups_and_repo(self):
1517 1517 return self.groups_with_parents, self
1518 1518
1519 1519 @LazyProperty
1520 1520 def repo_path(self):
1521 1521 """
1522 1522 Returns base full path for that repository means where it actually
1523 1523 exists on a filesystem
1524 1524 """
1525 1525 q = Session().query(RhodeCodeUi).filter(
1526 1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1527 1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1528 1528 return q.one().ui_value
1529 1529
1530 1530 @property
1531 1531 def repo_full_path(self):
1532 1532 p = [self.repo_path]
1533 1533 # we need to split the name by / since this is how we store the
1534 1534 # names in the database, but that eventually needs to be converted
1535 1535 # into a valid system path
1536 1536 p += self.repo_name.split(self.NAME_SEP)
1537 1537 return os.path.join(*map(safe_unicode, p))
1538 1538
1539 1539 @property
1540 1540 def cache_keys(self):
1541 1541 """
1542 1542 Returns associated cache keys for that repo
1543 1543 """
1544 1544 return CacheKey.query()\
1545 1545 .filter(CacheKey.cache_args == self.repo_name)\
1546 1546 .order_by(CacheKey.cache_key)\
1547 1547 .all()
1548 1548
1549 1549 def get_new_name(self, repo_name):
1550 1550 """
1551 1551 returns new full repository name based on assigned group and new new
1552 1552
1553 1553 :param group_name:
1554 1554 """
1555 1555 path_prefix = self.group.full_path_splitted if self.group else []
1556 1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1557 1557
1558 1558 @property
1559 1559 def _config(self):
1560 1560 """
1561 1561 Returns db based config object.
1562 1562 """
1563 1563 from rhodecode.lib.utils import make_db_config
1564 1564 return make_db_config(clear_session=False, repo=self)
1565 1565
1566 1566 def permissions(self, with_admins=True, with_owner=True):
1567 1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1568 1568 q = q.options(joinedload(UserRepoToPerm.repository),
1569 1569 joinedload(UserRepoToPerm.user),
1570 1570 joinedload(UserRepoToPerm.permission),)
1571 1571
1572 1572 # get owners and admins and permissions. We do a trick of re-writing
1573 1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1574 1574 # has a global reference and changing one object propagates to all
1575 1575 # others. This means if admin is also an owner admin_row that change
1576 1576 # would propagate to both objects
1577 1577 perm_rows = []
1578 1578 for _usr in q.all():
1579 1579 usr = AttributeDict(_usr.user.get_dict())
1580 1580 usr.permission = _usr.permission.permission_name
1581 1581 perm_rows.append(usr)
1582 1582
1583 1583 # filter the perm rows by 'default' first and then sort them by
1584 1584 # admin,write,read,none permissions sorted again alphabetically in
1585 1585 # each group
1586 1586 perm_rows = sorted(perm_rows, key=display_sort)
1587 1587
1588 1588 _admin_perm = 'repository.admin'
1589 1589 owner_row = []
1590 1590 if with_owner:
1591 1591 usr = AttributeDict(self.user.get_dict())
1592 1592 usr.owner_row = True
1593 1593 usr.permission = _admin_perm
1594 1594 owner_row.append(usr)
1595 1595
1596 1596 super_admin_rows = []
1597 1597 if with_admins:
1598 1598 for usr in User.get_all_super_admins():
1599 1599 # if this admin is also owner, don't double the record
1600 1600 if usr.user_id == owner_row[0].user_id:
1601 1601 owner_row[0].admin_row = True
1602 1602 else:
1603 1603 usr = AttributeDict(usr.get_dict())
1604 1604 usr.admin_row = True
1605 1605 usr.permission = _admin_perm
1606 1606 super_admin_rows.append(usr)
1607 1607
1608 1608 return super_admin_rows + owner_row + perm_rows
1609 1609
1610 1610 def permission_user_groups(self):
1611 1611 q = UserGroupRepoToPerm.query().filter(
1612 1612 UserGroupRepoToPerm.repository == self)
1613 1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1614 1614 joinedload(UserGroupRepoToPerm.users_group),
1615 1615 joinedload(UserGroupRepoToPerm.permission),)
1616 1616
1617 1617 perm_rows = []
1618 1618 for _user_group in q.all():
1619 1619 usr = AttributeDict(_user_group.users_group.get_dict())
1620 1620 usr.permission = _user_group.permission.permission_name
1621 1621 perm_rows.append(usr)
1622 1622
1623 1623 return perm_rows
1624 1624
1625 1625 def get_api_data(self, include_secrets=False):
1626 1626 """
1627 1627 Common function for generating repo api data
1628 1628
1629 1629 :param include_secrets: See :meth:`User.get_api_data`.
1630 1630
1631 1631 """
1632 1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1633 1633 # move this methods on models level.
1634 1634 from rhodecode.model.settings import SettingsModel
1635 1635
1636 1636 repo = self
1637 1637 _user_id, _time, _reason = self.locked
1638 1638
1639 1639 data = {
1640 1640 'repo_id': repo.repo_id,
1641 1641 'repo_name': repo.repo_name,
1642 1642 'repo_type': repo.repo_type,
1643 1643 'clone_uri': repo.clone_uri or '',
1644 1644 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1645 1645 'private': repo.private,
1646 1646 'created_on': repo.created_on,
1647 1647 'description': repo.description,
1648 1648 'landing_rev': repo.landing_rev,
1649 1649 'owner': repo.user.username,
1650 1650 'fork_of': repo.fork.repo_name if repo.fork else None,
1651 1651 'enable_statistics': repo.enable_statistics,
1652 1652 'enable_locking': repo.enable_locking,
1653 1653 'enable_downloads': repo.enable_downloads,
1654 1654 'last_changeset': repo.changeset_cache,
1655 1655 'locked_by': User.get(_user_id).get_api_data(
1656 1656 include_secrets=include_secrets) if _user_id else None,
1657 1657 'locked_date': time_to_datetime(_time) if _time else None,
1658 1658 'lock_reason': _reason if _reason else None,
1659 1659 }
1660 1660
1661 1661 # TODO: mikhail: should be per-repo settings here
1662 1662 rc_config = SettingsModel().get_all_settings()
1663 1663 repository_fields = str2bool(
1664 1664 rc_config.get('rhodecode_repository_fields'))
1665 1665 if repository_fields:
1666 1666 for f in self.extra_fields:
1667 1667 data[f.field_key_prefixed] = f.field_value
1668 1668
1669 1669 return data
1670 1670
1671 1671 @classmethod
1672 1672 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1673 1673 if not lock_time:
1674 1674 lock_time = time.time()
1675 1675 if not lock_reason:
1676 1676 lock_reason = cls.LOCK_AUTOMATIC
1677 1677 repo.locked = [user_id, lock_time, lock_reason]
1678 1678 Session().add(repo)
1679 1679 Session().commit()
1680 1680
1681 1681 @classmethod
1682 1682 def unlock(cls, repo):
1683 1683 repo.locked = None
1684 1684 Session().add(repo)
1685 1685 Session().commit()
1686 1686
1687 1687 @classmethod
1688 1688 def getlock(cls, repo):
1689 1689 return repo.locked
1690 1690
1691 1691 def is_user_lock(self, user_id):
1692 1692 if self.lock[0]:
1693 1693 lock_user_id = safe_int(self.lock[0])
1694 1694 user_id = safe_int(user_id)
1695 1695 # both are ints, and they are equal
1696 1696 return all([lock_user_id, user_id]) and lock_user_id == user_id
1697 1697
1698 1698 return False
1699 1699
1700 1700 def get_locking_state(self, action, user_id, only_when_enabled=True):
1701 1701 """
1702 1702 Checks locking on this repository, if locking is enabled and lock is
1703 1703 present returns a tuple of make_lock, locked, locked_by.
1704 1704 make_lock can have 3 states None (do nothing) True, make lock
1705 1705 False release lock, This value is later propagated to hooks, which
1706 1706 do the locking. Think about this as signals passed to hooks what to do.
1707 1707
1708 1708 """
1709 1709 # TODO: johbo: This is part of the business logic and should be moved
1710 1710 # into the RepositoryModel.
1711 1711
1712 1712 if action not in ('push', 'pull'):
1713 1713 raise ValueError("Invalid action value: %s" % repr(action))
1714 1714
1715 1715 # defines if locked error should be thrown to user
1716 1716 currently_locked = False
1717 1717 # defines if new lock should be made, tri-state
1718 1718 make_lock = None
1719 1719 repo = self
1720 1720 user = User.get(user_id)
1721 1721
1722 1722 lock_info = repo.locked
1723 1723
1724 1724 if repo and (repo.enable_locking or not only_when_enabled):
1725 1725 if action == 'push':
1726 1726 # check if it's already locked !, if it is compare users
1727 1727 locked_by_user_id = lock_info[0]
1728 1728 if user.user_id == locked_by_user_id:
1729 1729 log.debug(
1730 1730 'Got `push` action from user %s, now unlocking', user)
1731 1731 # unlock if we have push from user who locked
1732 1732 make_lock = False
1733 1733 else:
1734 1734 # we're not the same user who locked, ban with
1735 1735 # code defined in settings (default is 423 HTTP Locked) !
1736 1736 log.debug('Repo %s is currently locked by %s', repo, user)
1737 1737 currently_locked = True
1738 1738 elif action == 'pull':
1739 1739 # [0] user [1] date
1740 1740 if lock_info[0] and lock_info[1]:
1741 1741 log.debug('Repo %s is currently locked by %s', repo, user)
1742 1742 currently_locked = True
1743 1743 else:
1744 1744 log.debug('Setting lock on repo %s by %s', repo, user)
1745 1745 make_lock = True
1746 1746
1747 1747 else:
1748 1748 log.debug('Repository %s do not have locking enabled', repo)
1749 1749
1750 1750 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1751 1751 make_lock, currently_locked, lock_info)
1752 1752
1753 1753 from rhodecode.lib.auth import HasRepoPermissionAny
1754 1754 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1755 1755 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1756 1756 # if we don't have at least write permission we cannot make a lock
1757 1757 log.debug('lock state reset back to FALSE due to lack '
1758 1758 'of at least read permission')
1759 1759 make_lock = False
1760 1760
1761 1761 return make_lock, currently_locked, lock_info
1762 1762
1763 1763 @property
1764 1764 def last_db_change(self):
1765 1765 return self.updated_on
1766 1766
1767 1767 @property
1768 1768 def clone_uri_hidden(self):
1769 1769 clone_uri = self.clone_uri
1770 1770 if clone_uri:
1771 1771 import urlobject
1772 1772 url_obj = urlobject.URLObject(clone_uri)
1773 1773 if url_obj.password:
1774 1774 clone_uri = url_obj.with_password('*****')
1775 1775 return clone_uri
1776 1776
1777 1777 def clone_url(self, **override):
1778 1778 qualified_home_url = url('home', qualified=True)
1779 1779
1780 1780 uri_tmpl = None
1781 1781 if 'with_id' in override:
1782 1782 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1783 1783 del override['with_id']
1784 1784
1785 1785 if 'uri_tmpl' in override:
1786 1786 uri_tmpl = override['uri_tmpl']
1787 1787 del override['uri_tmpl']
1788 1788
1789 1789 # we didn't override our tmpl from **overrides
1790 1790 if not uri_tmpl:
1791 1791 uri_tmpl = self.DEFAULT_CLONE_URI
1792 1792 try:
1793 1793 from pylons import tmpl_context as c
1794 1794 uri_tmpl = c.clone_uri_tmpl
1795 1795 except Exception:
1796 1796 # in any case if we call this outside of request context,
1797 1797 # ie, not having tmpl_context set up
1798 1798 pass
1799 1799
1800 1800 return get_clone_url(uri_tmpl=uri_tmpl,
1801 1801 qualifed_home_url=qualified_home_url,
1802 1802 repo_name=self.repo_name,
1803 1803 repo_id=self.repo_id, **override)
1804 1804
1805 1805 def set_state(self, state):
1806 1806 self.repo_state = state
1807 1807 Session().add(self)
1808 1808 #==========================================================================
1809 1809 # SCM PROPERTIES
1810 1810 #==========================================================================
1811 1811
1812 1812 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1813 1813 return get_commit_safe(
1814 1814 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1815 1815
1816 1816 def get_changeset(self, rev=None, pre_load=None):
1817 1817 warnings.warn("Use get_commit", DeprecationWarning)
1818 1818 commit_id = None
1819 1819 commit_idx = None
1820 1820 if isinstance(rev, basestring):
1821 1821 commit_id = rev
1822 1822 else:
1823 1823 commit_idx = rev
1824 1824 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1825 1825 pre_load=pre_load)
1826 1826
1827 1827 def get_landing_commit(self):
1828 1828 """
1829 1829 Returns landing commit, or if that doesn't exist returns the tip
1830 1830 """
1831 1831 _rev_type, _rev = self.landing_rev
1832 1832 commit = self.get_commit(_rev)
1833 1833 if isinstance(commit, EmptyCommit):
1834 1834 return self.get_commit()
1835 1835 return commit
1836 1836
1837 1837 def update_commit_cache(self, cs_cache=None, config=None):
1838 1838 """
1839 1839 Update cache of last changeset for repository, keys should be::
1840 1840
1841 1841 short_id
1842 1842 raw_id
1843 1843 revision
1844 1844 parents
1845 1845 message
1846 1846 date
1847 1847 author
1848 1848
1849 1849 :param cs_cache:
1850 1850 """
1851 1851 from rhodecode.lib.vcs.backends.base import BaseChangeset
1852 1852 if cs_cache is None:
1853 1853 # use no-cache version here
1854 1854 scm_repo = self.scm_instance(cache=False, config=config)
1855 1855 if scm_repo:
1856 1856 cs_cache = scm_repo.get_commit(
1857 1857 pre_load=["author", "date", "message", "parents"])
1858 1858 else:
1859 1859 cs_cache = EmptyCommit()
1860 1860
1861 1861 if isinstance(cs_cache, BaseChangeset):
1862 1862 cs_cache = cs_cache.__json__()
1863 1863
1864 1864 def is_outdated(new_cs_cache):
1865 1865 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1866 1866 new_cs_cache['revision'] != self.changeset_cache['revision']):
1867 1867 return True
1868 1868 return False
1869 1869
1870 1870 # check if we have maybe already latest cached revision
1871 1871 if is_outdated(cs_cache) or not self.changeset_cache:
1872 1872 _default = datetime.datetime.fromtimestamp(0)
1873 1873 last_change = cs_cache.get('date') or _default
1874 1874 log.debug('updated repo %s with new cs cache %s',
1875 1875 self.repo_name, cs_cache)
1876 1876 self.updated_on = last_change
1877 1877 self.changeset_cache = cs_cache
1878 1878 Session().add(self)
1879 1879 Session().commit()
1880 1880 else:
1881 1881 log.debug('Skipping update_commit_cache for repo:`%s` '
1882 1882 'commit already with latest changes', self.repo_name)
1883 1883
1884 1884 @property
1885 1885 def tip(self):
1886 1886 return self.get_commit('tip')
1887 1887
1888 1888 @property
1889 1889 def author(self):
1890 1890 return self.tip.author
1891 1891
1892 1892 @property
1893 1893 def last_change(self):
1894 1894 return self.scm_instance().last_change
1895 1895
1896 1896 def get_comments(self, revisions=None):
1897 1897 """
1898 1898 Returns comments for this repository grouped by revisions
1899 1899
1900 1900 :param revisions: filter query by revisions only
1901 1901 """
1902 1902 cmts = ChangesetComment.query()\
1903 1903 .filter(ChangesetComment.repo == self)
1904 1904 if revisions:
1905 1905 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1906 1906 grouped = collections.defaultdict(list)
1907 1907 for cmt in cmts.all():
1908 1908 grouped[cmt.revision].append(cmt)
1909 1909 return grouped
1910 1910
1911 1911 def statuses(self, revisions=None):
1912 1912 """
1913 1913 Returns statuses for this repository
1914 1914
1915 1915 :param revisions: list of revisions to get statuses for
1916 1916 """
1917 1917 statuses = ChangesetStatus.query()\
1918 1918 .filter(ChangesetStatus.repo == self)\
1919 1919 .filter(ChangesetStatus.version == 0)
1920 1920
1921 1921 if revisions:
1922 1922 # Try doing the filtering in chunks to avoid hitting limits
1923 1923 size = 500
1924 1924 status_results = []
1925 1925 for chunk in xrange(0, len(revisions), size):
1926 1926 status_results += statuses.filter(
1927 1927 ChangesetStatus.revision.in_(
1928 1928 revisions[chunk: chunk+size])
1929 1929 ).all()
1930 1930 else:
1931 1931 status_results = statuses.all()
1932 1932
1933 1933 grouped = {}
1934 1934
1935 1935 # maybe we have open new pullrequest without a status?
1936 1936 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1937 1937 status_lbl = ChangesetStatus.get_status_lbl(stat)
1938 1938 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1939 1939 for rev in pr.revisions:
1940 1940 pr_id = pr.pull_request_id
1941 1941 pr_repo = pr.target_repo.repo_name
1942 1942 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1943 1943
1944 1944 for stat in status_results:
1945 1945 pr_id = pr_repo = None
1946 1946 if stat.pull_request:
1947 1947 pr_id = stat.pull_request.pull_request_id
1948 1948 pr_repo = stat.pull_request.target_repo.repo_name
1949 1949 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1950 1950 pr_id, pr_repo]
1951 1951 return grouped
1952 1952
1953 1953 # ==========================================================================
1954 1954 # SCM CACHE INSTANCE
1955 1955 # ==========================================================================
1956 1956
1957 1957 def scm_instance(self, **kwargs):
1958 1958 import rhodecode
1959 1959
1960 1960 # Passing a config will not hit the cache currently only used
1961 1961 # for repo2dbmapper
1962 1962 config = kwargs.pop('config', None)
1963 1963 cache = kwargs.pop('cache', None)
1964 1964 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1965 1965 # if cache is NOT defined use default global, else we have a full
1966 1966 # control over cache behaviour
1967 1967 if cache is None and full_cache and not config:
1968 1968 return self._get_instance_cached()
1969 1969 return self._get_instance(cache=bool(cache), config=config)
1970 1970
1971 1971 def _get_instance_cached(self):
1972 1972 @cache_region('long_term')
1973 1973 def _get_repo(cache_key):
1974 1974 return self._get_instance()
1975 1975
1976 1976 invalidator_context = CacheKey.repo_context_cache(
1977 1977 _get_repo, self.repo_name, None, thread_scoped=True)
1978 1978
1979 1979 with invalidator_context as context:
1980 1980 context.invalidate()
1981 1981 repo = context.compute()
1982 1982
1983 1983 return repo
1984 1984
1985 1985 def _get_instance(self, cache=True, config=None):
1986 1986 config = config or self._config
1987 1987 custom_wire = {
1988 1988 'cache': cache # controls the vcs.remote cache
1989 1989 }
1990 1990
1991 1991 repo = get_vcs_instance(
1992 1992 repo_path=safe_str(self.repo_full_path),
1993 1993 config=config,
1994 1994 with_wire=custom_wire,
1995 1995 create=False)
1996 1996
1997 1997 return repo
1998 1998
1999 1999 def __json__(self):
2000 2000 return {'landing_rev': self.landing_rev}
2001 2001
2002 2002 def get_dict(self):
2003 2003
2004 2004 # Since we transformed `repo_name` to a hybrid property, we need to
2005 2005 # keep compatibility with the code which uses `repo_name` field.
2006 2006
2007 2007 result = super(Repository, self).get_dict()
2008 2008 result['repo_name'] = result.pop('_repo_name', None)
2009 2009 return result
2010 2010
2011 2011
2012 2012 class RepoGroup(Base, BaseModel):
2013 2013 __tablename__ = 'groups'
2014 2014 __table_args__ = (
2015 2015 UniqueConstraint('group_name', 'group_parent_id'),
2016 2016 CheckConstraint('group_id != group_parent_id'),
2017 2017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2018 2018 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2019 2019 )
2020 2020 __mapper_args__ = {'order_by': 'group_name'}
2021 2021
2022 2022 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2023 2023
2024 2024 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2025 2025 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2026 2026 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2027 2027 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2028 2028 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2029 2029 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2030 2030 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2031 2031
2032 2032 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2033 2033 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2034 2034 parent_group = relationship('RepoGroup', remote_side=group_id)
2035 2035 user = relationship('User')
2036 2036 integrations = relationship('Integration',
2037 2037 cascade="all, delete, delete-orphan")
2038 2038
2039 2039 def __init__(self, group_name='', parent_group=None):
2040 2040 self.group_name = group_name
2041 2041 self.parent_group = parent_group
2042 2042
2043 2043 def __unicode__(self):
2044 2044 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2045 2045 self.group_name)
2046 2046
2047 2047 @classmethod
2048 2048 def _generate_choice(cls, repo_group):
2049 2049 from webhelpers.html import literal as _literal
2050 2050 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2051 2051 return repo_group.group_id, _name(repo_group.full_path_splitted)
2052 2052
2053 2053 @classmethod
2054 2054 def groups_choices(cls, groups=None, show_empty_group=True):
2055 2055 if not groups:
2056 2056 groups = cls.query().all()
2057 2057
2058 2058 repo_groups = []
2059 2059 if show_empty_group:
2060 2060 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2061 2061
2062 2062 repo_groups.extend([cls._generate_choice(x) for x in groups])
2063 2063
2064 2064 repo_groups = sorted(
2065 2065 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2066 2066 return repo_groups
2067 2067
2068 2068 @classmethod
2069 2069 def url_sep(cls):
2070 2070 return URL_SEP
2071 2071
2072 2072 @classmethod
2073 2073 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2074 2074 if case_insensitive:
2075 2075 gr = cls.query().filter(func.lower(cls.group_name)
2076 2076 == func.lower(group_name))
2077 2077 else:
2078 2078 gr = cls.query().filter(cls.group_name == group_name)
2079 2079 if cache:
2080 2080 gr = gr.options(FromCache(
2081 2081 "sql_cache_short",
2082 2082 "get_group_%s" % _hash_key(group_name)))
2083 2083 return gr.scalar()
2084 2084
2085 2085 @classmethod
2086 2086 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2087 2087 case_insensitive=True):
2088 2088 q = RepoGroup.query()
2089 2089
2090 2090 if not isinstance(user_id, Optional):
2091 2091 q = q.filter(RepoGroup.user_id == user_id)
2092 2092
2093 2093 if not isinstance(group_id, Optional):
2094 2094 q = q.filter(RepoGroup.group_parent_id == group_id)
2095 2095
2096 2096 if case_insensitive:
2097 2097 q = q.order_by(func.lower(RepoGroup.group_name))
2098 2098 else:
2099 2099 q = q.order_by(RepoGroup.group_name)
2100 2100 return q.all()
2101 2101
2102 2102 @property
2103 2103 def parents(self):
2104 2104 parents_recursion_limit = 10
2105 2105 groups = []
2106 2106 if self.parent_group is None:
2107 2107 return groups
2108 2108 cur_gr = self.parent_group
2109 2109 groups.insert(0, cur_gr)
2110 2110 cnt = 0
2111 2111 while 1:
2112 2112 cnt += 1
2113 2113 gr = getattr(cur_gr, 'parent_group', None)
2114 2114 cur_gr = cur_gr.parent_group
2115 2115 if gr is None:
2116 2116 break
2117 2117 if cnt == parents_recursion_limit:
2118 2118 # this will prevent accidental infinit loops
2119 2119 log.error(('more than %s parents found for group %s, stopping '
2120 2120 'recursive parent fetching' % (parents_recursion_limit, self)))
2121 2121 break
2122 2122
2123 2123 groups.insert(0, gr)
2124 2124 return groups
2125 2125
2126 2126 @property
2127 2127 def children(self):
2128 2128 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2129 2129
2130 2130 @property
2131 2131 def name(self):
2132 2132 return self.group_name.split(RepoGroup.url_sep())[-1]
2133 2133
2134 2134 @property
2135 2135 def full_path(self):
2136 2136 return self.group_name
2137 2137
2138 2138 @property
2139 2139 def full_path_splitted(self):
2140 2140 return self.group_name.split(RepoGroup.url_sep())
2141 2141
2142 2142 @property
2143 2143 def repositories(self):
2144 2144 return Repository.query()\
2145 2145 .filter(Repository.group == self)\
2146 2146 .order_by(Repository.repo_name)
2147 2147
2148 2148 @property
2149 2149 def repositories_recursive_count(self):
2150 2150 cnt = self.repositories.count()
2151 2151
2152 2152 def children_count(group):
2153 2153 cnt = 0
2154 2154 for child in group.children:
2155 2155 cnt += child.repositories.count()
2156 2156 cnt += children_count(child)
2157 2157 return cnt
2158 2158
2159 2159 return cnt + children_count(self)
2160 2160
2161 2161 def _recursive_objects(self, include_repos=True):
2162 2162 all_ = []
2163 2163
2164 2164 def _get_members(root_gr):
2165 2165 if include_repos:
2166 2166 for r in root_gr.repositories:
2167 2167 all_.append(r)
2168 2168 childs = root_gr.children.all()
2169 2169 if childs:
2170 2170 for gr in childs:
2171 2171 all_.append(gr)
2172 2172 _get_members(gr)
2173 2173
2174 2174 _get_members(self)
2175 2175 return [self] + all_
2176 2176
2177 2177 def recursive_groups_and_repos(self):
2178 2178 """
2179 2179 Recursive return all groups, with repositories in those groups
2180 2180 """
2181 2181 return self._recursive_objects()
2182 2182
2183 2183 def recursive_groups(self):
2184 2184 """
2185 2185 Returns all children groups for this group including children of children
2186 2186 """
2187 2187 return self._recursive_objects(include_repos=False)
2188 2188
2189 2189 def get_new_name(self, group_name):
2190 2190 """
2191 2191 returns new full group name based on parent and new name
2192 2192
2193 2193 :param group_name:
2194 2194 """
2195 2195 path_prefix = (self.parent_group.full_path_splitted if
2196 2196 self.parent_group else [])
2197 2197 return RepoGroup.url_sep().join(path_prefix + [group_name])
2198 2198
2199 2199 def permissions(self, with_admins=True, with_owner=True):
2200 2200 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2201 2201 q = q.options(joinedload(UserRepoGroupToPerm.group),
2202 2202 joinedload(UserRepoGroupToPerm.user),
2203 2203 joinedload(UserRepoGroupToPerm.permission),)
2204 2204
2205 2205 # get owners and admins and permissions. We do a trick of re-writing
2206 2206 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2207 2207 # has a global reference and changing one object propagates to all
2208 2208 # others. This means if admin is also an owner admin_row that change
2209 2209 # would propagate to both objects
2210 2210 perm_rows = []
2211 2211 for _usr in q.all():
2212 2212 usr = AttributeDict(_usr.user.get_dict())
2213 2213 usr.permission = _usr.permission.permission_name
2214 2214 perm_rows.append(usr)
2215 2215
2216 2216 # filter the perm rows by 'default' first and then sort them by
2217 2217 # admin,write,read,none permissions sorted again alphabetically in
2218 2218 # each group
2219 2219 perm_rows = sorted(perm_rows, key=display_sort)
2220 2220
2221 2221 _admin_perm = 'group.admin'
2222 2222 owner_row = []
2223 2223 if with_owner:
2224 2224 usr = AttributeDict(self.user.get_dict())
2225 2225 usr.owner_row = True
2226 2226 usr.permission = _admin_perm
2227 2227 owner_row.append(usr)
2228 2228
2229 2229 super_admin_rows = []
2230 2230 if with_admins:
2231 2231 for usr in User.get_all_super_admins():
2232 2232 # if this admin is also owner, don't double the record
2233 2233 if usr.user_id == owner_row[0].user_id:
2234 2234 owner_row[0].admin_row = True
2235 2235 else:
2236 2236 usr = AttributeDict(usr.get_dict())
2237 2237 usr.admin_row = True
2238 2238 usr.permission = _admin_perm
2239 2239 super_admin_rows.append(usr)
2240 2240
2241 2241 return super_admin_rows + owner_row + perm_rows
2242 2242
2243 2243 def permission_user_groups(self):
2244 2244 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2245 2245 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2246 2246 joinedload(UserGroupRepoGroupToPerm.users_group),
2247 2247 joinedload(UserGroupRepoGroupToPerm.permission),)
2248 2248
2249 2249 perm_rows = []
2250 2250 for _user_group in q.all():
2251 2251 usr = AttributeDict(_user_group.users_group.get_dict())
2252 2252 usr.permission = _user_group.permission.permission_name
2253 2253 perm_rows.append(usr)
2254 2254
2255 2255 return perm_rows
2256 2256
2257 2257 def get_api_data(self):
2258 2258 """
2259 2259 Common function for generating api data
2260 2260
2261 2261 """
2262 2262 group = self
2263 2263 data = {
2264 2264 'group_id': group.group_id,
2265 2265 'group_name': group.group_name,
2266 2266 'group_description': group.group_description,
2267 2267 'parent_group': group.parent_group.group_name if group.parent_group else None,
2268 2268 'repositories': [x.repo_name for x in group.repositories],
2269 2269 'owner': group.user.username,
2270 2270 }
2271 2271 return data
2272 2272
2273 2273
2274 2274 class Permission(Base, BaseModel):
2275 2275 __tablename__ = 'permissions'
2276 2276 __table_args__ = (
2277 2277 Index('p_perm_name_idx', 'permission_name'),
2278 2278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2279 2279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2280 2280 )
2281 2281 PERMS = [
2282 2282 ('hg.admin', _('RhodeCode Super Administrator')),
2283 2283
2284 2284 ('repository.none', _('Repository no access')),
2285 2285 ('repository.read', _('Repository read access')),
2286 2286 ('repository.write', _('Repository write access')),
2287 2287 ('repository.admin', _('Repository admin access')),
2288 2288
2289 2289 ('group.none', _('Repository group no access')),
2290 2290 ('group.read', _('Repository group read access')),
2291 2291 ('group.write', _('Repository group write access')),
2292 2292 ('group.admin', _('Repository group admin access')),
2293 2293
2294 2294 ('usergroup.none', _('User group no access')),
2295 2295 ('usergroup.read', _('User group read access')),
2296 2296 ('usergroup.write', _('User group write access')),
2297 2297 ('usergroup.admin', _('User group admin access')),
2298 2298
2299 2299 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2300 2300 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2301 2301
2302 2302 ('hg.usergroup.create.false', _('User Group creation disabled')),
2303 2303 ('hg.usergroup.create.true', _('User Group creation enabled')),
2304 2304
2305 2305 ('hg.create.none', _('Repository creation disabled')),
2306 2306 ('hg.create.repository', _('Repository creation enabled')),
2307 2307 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2308 2308 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2309 2309
2310 2310 ('hg.fork.none', _('Repository forking disabled')),
2311 2311 ('hg.fork.repository', _('Repository forking enabled')),
2312 2312
2313 2313 ('hg.register.none', _('Registration disabled')),
2314 2314 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2315 2315 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2316 2316
2317 ('hg.password_reset.enabled', _('Password reset enabled')),
2318 ('hg.password_reset.hidden', _('Password reset hidden')),
2319 ('hg.password_reset.disabled', _('Password reset disabled')),
2320
2317 2321 ('hg.extern_activate.manual', _('Manual activation of external account')),
2318 2322 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2319 2323
2320 2324 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2321 2325 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2322 2326 ]
2323 2327
2324 2328 # definition of system default permissions for DEFAULT user
2325 2329 DEFAULT_USER_PERMISSIONS = [
2326 2330 'repository.read',
2327 2331 'group.read',
2328 2332 'usergroup.read',
2329 2333 'hg.create.repository',
2330 2334 'hg.repogroup.create.false',
2331 2335 'hg.usergroup.create.false',
2332 2336 'hg.create.write_on_repogroup.true',
2333 2337 'hg.fork.repository',
2334 2338 'hg.register.manual_activate',
2339 'hg.password_reset.enabled',
2335 2340 'hg.extern_activate.auto',
2336 2341 'hg.inherit_default_perms.true',
2337 2342 ]
2338 2343
2339 2344 # defines which permissions are more important higher the more important
2340 2345 # Weight defines which permissions are more important.
2341 2346 # The higher number the more important.
2342 2347 PERM_WEIGHTS = {
2343 2348 'repository.none': 0,
2344 2349 'repository.read': 1,
2345 2350 'repository.write': 3,
2346 2351 'repository.admin': 4,
2347 2352
2348 2353 'group.none': 0,
2349 2354 'group.read': 1,
2350 2355 'group.write': 3,
2351 2356 'group.admin': 4,
2352 2357
2353 2358 'usergroup.none': 0,
2354 2359 'usergroup.read': 1,
2355 2360 'usergroup.write': 3,
2356 2361 'usergroup.admin': 4,
2357 2362
2358 2363 'hg.repogroup.create.false': 0,
2359 2364 'hg.repogroup.create.true': 1,
2360 2365
2361 2366 'hg.usergroup.create.false': 0,
2362 2367 'hg.usergroup.create.true': 1,
2363 2368
2364 2369 'hg.fork.none': 0,
2365 2370 'hg.fork.repository': 1,
2366 2371 'hg.create.none': 0,
2367 2372 'hg.create.repository': 1
2368 2373 }
2369 2374
2370 2375 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2371 2376 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2372 2377 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2373 2378
2374 2379 def __unicode__(self):
2375 2380 return u"<%s('%s:%s')>" % (
2376 2381 self.__class__.__name__, self.permission_id, self.permission_name
2377 2382 )
2378 2383
2379 2384 @classmethod
2380 2385 def get_by_key(cls, key):
2381 2386 return cls.query().filter(cls.permission_name == key).scalar()
2382 2387
2383 2388 @classmethod
2384 2389 def get_default_repo_perms(cls, user_id, repo_id=None):
2385 2390 q = Session().query(UserRepoToPerm, Repository, Permission)\
2386 2391 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2387 2392 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2388 2393 .filter(UserRepoToPerm.user_id == user_id)
2389 2394 if repo_id:
2390 2395 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2391 2396 return q.all()
2392 2397
2393 2398 @classmethod
2394 2399 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2395 2400 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2396 2401 .join(
2397 2402 Permission,
2398 2403 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2399 2404 .join(
2400 2405 Repository,
2401 2406 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2402 2407 .join(
2403 2408 UserGroup,
2404 2409 UserGroupRepoToPerm.users_group_id ==
2405 2410 UserGroup.users_group_id)\
2406 2411 .join(
2407 2412 UserGroupMember,
2408 2413 UserGroupRepoToPerm.users_group_id ==
2409 2414 UserGroupMember.users_group_id)\
2410 2415 .filter(
2411 2416 UserGroupMember.user_id == user_id,
2412 2417 UserGroup.users_group_active == true())
2413 2418 if repo_id:
2414 2419 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2415 2420 return q.all()
2416 2421
2417 2422 @classmethod
2418 2423 def get_default_group_perms(cls, user_id, repo_group_id=None):
2419 2424 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2420 2425 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2421 2426 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2422 2427 .filter(UserRepoGroupToPerm.user_id == user_id)
2423 2428 if repo_group_id:
2424 2429 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2425 2430 return q.all()
2426 2431
2427 2432 @classmethod
2428 2433 def get_default_group_perms_from_user_group(
2429 2434 cls, user_id, repo_group_id=None):
2430 2435 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2431 2436 .join(
2432 2437 Permission,
2433 2438 UserGroupRepoGroupToPerm.permission_id ==
2434 2439 Permission.permission_id)\
2435 2440 .join(
2436 2441 RepoGroup,
2437 2442 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2438 2443 .join(
2439 2444 UserGroup,
2440 2445 UserGroupRepoGroupToPerm.users_group_id ==
2441 2446 UserGroup.users_group_id)\
2442 2447 .join(
2443 2448 UserGroupMember,
2444 2449 UserGroupRepoGroupToPerm.users_group_id ==
2445 2450 UserGroupMember.users_group_id)\
2446 2451 .filter(
2447 2452 UserGroupMember.user_id == user_id,
2448 2453 UserGroup.users_group_active == true())
2449 2454 if repo_group_id:
2450 2455 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2451 2456 return q.all()
2452 2457
2453 2458 @classmethod
2454 2459 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2455 2460 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2456 2461 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2457 2462 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2458 2463 .filter(UserUserGroupToPerm.user_id == user_id)
2459 2464 if user_group_id:
2460 2465 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2461 2466 return q.all()
2462 2467
2463 2468 @classmethod
2464 2469 def get_default_user_group_perms_from_user_group(
2465 2470 cls, user_id, user_group_id=None):
2466 2471 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2467 2472 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2468 2473 .join(
2469 2474 Permission,
2470 2475 UserGroupUserGroupToPerm.permission_id ==
2471 2476 Permission.permission_id)\
2472 2477 .join(
2473 2478 TargetUserGroup,
2474 2479 UserGroupUserGroupToPerm.target_user_group_id ==
2475 2480 TargetUserGroup.users_group_id)\
2476 2481 .join(
2477 2482 UserGroup,
2478 2483 UserGroupUserGroupToPerm.user_group_id ==
2479 2484 UserGroup.users_group_id)\
2480 2485 .join(
2481 2486 UserGroupMember,
2482 2487 UserGroupUserGroupToPerm.user_group_id ==
2483 2488 UserGroupMember.users_group_id)\
2484 2489 .filter(
2485 2490 UserGroupMember.user_id == user_id,
2486 2491 UserGroup.users_group_active == true())
2487 2492 if user_group_id:
2488 2493 q = q.filter(
2489 2494 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2490 2495
2491 2496 return q.all()
2492 2497
2493 2498
2494 2499 class UserRepoToPerm(Base, BaseModel):
2495 2500 __tablename__ = 'repo_to_perm'
2496 2501 __table_args__ = (
2497 2502 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2498 2503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2499 2504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2500 2505 )
2501 2506 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2502 2507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2503 2508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2504 2509 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2505 2510
2506 2511 user = relationship('User')
2507 2512 repository = relationship('Repository')
2508 2513 permission = relationship('Permission')
2509 2514
2510 2515 @classmethod
2511 2516 def create(cls, user, repository, permission):
2512 2517 n = cls()
2513 2518 n.user = user
2514 2519 n.repository = repository
2515 2520 n.permission = permission
2516 2521 Session().add(n)
2517 2522 return n
2518 2523
2519 2524 def __unicode__(self):
2520 2525 return u'<%s => %s >' % (self.user, self.repository)
2521 2526
2522 2527
2523 2528 class UserUserGroupToPerm(Base, BaseModel):
2524 2529 __tablename__ = 'user_user_group_to_perm'
2525 2530 __table_args__ = (
2526 2531 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2527 2532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2528 2533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2529 2534 )
2530 2535 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2531 2536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2532 2537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2533 2538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2534 2539
2535 2540 user = relationship('User')
2536 2541 user_group = relationship('UserGroup')
2537 2542 permission = relationship('Permission')
2538 2543
2539 2544 @classmethod
2540 2545 def create(cls, user, user_group, permission):
2541 2546 n = cls()
2542 2547 n.user = user
2543 2548 n.user_group = user_group
2544 2549 n.permission = permission
2545 2550 Session().add(n)
2546 2551 return n
2547 2552
2548 2553 def __unicode__(self):
2549 2554 return u'<%s => %s >' % (self.user, self.user_group)
2550 2555
2551 2556
2552 2557 class UserToPerm(Base, BaseModel):
2553 2558 __tablename__ = 'user_to_perm'
2554 2559 __table_args__ = (
2555 2560 UniqueConstraint('user_id', 'permission_id'),
2556 2561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2557 2562 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2558 2563 )
2559 2564 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2560 2565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2561 2566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2562 2567
2563 2568 user = relationship('User')
2564 2569 permission = relationship('Permission', lazy='joined')
2565 2570
2566 2571 def __unicode__(self):
2567 2572 return u'<%s => %s >' % (self.user, self.permission)
2568 2573
2569 2574
2570 2575 class UserGroupRepoToPerm(Base, BaseModel):
2571 2576 __tablename__ = 'users_group_repo_to_perm'
2572 2577 __table_args__ = (
2573 2578 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2574 2579 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2575 2580 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2576 2581 )
2577 2582 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2578 2583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2579 2584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2580 2585 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2581 2586
2582 2587 users_group = relationship('UserGroup')
2583 2588 permission = relationship('Permission')
2584 2589 repository = relationship('Repository')
2585 2590
2586 2591 @classmethod
2587 2592 def create(cls, users_group, repository, permission):
2588 2593 n = cls()
2589 2594 n.users_group = users_group
2590 2595 n.repository = repository
2591 2596 n.permission = permission
2592 2597 Session().add(n)
2593 2598 return n
2594 2599
2595 2600 def __unicode__(self):
2596 2601 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2597 2602
2598 2603
2599 2604 class UserGroupUserGroupToPerm(Base, BaseModel):
2600 2605 __tablename__ = 'user_group_user_group_to_perm'
2601 2606 __table_args__ = (
2602 2607 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2603 2608 CheckConstraint('target_user_group_id != user_group_id'),
2604 2609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2605 2610 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2606 2611 )
2607 2612 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)
2608 2613 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2609 2614 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2610 2615 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2611 2616
2612 2617 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2613 2618 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2614 2619 permission = relationship('Permission')
2615 2620
2616 2621 @classmethod
2617 2622 def create(cls, target_user_group, user_group, permission):
2618 2623 n = cls()
2619 2624 n.target_user_group = target_user_group
2620 2625 n.user_group = user_group
2621 2626 n.permission = permission
2622 2627 Session().add(n)
2623 2628 return n
2624 2629
2625 2630 def __unicode__(self):
2626 2631 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2627 2632
2628 2633
2629 2634 class UserGroupToPerm(Base, BaseModel):
2630 2635 __tablename__ = 'users_group_to_perm'
2631 2636 __table_args__ = (
2632 2637 UniqueConstraint('users_group_id', 'permission_id',),
2633 2638 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2634 2639 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2635 2640 )
2636 2641 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2637 2642 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2638 2643 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2639 2644
2640 2645 users_group = relationship('UserGroup')
2641 2646 permission = relationship('Permission')
2642 2647
2643 2648
2644 2649 class UserRepoGroupToPerm(Base, BaseModel):
2645 2650 __tablename__ = 'user_repo_group_to_perm'
2646 2651 __table_args__ = (
2647 2652 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2648 2653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2649 2654 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2650 2655 )
2651 2656
2652 2657 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2653 2658 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2654 2659 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2655 2660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2656 2661
2657 2662 user = relationship('User')
2658 2663 group = relationship('RepoGroup')
2659 2664 permission = relationship('Permission')
2660 2665
2661 2666 @classmethod
2662 2667 def create(cls, user, repository_group, permission):
2663 2668 n = cls()
2664 2669 n.user = user
2665 2670 n.group = repository_group
2666 2671 n.permission = permission
2667 2672 Session().add(n)
2668 2673 return n
2669 2674
2670 2675
2671 2676 class UserGroupRepoGroupToPerm(Base, BaseModel):
2672 2677 __tablename__ = 'users_group_repo_group_to_perm'
2673 2678 __table_args__ = (
2674 2679 UniqueConstraint('users_group_id', 'group_id'),
2675 2680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2676 2681 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2677 2682 )
2678 2683
2679 2684 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)
2680 2685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2681 2686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2682 2687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2683 2688
2684 2689 users_group = relationship('UserGroup')
2685 2690 permission = relationship('Permission')
2686 2691 group = relationship('RepoGroup')
2687 2692
2688 2693 @classmethod
2689 2694 def create(cls, user_group, repository_group, permission):
2690 2695 n = cls()
2691 2696 n.users_group = user_group
2692 2697 n.group = repository_group
2693 2698 n.permission = permission
2694 2699 Session().add(n)
2695 2700 return n
2696 2701
2697 2702 def __unicode__(self):
2698 2703 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2699 2704
2700 2705
2701 2706 class Statistics(Base, BaseModel):
2702 2707 __tablename__ = 'statistics'
2703 2708 __table_args__ = (
2704 2709 UniqueConstraint('repository_id'),
2705 2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2706 2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2707 2712 )
2708 2713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2709 2714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2710 2715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2711 2716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2712 2717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2713 2718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2714 2719
2715 2720 repository = relationship('Repository', single_parent=True)
2716 2721
2717 2722
2718 2723 class UserFollowing(Base, BaseModel):
2719 2724 __tablename__ = 'user_followings'
2720 2725 __table_args__ = (
2721 2726 UniqueConstraint('user_id', 'follows_repository_id'),
2722 2727 UniqueConstraint('user_id', 'follows_user_id'),
2723 2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2724 2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2725 2730 )
2726 2731
2727 2732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2728 2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2729 2734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2730 2735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2731 2736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2732 2737
2733 2738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2734 2739
2735 2740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2736 2741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2737 2742
2738 2743 @classmethod
2739 2744 def get_repo_followers(cls, repo_id):
2740 2745 return cls.query().filter(cls.follows_repo_id == repo_id)
2741 2746
2742 2747
2743 2748 class CacheKey(Base, BaseModel):
2744 2749 __tablename__ = 'cache_invalidation'
2745 2750 __table_args__ = (
2746 2751 UniqueConstraint('cache_key'),
2747 2752 Index('key_idx', 'cache_key'),
2748 2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2749 2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2750 2755 )
2751 2756 CACHE_TYPE_ATOM = 'ATOM'
2752 2757 CACHE_TYPE_RSS = 'RSS'
2753 2758 CACHE_TYPE_README = 'README'
2754 2759
2755 2760 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2756 2761 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2757 2762 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2758 2763 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2759 2764
2760 2765 def __init__(self, cache_key, cache_args=''):
2761 2766 self.cache_key = cache_key
2762 2767 self.cache_args = cache_args
2763 2768 self.cache_active = False
2764 2769
2765 2770 def __unicode__(self):
2766 2771 return u"<%s('%s:%s[%s]')>" % (
2767 2772 self.__class__.__name__,
2768 2773 self.cache_id, self.cache_key, self.cache_active)
2769 2774
2770 2775 def _cache_key_partition(self):
2771 2776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2772 2777 return prefix, repo_name, suffix
2773 2778
2774 2779 def get_prefix(self):
2775 2780 """
2776 2781 Try to extract prefix from existing cache key. The key could consist
2777 2782 of prefix, repo_name, suffix
2778 2783 """
2779 2784 # this returns prefix, repo_name, suffix
2780 2785 return self._cache_key_partition()[0]
2781 2786
2782 2787 def get_suffix(self):
2783 2788 """
2784 2789 get suffix that might have been used in _get_cache_key to
2785 2790 generate self.cache_key. Only used for informational purposes
2786 2791 in repo_edit.html.
2787 2792 """
2788 2793 # prefix, repo_name, suffix
2789 2794 return self._cache_key_partition()[2]
2790 2795
2791 2796 @classmethod
2792 2797 def delete_all_cache(cls):
2793 2798 """
2794 2799 Delete all cache keys from database.
2795 2800 Should only be run when all instances are down and all entries
2796 2801 thus stale.
2797 2802 """
2798 2803 cls.query().delete()
2799 2804 Session().commit()
2800 2805
2801 2806 @classmethod
2802 2807 def get_cache_key(cls, repo_name, cache_type):
2803 2808 """
2804 2809
2805 2810 Generate a cache key for this process of RhodeCode instance.
2806 2811 Prefix most likely will be process id or maybe explicitly set
2807 2812 instance_id from .ini file.
2808 2813 """
2809 2814 import rhodecode
2810 2815 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2811 2816
2812 2817 repo_as_unicode = safe_unicode(repo_name)
2813 2818 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2814 2819 if cache_type else repo_as_unicode
2815 2820
2816 2821 return u'{}{}'.format(prefix, key)
2817 2822
2818 2823 @classmethod
2819 2824 def set_invalidate(cls, repo_name, delete=False):
2820 2825 """
2821 2826 Mark all caches of a repo as invalid in the database.
2822 2827 """
2823 2828
2824 2829 try:
2825 2830 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2826 2831 if delete:
2827 2832 log.debug('cache objects deleted for repo %s',
2828 2833 safe_str(repo_name))
2829 2834 qry.delete()
2830 2835 else:
2831 2836 log.debug('cache objects marked as invalid for repo %s',
2832 2837 safe_str(repo_name))
2833 2838 qry.update({"cache_active": False})
2834 2839
2835 2840 Session().commit()
2836 2841 except Exception:
2837 2842 log.exception(
2838 2843 'Cache key invalidation failed for repository %s',
2839 2844 safe_str(repo_name))
2840 2845 Session().rollback()
2841 2846
2842 2847 @classmethod
2843 2848 def get_active_cache(cls, cache_key):
2844 2849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2845 2850 if inv_obj:
2846 2851 return inv_obj
2847 2852 return None
2848 2853
2849 2854 @classmethod
2850 2855 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2851 2856 thread_scoped=False):
2852 2857 """
2853 2858 @cache_region('long_term')
2854 2859 def _heavy_calculation(cache_key):
2855 2860 return 'result'
2856 2861
2857 2862 cache_context = CacheKey.repo_context_cache(
2858 2863 _heavy_calculation, repo_name, cache_type)
2859 2864
2860 2865 with cache_context as context:
2861 2866 context.invalidate()
2862 2867 computed = context.compute()
2863 2868
2864 2869 assert computed == 'result'
2865 2870 """
2866 2871 from rhodecode.lib import caches
2867 2872 return caches.InvalidationContext(
2868 2873 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2869 2874
2870 2875
2871 2876 class ChangesetComment(Base, BaseModel):
2872 2877 __tablename__ = 'changeset_comments'
2873 2878 __table_args__ = (
2874 2879 Index('cc_revision_idx', 'revision'),
2875 2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2876 2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2877 2882 )
2878 2883
2879 2884 COMMENT_OUTDATED = u'comment_outdated'
2880 2885
2881 2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2882 2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2883 2888 revision = Column('revision', String(40), nullable=True)
2884 2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2885 2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2886 2891 line_no = Column('line_no', Unicode(10), nullable=True)
2887 2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2888 2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2889 2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2890 2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2891 2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2892 2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2893 2898 renderer = Column('renderer', Unicode(64), nullable=True)
2894 2899 display_state = Column('display_state', Unicode(128), nullable=True)
2895 2900
2896 2901 author = relationship('User', lazy='joined')
2897 2902 repo = relationship('Repository')
2898 2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2899 2904 pull_request = relationship('PullRequest', lazy='joined')
2900 2905 pull_request_version = relationship('PullRequestVersion')
2901 2906
2902 2907 @classmethod
2903 2908 def get_users(cls, revision=None, pull_request_id=None):
2904 2909 """
2905 2910 Returns user associated with this ChangesetComment. ie those
2906 2911 who actually commented
2907 2912
2908 2913 :param cls:
2909 2914 :param revision:
2910 2915 """
2911 2916 q = Session().query(User)\
2912 2917 .join(ChangesetComment.author)
2913 2918 if revision:
2914 2919 q = q.filter(cls.revision == revision)
2915 2920 elif pull_request_id:
2916 2921 q = q.filter(cls.pull_request_id == pull_request_id)
2917 2922 return q.all()
2918 2923
2919 2924 def render(self, mentions=False):
2920 2925 from rhodecode.lib import helpers as h
2921 2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2922 2927
2923 2928 def __repr__(self):
2924 2929 if self.comment_id:
2925 2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2926 2931 else:
2927 2932 return '<DB:ChangesetComment at %#x>' % id(self)
2928 2933
2929 2934
2930 2935 class ChangesetStatus(Base, BaseModel):
2931 2936 __tablename__ = 'changeset_statuses'
2932 2937 __table_args__ = (
2933 2938 Index('cs_revision_idx', 'revision'),
2934 2939 Index('cs_version_idx', 'version'),
2935 2940 UniqueConstraint('repo_id', 'revision', 'version'),
2936 2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2937 2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2938 2943 )
2939 2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2940 2945 STATUS_APPROVED = 'approved'
2941 2946 STATUS_REJECTED = 'rejected'
2942 2947 STATUS_UNDER_REVIEW = 'under_review'
2943 2948
2944 2949 STATUSES = [
2945 2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2946 2951 (STATUS_APPROVED, _("Approved")),
2947 2952 (STATUS_REJECTED, _("Rejected")),
2948 2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2949 2954 ]
2950 2955
2951 2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2952 2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2953 2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2954 2959 revision = Column('revision', String(40), nullable=False)
2955 2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2956 2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2957 2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2958 2963 version = Column('version', Integer(), nullable=False, default=0)
2959 2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2960 2965
2961 2966 author = relationship('User', lazy='joined')
2962 2967 repo = relationship('Repository')
2963 2968 comment = relationship('ChangesetComment', lazy='joined')
2964 2969 pull_request = relationship('PullRequest', lazy='joined')
2965 2970
2966 2971 def __unicode__(self):
2967 2972 return u"<%s('%s[%s]:%s')>" % (
2968 2973 self.__class__.__name__,
2969 2974 self.status, self.version, self.author
2970 2975 )
2971 2976
2972 2977 @classmethod
2973 2978 def get_status_lbl(cls, value):
2974 2979 return dict(cls.STATUSES).get(value)
2975 2980
2976 2981 @property
2977 2982 def status_lbl(self):
2978 2983 return ChangesetStatus.get_status_lbl(self.status)
2979 2984
2980 2985
2981 2986 class _PullRequestBase(BaseModel):
2982 2987 """
2983 2988 Common attributes of pull request and version entries.
2984 2989 """
2985 2990
2986 2991 # .status values
2987 2992 STATUS_NEW = u'new'
2988 2993 STATUS_OPEN = u'open'
2989 2994 STATUS_CLOSED = u'closed'
2990 2995
2991 2996 title = Column('title', Unicode(255), nullable=True)
2992 2997 description = Column(
2993 2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2994 2999 nullable=True)
2995 3000 # new/open/closed status of pull request (not approve/reject/etc)
2996 3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2997 3002 created_on = Column(
2998 3003 'created_on', DateTime(timezone=False), nullable=False,
2999 3004 default=datetime.datetime.now)
3000 3005 updated_on = Column(
3001 3006 'updated_on', DateTime(timezone=False), nullable=False,
3002 3007 default=datetime.datetime.now)
3003 3008
3004 3009 @declared_attr
3005 3010 def user_id(cls):
3006 3011 return Column(
3007 3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3008 3013 unique=None)
3009 3014
3010 3015 # 500 revisions max
3011 3016 _revisions = Column(
3012 3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3013 3018
3014 3019 @declared_attr
3015 3020 def source_repo_id(cls):
3016 3021 # TODO: dan: rename column to source_repo_id
3017 3022 return Column(
3018 3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3019 3024 nullable=False)
3020 3025
3021 3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3022 3027
3023 3028 @declared_attr
3024 3029 def target_repo_id(cls):
3025 3030 # TODO: dan: rename column to target_repo_id
3026 3031 return Column(
3027 3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3028 3033 nullable=False)
3029 3034
3030 3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3031 3036
3032 3037 # TODO: dan: rename column to last_merge_source_rev
3033 3038 _last_merge_source_rev = Column(
3034 3039 'last_merge_org_rev', String(40), nullable=True)
3035 3040 # TODO: dan: rename column to last_merge_target_rev
3036 3041 _last_merge_target_rev = Column(
3037 3042 'last_merge_other_rev', String(40), nullable=True)
3038 3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3039 3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3040 3045
3041 3046 @hybrid_property
3042 3047 def revisions(self):
3043 3048 return self._revisions.split(':') if self._revisions else []
3044 3049
3045 3050 @revisions.setter
3046 3051 def revisions(self, val):
3047 3052 self._revisions = ':'.join(val)
3048 3053
3049 3054 @declared_attr
3050 3055 def author(cls):
3051 3056 return relationship('User', lazy='joined')
3052 3057
3053 3058 @declared_attr
3054 3059 def source_repo(cls):
3055 3060 return relationship(
3056 3061 'Repository',
3057 3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3058 3063
3059 3064 @property
3060 3065 def source_ref_parts(self):
3061 3066 refs = self.source_ref.split(':')
3062 3067 return Reference(refs[0], refs[1], refs[2])
3063 3068
3064 3069 @declared_attr
3065 3070 def target_repo(cls):
3066 3071 return relationship(
3067 3072 'Repository',
3068 3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3069 3074
3070 3075 @property
3071 3076 def target_ref_parts(self):
3072 3077 refs = self.target_ref.split(':')
3073 3078 return Reference(refs[0], refs[1], refs[2])
3074 3079
3075 3080
3076 3081 class PullRequest(Base, _PullRequestBase):
3077 3082 __tablename__ = 'pull_requests'
3078 3083 __table_args__ = (
3079 3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3080 3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3081 3086 )
3082 3087
3083 3088 pull_request_id = Column(
3084 3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3085 3090
3086 3091 def __repr__(self):
3087 3092 if self.pull_request_id:
3088 3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3089 3094 else:
3090 3095 return '<DB:PullRequest at %#x>' % id(self)
3091 3096
3092 3097 reviewers = relationship('PullRequestReviewers',
3093 3098 cascade="all, delete, delete-orphan")
3094 3099 statuses = relationship('ChangesetStatus')
3095 3100 comments = relationship('ChangesetComment',
3096 3101 cascade="all, delete, delete-orphan")
3097 3102 versions = relationship('PullRequestVersion',
3098 3103 cascade="all, delete, delete-orphan")
3099 3104
3100 3105 def is_closed(self):
3101 3106 return self.status == self.STATUS_CLOSED
3102 3107
3103 3108 def get_api_data(self):
3104 3109 from rhodecode.model.pull_request import PullRequestModel
3105 3110 pull_request = self
3106 3111 merge_status = PullRequestModel().merge_status(pull_request)
3107 3112 pull_request_url = url(
3108 3113 'pullrequest_show', repo_name=self.target_repo.repo_name,
3109 3114 pull_request_id=self.pull_request_id, qualified=True)
3110 3115 data = {
3111 3116 'pull_request_id': pull_request.pull_request_id,
3112 3117 'url': pull_request_url,
3113 3118 'title': pull_request.title,
3114 3119 'description': pull_request.description,
3115 3120 'status': pull_request.status,
3116 3121 'created_on': pull_request.created_on,
3117 3122 'updated_on': pull_request.updated_on,
3118 3123 'commit_ids': pull_request.revisions,
3119 3124 'review_status': pull_request.calculated_review_status(),
3120 3125 'mergeable': {
3121 3126 'status': merge_status[0],
3122 3127 'message': unicode(merge_status[1]),
3123 3128 },
3124 3129 'source': {
3125 3130 'clone_url': pull_request.source_repo.clone_url(),
3126 3131 'repository': pull_request.source_repo.repo_name,
3127 3132 'reference': {
3128 3133 'name': pull_request.source_ref_parts.name,
3129 3134 'type': pull_request.source_ref_parts.type,
3130 3135 'commit_id': pull_request.source_ref_parts.commit_id,
3131 3136 },
3132 3137 },
3133 3138 'target': {
3134 3139 'clone_url': pull_request.target_repo.clone_url(),
3135 3140 'repository': pull_request.target_repo.repo_name,
3136 3141 'reference': {
3137 3142 'name': pull_request.target_ref_parts.name,
3138 3143 'type': pull_request.target_ref_parts.type,
3139 3144 'commit_id': pull_request.target_ref_parts.commit_id,
3140 3145 },
3141 3146 },
3142 3147 'shadow': {
3143 3148 'clone_url': PullRequestModel().get_shadow_clone_url(
3144 3149 pull_request),
3145 3150 },
3146 3151 'author': pull_request.author.get_api_data(include_secrets=False,
3147 3152 details='basic'),
3148 3153 'reviewers': [
3149 3154 {
3150 3155 'user': reviewer.get_api_data(include_secrets=False,
3151 3156 details='basic'),
3152 3157 'reasons': reasons,
3153 3158 'review_status': st[0][1].status if st else 'not_reviewed',
3154 3159 }
3155 3160 for reviewer, reasons, st in pull_request.reviewers_statuses()
3156 3161 ]
3157 3162 }
3158 3163
3159 3164 return data
3160 3165
3161 3166 def __json__(self):
3162 3167 return {
3163 3168 'revisions': self.revisions,
3164 3169 }
3165 3170
3166 3171 def calculated_review_status(self):
3167 3172 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3168 3173 # because it's tricky on how to use ChangesetStatusModel from there
3169 3174 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3170 3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3171 3176 return ChangesetStatusModel().calculated_review_status(self)
3172 3177
3173 3178 def reviewers_statuses(self):
3174 3179 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3175 3180 from rhodecode.model.changeset_status import ChangesetStatusModel
3176 3181 return ChangesetStatusModel().reviewers_statuses(self)
3177 3182
3178 3183
3179 3184 class PullRequestVersion(Base, _PullRequestBase):
3180 3185 __tablename__ = 'pull_request_versions'
3181 3186 __table_args__ = (
3182 3187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3183 3188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3184 3189 )
3185 3190
3186 3191 pull_request_version_id = Column(
3187 3192 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3188 3193 pull_request_id = Column(
3189 3194 'pull_request_id', Integer(),
3190 3195 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3191 3196 pull_request = relationship('PullRequest')
3192 3197
3193 3198 def __repr__(self):
3194 3199 if self.pull_request_version_id:
3195 3200 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3196 3201 else:
3197 3202 return '<DB:PullRequestVersion at %#x>' % id(self)
3198 3203
3199 3204
3200 3205 class PullRequestReviewers(Base, BaseModel):
3201 3206 __tablename__ = 'pull_request_reviewers'
3202 3207 __table_args__ = (
3203 3208 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3204 3209 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3205 3210 )
3206 3211
3207 3212 def __init__(self, user=None, pull_request=None, reasons=None):
3208 3213 self.user = user
3209 3214 self.pull_request = pull_request
3210 3215 self.reasons = reasons or []
3211 3216
3212 3217 @hybrid_property
3213 3218 def reasons(self):
3214 3219 if not self._reasons:
3215 3220 return []
3216 3221 return self._reasons
3217 3222
3218 3223 @reasons.setter
3219 3224 def reasons(self, val):
3220 3225 val = val or []
3221 3226 if any(not isinstance(x, basestring) for x in val):
3222 3227 raise Exception('invalid reasons type, must be list of strings')
3223 3228 self._reasons = val
3224 3229
3225 3230 pull_requests_reviewers_id = Column(
3226 3231 'pull_requests_reviewers_id', Integer(), nullable=False,
3227 3232 primary_key=True)
3228 3233 pull_request_id = Column(
3229 3234 "pull_request_id", Integer(),
3230 3235 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3231 3236 user_id = Column(
3232 3237 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3233 3238 _reasons = Column(
3234 3239 'reason', MutationList.as_mutable(
3235 3240 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3236 3241
3237 3242 user = relationship('User')
3238 3243 pull_request = relationship('PullRequest')
3239 3244
3240 3245
3241 3246 class Notification(Base, BaseModel):
3242 3247 __tablename__ = 'notifications'
3243 3248 __table_args__ = (
3244 3249 Index('notification_type_idx', 'type'),
3245 3250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 3251 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3247 3252 )
3248 3253
3249 3254 TYPE_CHANGESET_COMMENT = u'cs_comment'
3250 3255 TYPE_MESSAGE = u'message'
3251 3256 TYPE_MENTION = u'mention'
3252 3257 TYPE_REGISTRATION = u'registration'
3253 3258 TYPE_PULL_REQUEST = u'pull_request'
3254 3259 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3255 3260
3256 3261 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3257 3262 subject = Column('subject', Unicode(512), nullable=True)
3258 3263 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3259 3264 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3260 3265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3261 3266 type_ = Column('type', Unicode(255))
3262 3267
3263 3268 created_by_user = relationship('User')
3264 3269 notifications_to_users = relationship('UserNotification', lazy='joined',
3265 3270 cascade="all, delete, delete-orphan")
3266 3271
3267 3272 @property
3268 3273 def recipients(self):
3269 3274 return [x.user for x in UserNotification.query()\
3270 3275 .filter(UserNotification.notification == self)\
3271 3276 .order_by(UserNotification.user_id.asc()).all()]
3272 3277
3273 3278 @classmethod
3274 3279 def create(cls, created_by, subject, body, recipients, type_=None):
3275 3280 if type_ is None:
3276 3281 type_ = Notification.TYPE_MESSAGE
3277 3282
3278 3283 notification = cls()
3279 3284 notification.created_by_user = created_by
3280 3285 notification.subject = subject
3281 3286 notification.body = body
3282 3287 notification.type_ = type_
3283 3288 notification.created_on = datetime.datetime.now()
3284 3289
3285 3290 for u in recipients:
3286 3291 assoc = UserNotification()
3287 3292 assoc.notification = notification
3288 3293
3289 3294 # if created_by is inside recipients mark his notification
3290 3295 # as read
3291 3296 if u.user_id == created_by.user_id:
3292 3297 assoc.read = True
3293 3298
3294 3299 u.notifications.append(assoc)
3295 3300 Session().add(notification)
3296 3301
3297 3302 return notification
3298 3303
3299 3304 @property
3300 3305 def description(self):
3301 3306 from rhodecode.model.notification import NotificationModel
3302 3307 return NotificationModel().make_description(self)
3303 3308
3304 3309
3305 3310 class UserNotification(Base, BaseModel):
3306 3311 __tablename__ = 'user_to_notification'
3307 3312 __table_args__ = (
3308 3313 UniqueConstraint('user_id', 'notification_id'),
3309 3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3310 3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3311 3316 )
3312 3317 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3313 3318 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3314 3319 read = Column('read', Boolean, default=False)
3315 3320 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3316 3321
3317 3322 user = relationship('User', lazy="joined")
3318 3323 notification = relationship('Notification', lazy="joined",
3319 3324 order_by=lambda: Notification.created_on.desc(),)
3320 3325
3321 3326 def mark_as_read(self):
3322 3327 self.read = True
3323 3328 Session().add(self)
3324 3329
3325 3330
3326 3331 class Gist(Base, BaseModel):
3327 3332 __tablename__ = 'gists'
3328 3333 __table_args__ = (
3329 3334 Index('g_gist_access_id_idx', 'gist_access_id'),
3330 3335 Index('g_created_on_idx', 'created_on'),
3331 3336 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3332 3337 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3333 3338 )
3334 3339 GIST_PUBLIC = u'public'
3335 3340 GIST_PRIVATE = u'private'
3336 3341 DEFAULT_FILENAME = u'gistfile1.txt'
3337 3342
3338 3343 ACL_LEVEL_PUBLIC = u'acl_public'
3339 3344 ACL_LEVEL_PRIVATE = u'acl_private'
3340 3345
3341 3346 gist_id = Column('gist_id', Integer(), primary_key=True)
3342 3347 gist_access_id = Column('gist_access_id', Unicode(250))
3343 3348 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3344 3349 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3345 3350 gist_expires = Column('gist_expires', Float(53), nullable=False)
3346 3351 gist_type = Column('gist_type', Unicode(128), nullable=False)
3347 3352 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3348 3353 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3349 3354 acl_level = Column('acl_level', Unicode(128), nullable=True)
3350 3355
3351 3356 owner = relationship('User')
3352 3357
3353 3358 def __repr__(self):
3354 3359 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3355 3360
3356 3361 @classmethod
3357 3362 def get_or_404(cls, id_):
3358 3363 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3359 3364 if not res:
3360 3365 raise HTTPNotFound
3361 3366 return res
3362 3367
3363 3368 @classmethod
3364 3369 def get_by_access_id(cls, gist_access_id):
3365 3370 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3366 3371
3367 3372 def gist_url(self):
3368 3373 import rhodecode
3369 3374 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3370 3375 if alias_url:
3371 3376 return alias_url.replace('{gistid}', self.gist_access_id)
3372 3377
3373 3378 return url('gist', gist_id=self.gist_access_id, qualified=True)
3374 3379
3375 3380 @classmethod
3376 3381 def base_path(cls):
3377 3382 """
3378 3383 Returns base path when all gists are stored
3379 3384
3380 3385 :param cls:
3381 3386 """
3382 3387 from rhodecode.model.gist import GIST_STORE_LOC
3383 3388 q = Session().query(RhodeCodeUi)\
3384 3389 .filter(RhodeCodeUi.ui_key == URL_SEP)
3385 3390 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3386 3391 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3387 3392
3388 3393 def get_api_data(self):
3389 3394 """
3390 3395 Common function for generating gist related data for API
3391 3396 """
3392 3397 gist = self
3393 3398 data = {
3394 3399 'gist_id': gist.gist_id,
3395 3400 'type': gist.gist_type,
3396 3401 'access_id': gist.gist_access_id,
3397 3402 'description': gist.gist_description,
3398 3403 'url': gist.gist_url(),
3399 3404 'expires': gist.gist_expires,
3400 3405 'created_on': gist.created_on,
3401 3406 'modified_at': gist.modified_at,
3402 3407 'content': None,
3403 3408 'acl_level': gist.acl_level,
3404 3409 }
3405 3410 return data
3406 3411
3407 3412 def __json__(self):
3408 3413 data = dict(
3409 3414 )
3410 3415 data.update(self.get_api_data())
3411 3416 return data
3412 3417 # SCM functions
3413 3418
3414 3419 def scm_instance(self, **kwargs):
3415 3420 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3416 3421 return get_vcs_instance(
3417 3422 repo_path=safe_str(full_repo_path), create=False)
3418 3423
3419 3424
3420 3425 class DbMigrateVersion(Base, BaseModel):
3421 3426 __tablename__ = 'db_migrate_version'
3422 3427 __table_args__ = (
3423 3428 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3424 3429 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3425 3430 )
3426 3431 repository_id = Column('repository_id', String(250), primary_key=True)
3427 3432 repository_path = Column('repository_path', Text)
3428 3433 version = Column('version', Integer)
3429 3434
3430 3435
3431 3436 class ExternalIdentity(Base, BaseModel):
3432 3437 __tablename__ = 'external_identities'
3433 3438 __table_args__ = (
3434 3439 Index('local_user_id_idx', 'local_user_id'),
3435 3440 Index('external_id_idx', 'external_id'),
3436 3441 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3437 3442 'mysql_charset': 'utf8'})
3438 3443
3439 3444 external_id = Column('external_id', Unicode(255), default=u'',
3440 3445 primary_key=True)
3441 3446 external_username = Column('external_username', Unicode(1024), default=u'')
3442 3447 local_user_id = Column('local_user_id', Integer(),
3443 3448 ForeignKey('users.user_id'), primary_key=True)
3444 3449 provider_name = Column('provider_name', Unicode(255), default=u'',
3445 3450 primary_key=True)
3446 3451 access_token = Column('access_token', String(1024), default=u'')
3447 3452 alt_token = Column('alt_token', String(1024), default=u'')
3448 3453 token_secret = Column('token_secret', String(1024), default=u'')
3449 3454
3450 3455 @classmethod
3451 3456 def by_external_id_and_provider(cls, external_id, provider_name,
3452 3457 local_user_id=None):
3453 3458 """
3454 3459 Returns ExternalIdentity instance based on search params
3455 3460
3456 3461 :param external_id:
3457 3462 :param provider_name:
3458 3463 :return: ExternalIdentity
3459 3464 """
3460 3465 query = cls.query()
3461 3466 query = query.filter(cls.external_id == external_id)
3462 3467 query = query.filter(cls.provider_name == provider_name)
3463 3468 if local_user_id:
3464 3469 query = query.filter(cls.local_user_id == local_user_id)
3465 3470 return query.first()
3466 3471
3467 3472 @classmethod
3468 3473 def user_by_external_id_and_provider(cls, external_id, provider_name):
3469 3474 """
3470 3475 Returns User instance based on search params
3471 3476
3472 3477 :param external_id:
3473 3478 :param provider_name:
3474 3479 :return: User
3475 3480 """
3476 3481 query = User.query()
3477 3482 query = query.filter(cls.external_id == external_id)
3478 3483 query = query.filter(cls.provider_name == provider_name)
3479 3484 query = query.filter(User.user_id == cls.local_user_id)
3480 3485 return query.first()
3481 3486
3482 3487 @classmethod
3483 3488 def by_local_user_id(cls, local_user_id):
3484 3489 """
3485 3490 Returns all tokens for user
3486 3491
3487 3492 :param local_user_id:
3488 3493 :return: ExternalIdentity
3489 3494 """
3490 3495 query = cls.query()
3491 3496 query = query.filter(cls.local_user_id == local_user_id)
3492 3497 return query
3493 3498
3494 3499
3495 3500 class Integration(Base, BaseModel):
3496 3501 __tablename__ = 'integrations'
3497 3502 __table_args__ = (
3498 3503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3499 3504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3500 3505 )
3501 3506
3502 3507 integration_id = Column('integration_id', Integer(), primary_key=True)
3503 3508 integration_type = Column('integration_type', String(255))
3504 3509 enabled = Column('enabled', Boolean(), nullable=False)
3505 3510 name = Column('name', String(255), nullable=False)
3506 3511 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3507 3512 default=False)
3508 3513
3509 3514 settings = Column(
3510 3515 'settings_json', MutationObj.as_mutable(
3511 3516 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3512 3517 repo_id = Column(
3513 3518 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3514 3519 nullable=True, unique=None, default=None)
3515 3520 repo = relationship('Repository', lazy='joined')
3516 3521
3517 3522 repo_group_id = Column(
3518 3523 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3519 3524 nullable=True, unique=None, default=None)
3520 3525 repo_group = relationship('RepoGroup', lazy='joined')
3521 3526
3522 3527 @property
3523 3528 def scope(self):
3524 3529 if self.repo:
3525 3530 return repr(self.repo)
3526 3531 if self.repo_group:
3527 3532 if self.child_repos_only:
3528 3533 return repr(self.repo_group) + ' (child repos only)'
3529 3534 else:
3530 3535 return repr(self.repo_group) + ' (recursive)'
3531 3536 if self.child_repos_only:
3532 3537 return 'root_repos'
3533 3538 return 'global'
3534 3539
3535 3540 def __repr__(self):
3536 3541 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3537 3542
3538 3543
3539 3544 class RepoReviewRuleUser(Base, BaseModel):
3540 3545 __tablename__ = 'repo_review_rules_users'
3541 3546 __table_args__ = (
3542 3547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3543 3548 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3544 3549 )
3545 3550 repo_review_rule_user_id = Column(
3546 3551 'repo_review_rule_user_id', Integer(), primary_key=True)
3547 3552 repo_review_rule_id = Column("repo_review_rule_id",
3548 3553 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3549 3554 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3550 3555 nullable=False)
3551 3556 user = relationship('User')
3552 3557
3553 3558
3554 3559 class RepoReviewRuleUserGroup(Base, BaseModel):
3555 3560 __tablename__ = 'repo_review_rules_users_groups'
3556 3561 __table_args__ = (
3557 3562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3558 3563 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3559 3564 )
3560 3565 repo_review_rule_users_group_id = Column(
3561 3566 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3562 3567 repo_review_rule_id = Column("repo_review_rule_id",
3563 3568 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3564 3569 users_group_id = Column("users_group_id", Integer(),
3565 3570 ForeignKey('users_groups.users_group_id'), nullable=False)
3566 3571 users_group = relationship('UserGroup')
3567 3572
3568 3573
3569 3574 class RepoReviewRule(Base, BaseModel):
3570 3575 __tablename__ = 'repo_review_rules'
3571 3576 __table_args__ = (
3572 3577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3573 3578 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3574 3579 )
3575 3580
3576 3581 repo_review_rule_id = Column(
3577 3582 'repo_review_rule_id', Integer(), primary_key=True)
3578 3583 repo_id = Column(
3579 3584 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3580 3585 repo = relationship('Repository', backref='review_rules')
3581 3586
3582 3587 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3583 3588 default=u'*') # glob
3584 3589 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3585 3590 default=u'*') # glob
3586 3591
3587 3592 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3588 3593 nullable=False, default=False)
3589 3594 rule_users = relationship('RepoReviewRuleUser')
3590 3595 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3591 3596
3592 3597 @hybrid_property
3593 3598 def branch_pattern(self):
3594 3599 return self._branch_pattern or '*'
3595 3600
3596 3601 def _validate_glob(self, value):
3597 3602 re.compile('^' + glob2re(value) + '$')
3598 3603
3599 3604 @branch_pattern.setter
3600 3605 def branch_pattern(self, value):
3601 3606 self._validate_glob(value)
3602 3607 self._branch_pattern = value or '*'
3603 3608
3604 3609 @hybrid_property
3605 3610 def file_pattern(self):
3606 3611 return self._file_pattern or '*'
3607 3612
3608 3613 @file_pattern.setter
3609 3614 def file_pattern(self, value):
3610 3615 self._validate_glob(value)
3611 3616 self._file_pattern = value or '*'
3612 3617
3613 3618 def matches(self, branch, files_changed):
3614 3619 """
3615 3620 Check if this review rule matches a branch/files in a pull request
3616 3621
3617 3622 :param branch: branch name for the commit
3618 3623 :param files_changed: list of file paths changed in the pull request
3619 3624 """
3620 3625
3621 3626 branch = branch or ''
3622 3627 files_changed = files_changed or []
3623 3628
3624 3629 branch_matches = True
3625 3630 if branch:
3626 3631 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3627 3632 branch_matches = bool(branch_regex.search(branch))
3628 3633
3629 3634 files_matches = True
3630 3635 if self.file_pattern != '*':
3631 3636 files_matches = False
3632 3637 file_regex = re.compile(glob2re(self.file_pattern))
3633 3638 for filename in files_changed:
3634 3639 if file_regex.search(filename):
3635 3640 files_matches = True
3636 3641 break
3637 3642
3638 3643 return branch_matches and files_matches
3639 3644
3640 3645 @property
3641 3646 def review_users(self):
3642 3647 """ Returns the users which this rule applies to """
3643 3648
3644 3649 users = set()
3645 3650 users |= set([
3646 3651 rule_user.user for rule_user in self.rule_users
3647 3652 if rule_user.user.active])
3648 3653 users |= set(
3649 3654 member.user
3650 3655 for rule_user_group in self.rule_user_groups
3651 3656 for member in rule_user_group.users_group.members
3652 3657 if member.user.active
3653 3658 )
3654 3659 return users
3655 3660
3656 3661 def __repr__(self):
3657 3662 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3658 3663 self.repo_review_rule_id, self.repo)
@@ -1,551 +1,553 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import deform
45 45 import logging
46 46 import formencode
47 47
48 48 from pkg_resources import resource_filename
49 49 from formencode import All, Pipe
50 50
51 51 from pylons.i18n.translation import _
52 52
53 53 from rhodecode import BACKENDS
54 54 from rhodecode.lib import helpers
55 55 from rhodecode.model import validators as v
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 deform_templates = resource_filename('deform', 'templates')
61 61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 62 search_path = (rhodecode_templates, deform_templates)
63 63
64 64
65 65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 67 def __call__(self, template_name, **kw):
68 68 kw['h'] = helpers
69 69 return self.load(template_name)(**kw)
70 70
71 71
72 72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 73 deform.Form.set_default_renderer(form_renderer)
74 74
75 75
76 76 def LoginForm():
77 77 class _LoginForm(formencode.Schema):
78 78 allow_extra_fields = True
79 79 filter_extra_fields = True
80 80 username = v.UnicodeString(
81 81 strip=True,
82 82 min=1,
83 83 not_empty=True,
84 84 messages={
85 85 'empty': _(u'Please enter a login'),
86 86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 87 }
88 88 )
89 89
90 90 password = v.UnicodeString(
91 91 strip=False,
92 92 min=3,
93 93 not_empty=True,
94 94 messages={
95 95 'empty': _(u'Please enter a password'),
96 96 'tooShort': _(u'Enter %(min)i characters or more')}
97 97 )
98 98
99 99 remember = v.StringBoolean(if_missing=False)
100 100
101 101 chained_validators = [v.ValidAuth()]
102 102 return _LoginForm
103 103
104 104
105 105 def UserForm(edit=False, available_languages=[], old_data={}):
106 106 class _UserForm(formencode.Schema):
107 107 allow_extra_fields = True
108 108 filter_extra_fields = True
109 109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 110 v.ValidUsername(edit, old_data))
111 111 if edit:
112 112 new_password = All(
113 113 v.ValidPassword(),
114 114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 115 )
116 116 password_confirmation = All(
117 117 v.ValidPassword(),
118 118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 119 )
120 120 admin = v.StringBoolean(if_missing=False)
121 121 else:
122 122 password = All(
123 123 v.ValidPassword(),
124 124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 125 )
126 126 password_confirmation = All(
127 127 v.ValidPassword(),
128 128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 129 )
130 130
131 131 password_change = v.StringBoolean(if_missing=False)
132 132 create_repo_group = v.StringBoolean(if_missing=False)
133 133
134 134 active = v.StringBoolean(if_missing=False)
135 135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 138 extern_name = v.UnicodeString(strip=True)
139 139 extern_type = v.UnicodeString(strip=True)
140 140 language = v.OneOf(available_languages, hideList=False,
141 141 testValueList=True, if_missing=None)
142 142 chained_validators = [v.ValidPasswordsMatch()]
143 143 return _UserForm
144 144
145 145
146 146 def UserGroupForm(edit=False, old_data=None, available_members=None,
147 147 allow_disabled=False):
148 148 old_data = old_data or {}
149 149 available_members = available_members or []
150 150
151 151 class _UserGroupForm(formencode.Schema):
152 152 allow_extra_fields = True
153 153 filter_extra_fields = True
154 154
155 155 users_group_name = All(
156 156 v.UnicodeString(strip=True, min=1, not_empty=True),
157 157 v.ValidUserGroup(edit, old_data)
158 158 )
159 159 user_group_description = v.UnicodeString(strip=True, min=1,
160 160 not_empty=False)
161 161
162 162 users_group_active = v.StringBoolean(if_missing=False)
163 163
164 164 if edit:
165 165 users_group_members = v.OneOf(
166 166 available_members, hideList=False, testValueList=True,
167 167 if_missing=None, not_empty=False
168 168 )
169 169 # this is user group owner
170 170 user = All(
171 171 v.UnicodeString(not_empty=True),
172 172 v.ValidRepoUser(allow_disabled))
173 173 return _UserGroupForm
174 174
175 175
176 176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
177 177 can_create_in_root=False, allow_disabled=False):
178 178 old_data = old_data or {}
179 179 available_groups = available_groups or []
180 180
181 181 class _RepoGroupForm(formencode.Schema):
182 182 allow_extra_fields = True
183 183 filter_extra_fields = False
184 184
185 185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
186 186 v.SlugifyName(),)
187 187 group_description = v.UnicodeString(strip=True, min=1,
188 188 not_empty=False)
189 189 group_copy_permissions = v.StringBoolean(if_missing=False)
190 190
191 191 group_parent_id = v.OneOf(available_groups, hideList=False,
192 192 testValueList=True, not_empty=True)
193 193 enable_locking = v.StringBoolean(if_missing=False)
194 194 chained_validators = [
195 195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
196 196
197 197 if edit:
198 198 # this is repo group owner
199 199 user = All(
200 200 v.UnicodeString(not_empty=True),
201 201 v.ValidRepoUser(allow_disabled))
202 202
203 203 return _RepoGroupForm
204 204
205 205
206 206 def RegisterForm(edit=False, old_data={}):
207 207 class _RegisterForm(formencode.Schema):
208 208 allow_extra_fields = True
209 209 filter_extra_fields = True
210 210 username = All(
211 211 v.ValidUsername(edit, old_data),
212 212 v.UnicodeString(strip=True, min=1, not_empty=True)
213 213 )
214 214 password = All(
215 215 v.ValidPassword(),
216 216 v.UnicodeString(strip=False, min=6, not_empty=True)
217 217 )
218 218 password_confirmation = All(
219 219 v.ValidPassword(),
220 220 v.UnicodeString(strip=False, min=6, not_empty=True)
221 221 )
222 222 active = v.StringBoolean(if_missing=False)
223 223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
225 225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
226 226
227 227 chained_validators = [v.ValidPasswordsMatch()]
228 228
229 229 return _RegisterForm
230 230
231 231
232 232 def PasswordResetForm():
233 233 class _PasswordResetForm(formencode.Schema):
234 234 allow_extra_fields = True
235 235 filter_extra_fields = True
236 236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
237 237 return _PasswordResetForm
238 238
239 239
240 240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
241 241 allow_disabled=False):
242 242 old_data = old_data or {}
243 243 repo_groups = repo_groups or []
244 244 landing_revs = landing_revs or []
245 245 supported_backends = BACKENDS.keys()
246 246
247 247 class _RepoForm(formencode.Schema):
248 248 allow_extra_fields = True
249 249 filter_extra_fields = False
250 250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
251 251 v.SlugifyName())
252 252 repo_group = All(v.CanWriteGroup(old_data),
253 253 v.OneOf(repo_groups, hideList=True))
254 254 repo_type = v.OneOf(supported_backends, required=False,
255 255 if_missing=old_data.get('repo_type'))
256 256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
257 257 repo_private = v.StringBoolean(if_missing=False)
258 258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
259 259 repo_copy_permissions = v.StringBoolean(if_missing=False)
260 260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
261 261
262 262 repo_enable_statistics = v.StringBoolean(if_missing=False)
263 263 repo_enable_downloads = v.StringBoolean(if_missing=False)
264 264 repo_enable_locking = v.StringBoolean(if_missing=False)
265 265
266 266 if edit:
267 267 # this is repo owner
268 268 user = All(
269 269 v.UnicodeString(not_empty=True),
270 270 v.ValidRepoUser(allow_disabled))
271 271 clone_uri_change = v.UnicodeString(
272 272 not_empty=False, if_missing=v.Missing)
273 273
274 274 chained_validators = [v.ValidCloneUri(),
275 275 v.ValidRepoName(edit, old_data)]
276 276 return _RepoForm
277 277
278 278
279 279 def RepoPermsForm():
280 280 class _RepoPermsForm(formencode.Schema):
281 281 allow_extra_fields = True
282 282 filter_extra_fields = False
283 283 chained_validators = [v.ValidPerms(type_='repo')]
284 284 return _RepoPermsForm
285 285
286 286
287 287 def RepoGroupPermsForm(valid_recursive_choices):
288 288 class _RepoGroupPermsForm(formencode.Schema):
289 289 allow_extra_fields = True
290 290 filter_extra_fields = False
291 291 recursive = v.OneOf(valid_recursive_choices)
292 292 chained_validators = [v.ValidPerms(type_='repo_group')]
293 293 return _RepoGroupPermsForm
294 294
295 295
296 296 def UserGroupPermsForm():
297 297 class _UserPermsForm(formencode.Schema):
298 298 allow_extra_fields = True
299 299 filter_extra_fields = False
300 300 chained_validators = [v.ValidPerms(type_='user_group')]
301 301 return _UserPermsForm
302 302
303 303
304 304 def RepoFieldForm():
305 305 class _RepoFieldForm(formencode.Schema):
306 306 filter_extra_fields = True
307 307 allow_extra_fields = True
308 308
309 309 new_field_key = All(v.FieldKey(),
310 310 v.UnicodeString(strip=True, min=3, not_empty=True))
311 311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
312 312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
313 313 if_missing='str')
314 314 new_field_label = v.UnicodeString(not_empty=False)
315 315 new_field_desc = v.UnicodeString(not_empty=False)
316 316
317 317 return _RepoFieldForm
318 318
319 319
320 320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
321 321 repo_groups=[], landing_revs=[]):
322 322 class _RepoForkForm(formencode.Schema):
323 323 allow_extra_fields = True
324 324 filter_extra_fields = False
325 325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
326 326 v.SlugifyName())
327 327 repo_group = All(v.CanWriteGroup(),
328 328 v.OneOf(repo_groups, hideList=True))
329 329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
330 330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
331 331 private = v.StringBoolean(if_missing=False)
332 332 copy_permissions = v.StringBoolean(if_missing=False)
333 333 fork_parent_id = v.UnicodeString()
334 334 chained_validators = [v.ValidForkName(edit, old_data)]
335 335 landing_rev = v.OneOf(landing_revs, hideList=True)
336 336
337 337 return _RepoForkForm
338 338
339 339
340 340 def ApplicationSettingsForm():
341 341 class _ApplicationSettingsForm(formencode.Schema):
342 342 allow_extra_fields = True
343 343 filter_extra_fields = False
344 344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
345 345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
346 346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
348 348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
350 350
351 351 return _ApplicationSettingsForm
352 352
353 353
354 354 def ApplicationVisualisationForm():
355 355 class _ApplicationVisualisationForm(formencode.Schema):
356 356 allow_extra_fields = True
357 357 filter_extra_fields = False
358 358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
359 359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
360 360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
361 361
362 362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
363 363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
364 364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
365 365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
366 366 rhodecode_show_version = v.StringBoolean(if_missing=False)
367 367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
368 368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
369 369 rhodecode_gravatar_url = v.UnicodeString(min=3)
370 370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
371 371 rhodecode_support_url = v.UnicodeString()
372 372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
373 373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
374 374
375 375 return _ApplicationVisualisationForm
376 376
377 377
378 378 class _BaseVcsSettingsForm(formencode.Schema):
379 379 allow_extra_fields = True
380 380 filter_extra_fields = False
381 381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
382 382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
383 383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
384 384
385 385 extensions_largefiles = v.StringBoolean(if_missing=False)
386 386 phases_publish = v.StringBoolean(if_missing=False)
387 387
388 388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
389 389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
390 390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
391 391
392 392 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
393 393 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
394 394
395 395
396 396 def ApplicationUiSettingsForm():
397 397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
398 398 web_push_ssl = v.StringBoolean(if_missing=False)
399 399 paths_root_path = All(
400 400 v.ValidPath(),
401 401 v.UnicodeString(strip=True, min=1, not_empty=True)
402 402 )
403 403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
404 404 extensions_hggit = v.StringBoolean(if_missing=False)
405 405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
406 406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
407 407
408 408 return _ApplicationUiSettingsForm
409 409
410 410
411 411 def RepoVcsSettingsForm(repo_name):
412 412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
413 413 inherit_global_settings = v.StringBoolean(if_missing=False)
414 414 new_svn_branch = v.ValidSvnPattern(
415 415 section='vcs_svn_branch', repo_name=repo_name)
416 416 new_svn_tag = v.ValidSvnPattern(
417 417 section='vcs_svn_tag', repo_name=repo_name)
418 418
419 419 return _RepoVcsSettingsForm
420 420
421 421
422 422 def LabsSettingsForm():
423 423 class _LabSettingsForm(formencode.Schema):
424 424 allow_extra_fields = True
425 425 filter_extra_fields = False
426 426
427 427 return _LabSettingsForm
428 428
429 429
430 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
430 def ApplicationPermissionsForm(
431 register_choices, password_reset_choices, extern_activate_choices):
431 432 class _DefaultPermissionsForm(formencode.Schema):
432 433 allow_extra_fields = True
433 434 filter_extra_fields = True
434 435
435 436 anonymous = v.StringBoolean(if_missing=False)
436 437 default_register = v.OneOf(register_choices)
437 438 default_register_message = v.UnicodeString()
439 default_password_reset = v.OneOf(password_reset_choices)
438 440 default_extern_activate = v.OneOf(extern_activate_choices)
439 441
440 442 return _DefaultPermissionsForm
441 443
442 444
443 445 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
444 446 user_group_perms_choices):
445 447 class _ObjectPermissionsForm(formencode.Schema):
446 448 allow_extra_fields = True
447 449 filter_extra_fields = True
448 450 overwrite_default_repo = v.StringBoolean(if_missing=False)
449 451 overwrite_default_group = v.StringBoolean(if_missing=False)
450 452 overwrite_default_user_group = v.StringBoolean(if_missing=False)
451 453 default_repo_perm = v.OneOf(repo_perms_choices)
452 454 default_group_perm = v.OneOf(group_perms_choices)
453 455 default_user_group_perm = v.OneOf(user_group_perms_choices)
454 456
455 457 return _ObjectPermissionsForm
456 458
457 459
458 460 def UserPermissionsForm(create_choices, create_on_write_choices,
459 461 repo_group_create_choices, user_group_create_choices,
460 462 fork_choices, inherit_default_permissions_choices):
461 463 class _DefaultPermissionsForm(formencode.Schema):
462 464 allow_extra_fields = True
463 465 filter_extra_fields = True
464 466
465 467 anonymous = v.StringBoolean(if_missing=False)
466 468
467 469 default_repo_create = v.OneOf(create_choices)
468 470 default_repo_create_on_write = v.OneOf(create_on_write_choices)
469 471 default_user_group_create = v.OneOf(user_group_create_choices)
470 472 default_repo_group_create = v.OneOf(repo_group_create_choices)
471 473 default_fork_create = v.OneOf(fork_choices)
472 474 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
473 475
474 476 return _DefaultPermissionsForm
475 477
476 478
477 479 def UserIndividualPermissionsForm():
478 480 class _DefaultPermissionsForm(formencode.Schema):
479 481 allow_extra_fields = True
480 482 filter_extra_fields = True
481 483
482 484 inherit_default_permissions = v.StringBoolean(if_missing=False)
483 485
484 486 return _DefaultPermissionsForm
485 487
486 488
487 489 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
488 490 class _DefaultsForm(formencode.Schema):
489 491 allow_extra_fields = True
490 492 filter_extra_fields = True
491 493 default_repo_type = v.OneOf(supported_backends)
492 494 default_repo_private = v.StringBoolean(if_missing=False)
493 495 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
494 496 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
495 497 default_repo_enable_locking = v.StringBoolean(if_missing=False)
496 498
497 499 return _DefaultsForm
498 500
499 501
500 502 def AuthSettingsForm():
501 503 class _AuthSettingsForm(formencode.Schema):
502 504 allow_extra_fields = True
503 505 filter_extra_fields = True
504 506 auth_plugins = All(v.ValidAuthPlugins(),
505 507 v.UniqueListFromString()(not_empty=True))
506 508
507 509 return _AuthSettingsForm
508 510
509 511
510 512 def UserExtraEmailForm():
511 513 class _UserExtraEmailForm(formencode.Schema):
512 514 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
513 515 return _UserExtraEmailForm
514 516
515 517
516 518 def UserExtraIpForm():
517 519 class _UserExtraIpForm(formencode.Schema):
518 520 ip = v.ValidIp()(not_empty=True)
519 521 return _UserExtraIpForm
520 522
521 523
522 524
523 525 def PullRequestForm(repo_id):
524 526 class ReviewerForm(formencode.Schema):
525 527 user_id = v.Int(not_empty=True)
526 528 reasons = All()
527 529
528 530 class _PullRequestForm(formencode.Schema):
529 531 allow_extra_fields = True
530 532 filter_extra_fields = True
531 533
532 534 user = v.UnicodeString(strip=True, required=True)
533 535 source_repo = v.UnicodeString(strip=True, required=True)
534 536 source_ref = v.UnicodeString(strip=True, required=True)
535 537 target_repo = v.UnicodeString(strip=True, required=True)
536 538 target_ref = v.UnicodeString(strip=True, required=True)
537 539 revisions = All(#v.NotReviewedRevisions(repo_id)(),
538 540 v.UniqueList()(not_empty=True))
539 541 review_members = formencode.ForEach(ReviewerForm())
540 542 pullrequest_title = v.UnicodeString(strip=True, required=True)
541 543 pullrequest_desc = v.UnicodeString(strip=True, required=False)
542 544
543 545 return _PullRequestForm
544 546
545 547
546 548 def IssueTrackerPatternsForm():
547 549 class _IssueTrackerPatternsForm(formencode.Schema):
548 550 allow_extra_fields = True
549 551 filter_extra_fields = False
550 552 chained_validators = [v.ValidPattern()]
551 553 return _IssueTrackerPatternsForm
@@ -1,470 +1,482 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 permissions model for RhodeCode
23 23 """
24 24
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from sqlalchemy.exc import DatabaseError
30 30
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import (
33 33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 34 UserUserGroupToPerm, UserGroup, UserGroupToPerm)
35 35 from rhodecode.lib.utils2 import str2bool, safe_int
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class PermissionModel(BaseModel):
41 41 """
42 42 Permissions model for RhodeCode
43 43 """
44 44
45 45 cls = Permission
46 46 global_perms = {
47 47 'default_repo_create': None,
48 48 # special case for create repos on write access to group
49 49 'default_repo_create_on_write': None,
50 50 'default_repo_group_create': None,
51 51 'default_user_group_create': None,
52 52 'default_fork_create': None,
53 53 'default_inherit_default_permissions': None,
54
55 54 'default_register': None,
55 'default_password_reset': None,
56 56 'default_extern_activate': None,
57 57
58 58 # object permissions below
59 59 'default_repo_perm': None,
60 60 'default_group_perm': None,
61 61 'default_user_group_perm': None,
62 62 }
63 63
64 64 def set_global_permission_choices(self, c_obj, translator):
65 65 c_obj.repo_perms_choices = [
66 66 ('repository.none', translator('None'),),
67 67 ('repository.read', translator('Read'),),
68 68 ('repository.write', translator('Write'),),
69 69 ('repository.admin', translator('Admin'),)]
70 70
71 71 c_obj.group_perms_choices = [
72 72 ('group.none', translator('None'),),
73 73 ('group.read', translator('Read'),),
74 74 ('group.write', translator('Write'),),
75 75 ('group.admin', translator('Admin'),)]
76 76
77 77 c_obj.user_group_perms_choices = [
78 78 ('usergroup.none', translator('None'),),
79 79 ('usergroup.read', translator('Read'),),
80 80 ('usergroup.write', translator('Write'),),
81 81 ('usergroup.admin', translator('Admin'),)]
82 82
83 83 c_obj.register_choices = [
84 84 ('hg.register.none', translator('Disabled')),
85 85 ('hg.register.manual_activate', translator('Allowed with manual account activation')),
86 86 ('hg.register.auto_activate', translator('Allowed with automatic account activation')),]
87 87
88 c_obj.password_reset_choices = [
89 ('hg.password_reset.enabled', translator('Allow password recovery')),
90 ('hg.password_reset.hidden', translator('Hide password recovery link')),
91 ('hg.password_reset.disabled', translator('Disable password recovery')),]
92
88 93 c_obj.extern_activate_choices = [
89 94 ('hg.extern_activate.manual', translator('Manual activation of external account')),
90 95 ('hg.extern_activate.auto', translator('Automatic activation of external account')),]
91 96
92 97 c_obj.repo_create_choices = [
93 98 ('hg.create.none', translator('Disabled')),
94 99 ('hg.create.repository', translator('Enabled'))]
95 100
96 101 c_obj.repo_create_on_write_choices = [
97 102 ('hg.create.write_on_repogroup.false', translator('Disabled')),
98 103 ('hg.create.write_on_repogroup.true', translator('Enabled'))]
99 104
100 105 c_obj.user_group_create_choices = [
101 106 ('hg.usergroup.create.false', translator('Disabled')),
102 107 ('hg.usergroup.create.true', translator('Enabled'))]
103 108
104 109 c_obj.repo_group_create_choices = [
105 110 ('hg.repogroup.create.false', translator('Disabled')),
106 111 ('hg.repogroup.create.true', translator('Enabled'))]
107 112
108 113 c_obj.fork_choices = [
109 114 ('hg.fork.none', translator('Disabled')),
110 115 ('hg.fork.repository', translator('Enabled'))]
111 116
112 117 c_obj.inherit_default_permission_choices = [
113 118 ('hg.inherit_default_perms.false', translator('Disabled')),
114 119 ('hg.inherit_default_perms.true', translator('Enabled'))]
115 120
116 121 def get_default_perms(self, object_perms, suffix):
117 122 defaults = {}
118 123 for perm in object_perms:
119 124 # perms
120 125 if perm.permission.permission_name.startswith('repository.'):
121 126 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
122 127
123 128 if perm.permission.permission_name.startswith('group.'):
124 129 defaults['default_group_perm' + suffix] = perm.permission.permission_name
125 130
126 131 if perm.permission.permission_name.startswith('usergroup.'):
127 132 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
128 133
129 134 # creation of objects
130 135 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
131 136 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
132 137
133 138 elif perm.permission.permission_name.startswith('hg.create.'):
134 139 defaults['default_repo_create' + suffix] = perm.permission.permission_name
135 140
136 141 if perm.permission.permission_name.startswith('hg.fork.'):
137 142 defaults['default_fork_create' + suffix] = perm.permission.permission_name
138 143
139 144 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
140 145 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
141 146
142 147 if perm.permission.permission_name.startswith('hg.repogroup.'):
143 148 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
144 149
145 150 if perm.permission.permission_name.startswith('hg.usergroup.'):
146 151 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
147 152
148 153 # registration and external account activation
149 154 if perm.permission.permission_name.startswith('hg.register.'):
150 155 defaults['default_register' + suffix] = perm.permission.permission_name
151 156
157 if perm.permission.permission_name.startswith('hg.password_reset.'):
158 defaults['default_password_reset' + suffix] = perm.permission.permission_name
159
152 160 if perm.permission.permission_name.startswith('hg.extern_activate.'):
153 161 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
154 162
155 163 return defaults
156 164
157 165 def _make_new_user_perm(self, user, perm_name):
158 166 log.debug('Creating new user permission:%s', perm_name)
159 167 new = UserToPerm()
160 168 new.user = user
161 169 new.permission = Permission.get_by_key(perm_name)
162 170 return new
163 171
164 172 def _make_new_user_group_perm(self, user_group, perm_name):
165 173 log.debug('Creating new user group permission:%s', perm_name)
166 174 new = UserGroupToPerm()
167 175 new.users_group = user_group
168 176 new.permission = Permission.get_by_key(perm_name)
169 177 return new
170 178
171 179 def _keep_perm(self, perm_name, keep_fields):
172 180 def get_pat(field_name):
173 181 return {
174 182 # global perms
175 183 'default_repo_create': 'hg.create.',
176 184 # special case for create repos on write access to group
177 185 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
178 186 'default_repo_group_create': 'hg.repogroup.create.',
179 187 'default_user_group_create': 'hg.usergroup.create.',
180 188 'default_fork_create': 'hg.fork.',
181 189 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
182 190
183 191 # application perms
184 192 'default_register': 'hg.register.',
193 'default_password_reset': 'hg.password_reset.',
185 194 'default_extern_activate': 'hg.extern_activate.',
186 195
187 196 # object permissions below
188 197 'default_repo_perm': 'repository.',
189 198 'default_group_perm': 'group.',
190 199 'default_user_group_perm': 'usergroup.',
191 200 }[field_name]
192 201 for field in keep_fields:
193 202 pat = get_pat(field)
194 203 if perm_name.startswith(pat):
195 204 return True
196 205 return False
197 206
198 207 def _clear_object_perm(self, object_perms, preserve=None):
199 208 preserve = preserve or []
200 209 _deleted = []
201 210 for perm in object_perms:
202 211 perm_name = perm.permission.permission_name
203 212 if not self._keep_perm(perm_name, keep_fields=preserve):
204 213 _deleted.append(perm_name)
205 214 self.sa.delete(perm)
206 215 return _deleted
207 216
208 217 def _clear_user_perms(self, user_id, preserve=None):
209 218 perms = self.sa.query(UserToPerm)\
210 219 .filter(UserToPerm.user_id == user_id)\
211 220 .all()
212 221 return self._clear_object_perm(perms, preserve=preserve)
213 222
214 223 def _clear_user_group_perms(self, user_group_id, preserve=None):
215 224 perms = self.sa.query(UserGroupToPerm)\
216 225 .filter(UserGroupToPerm.users_group_id == user_group_id)\
217 226 .all()
218 227 return self._clear_object_perm(perms, preserve=preserve)
219 228
220 229 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
221 230 # clear current entries, to make this function idempotent
222 231 # it will fix even if we define more permissions or permissions
223 232 # are somehow missing
224 233 preserve = preserve or []
225 234 _global_perms = self.global_perms.copy()
226 235 if obj_type not in ['user', 'user_group']:
227 236 raise ValueError("obj_type must be on of 'user' or 'user_group'")
228 237 if len(_global_perms) != len(Permission.DEFAULT_USER_PERMISSIONS):
229 238 raise Exception('Inconsistent permissions definition')
230 239
231 240 if obj_type == 'user':
232 241 self._clear_user_perms(object.user_id, preserve)
233 242 if obj_type == 'user_group':
234 243 self._clear_user_group_perms(object.users_group_id, preserve)
235 244
236 245 # now kill the keys that we want to preserve from the form.
237 246 for key in preserve:
238 247 del _global_perms[key]
239 248
240 249 for k in _global_perms.copy():
241 250 _global_perms[k] = form_result[k]
242 251
243 252 # at that stage we validate all are passed inside form_result
244 253 for _perm_key, perm_value in _global_perms.items():
245 254 if perm_value is None:
246 255 raise ValueError('Missing permission for %s' % (_perm_key,))
247 256
248 257 if obj_type == 'user':
249 258 p = self._make_new_user_perm(object, perm_value)
250 259 self.sa.add(p)
251 260 if obj_type == 'user_group':
252 261 p = self._make_new_user_group_perm(object, perm_value)
253 262 self.sa.add(p)
254 263
255 264 def _set_new_user_perms(self, user, form_result, preserve=None):
256 265 return self._set_new_object_perms(
257 266 'user', user, form_result, preserve)
258 267
259 268 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
260 269 return self._set_new_object_perms(
261 270 'user_group', user_group, form_result, preserve)
262 271
263 272 def set_new_user_perms(self, user, form_result):
264 273 # calculate what to preserve from what is given in form_result
265 274 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
266 275 return self._set_new_user_perms(user, form_result, preserve)
267 276
268 277 def set_new_user_group_perms(self, user_group, form_result):
269 278 # calculate what to preserve from what is given in form_result
270 279 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
271 280 return self._set_new_user_group_perms(user_group, form_result, preserve)
272 281
273 282 def create_permissions(self):
274 283 """
275 284 Create permissions for whole system
276 285 """
277 286 for p in Permission.PERMS:
278 287 if not Permission.get_by_key(p[0]):
279 288 new_perm = Permission()
280 289 new_perm.permission_name = p[0]
281 290 new_perm.permission_longname = p[0] # translation err with p[1]
282 291 self.sa.add(new_perm)
283 292
284 293 def _create_default_object_permission(self, obj_type, obj, obj_perms,
285 294 force=False):
286 295 if obj_type not in ['user', 'user_group']:
287 296 raise ValueError("obj_type must be on of 'user' or 'user_group'")
288 297
289 298 def _get_group(perm_name):
290 299 return '.'.join(perm_name.split('.')[:1])
291 300
292 301 defined_perms_groups = map(
293 302 _get_group, (x.permission.permission_name for x in obj_perms))
294 303 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
295 304
296 305 if force:
297 306 self._clear_object_perm(obj_perms)
298 307 self.sa.commit()
299 308 defined_perms_groups = []
300 309 # for every default permission that needs to be created, we check if
301 310 # it's group is already defined, if it's not we create default perm
302 311 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
303 312 gr = _get_group(perm_name)
304 313 if gr not in defined_perms_groups:
305 314 log.debug('GR:%s not found, creating permission %s',
306 315 gr, perm_name)
307 316 if obj_type == 'user':
308 317 new_perm = self._make_new_user_perm(obj, perm_name)
309 318 self.sa.add(new_perm)
310 319 if obj_type == 'user_group':
311 320 new_perm = self._make_new_user_group_perm(obj, perm_name)
312 321 self.sa.add(new_perm)
313 322
314 323 def create_default_user_permissions(self, user, force=False):
315 324 """
316 325 Creates only missing default permissions for user, if force is set it
317 326 resets the default permissions for that user
318 327
319 328 :param user:
320 329 :param force:
321 330 """
322 331 user = self._get_user(user)
323 332 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
324 333 return self._create_default_object_permission(
325 334 'user', user, obj_perms, force)
326 335
327 336 def create_default_user_group_permissions(self, user_group, force=False):
328 337 """
329 338 Creates only missing default permissions for user group, if force is set it
330 339 resets the default permissions for that user group
331 340
332 341 :param user_group:
333 342 :param force:
334 343 """
335 344 user_group = self._get_user_group(user_group)
336 345 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
337 346 return self._create_default_object_permission(
338 347 'user_group', user_group, obj_perms, force)
339 348
340 349 def update_application_permissions(self, form_result):
341 350 if 'perm_user_id' in form_result:
342 351 perm_user = User.get(safe_int(form_result['perm_user_id']))
343 352 else:
344 353 # used mostly to do lookup for default user
345 354 perm_user = User.get_by_username(form_result['perm_user_name'])
346 355
347 356 try:
348 357 # stage 1 set anonymous access
349 358 if perm_user.username == User.DEFAULT_USER:
350 359 perm_user.active = str2bool(form_result['anonymous'])
351 360 self.sa.add(perm_user)
352 361
353 362 # stage 2 reset defaults and set them from form data
354 363 self._set_new_user_perms(perm_user, form_result, preserve=[
355 364 'default_repo_perm',
356 365 'default_group_perm',
357 366 'default_user_group_perm',
358 367
359 368 'default_repo_group_create',
360 369 'default_user_group_create',
361 370 'default_repo_create_on_write',
362 371 'default_repo_create',
363 372 'default_fork_create',
364 373 'default_inherit_default_permissions',])
365 374
366 375 self.sa.commit()
367 376 except (DatabaseError,):
368 377 log.error(traceback.format_exc())
369 378 self.sa.rollback()
370 379 raise
371 380
372 381 def update_user_permissions(self, form_result):
373 382 if 'perm_user_id' in form_result:
374 383 perm_user = User.get(safe_int(form_result['perm_user_id']))
375 384 else:
376 385 # used mostly to do lookup for default user
377 386 perm_user = User.get_by_username(form_result['perm_user_name'])
378 387 try:
379 388 # stage 2 reset defaults and set them from form data
380 389 self._set_new_user_perms(perm_user, form_result, preserve=[
381 390 'default_repo_perm',
382 391 'default_group_perm',
383 392 'default_user_group_perm',
384 393
385 394 'default_register',
395 'default_password_reset',
386 396 'default_extern_activate'])
387 397 self.sa.commit()
388 398 except (DatabaseError,):
389 399 log.error(traceback.format_exc())
390 400 self.sa.rollback()
391 401 raise
392 402
393 403 def update_user_group_permissions(self, form_result):
394 404 if 'perm_user_group_id' in form_result:
395 405 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
396 406 else:
397 407 # used mostly to do lookup for default user
398 408 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
399 409 try:
400 410 # stage 2 reset defaults and set them from form data
401 411 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
402 412 'default_repo_perm',
403 413 'default_group_perm',
404 414 'default_user_group_perm',
405 415
406 416 'default_register',
417 'default_password_reset',
407 418 'default_extern_activate'])
408 419 self.sa.commit()
409 420 except (DatabaseError,):
410 421 log.error(traceback.format_exc())
411 422 self.sa.rollback()
412 423 raise
413 424
414 425 def update_object_permissions(self, form_result):
415 426 if 'perm_user_id' in form_result:
416 427 perm_user = User.get(safe_int(form_result['perm_user_id']))
417 428 else:
418 429 # used mostly to do lookup for default user
419 430 perm_user = User.get_by_username(form_result['perm_user_name'])
420 431 try:
421 432
422 433 # stage 2 reset defaults and set them from form data
423 434 self._set_new_user_perms(perm_user, form_result, preserve=[
424 435 'default_repo_group_create',
425 436 'default_user_group_create',
426 437 'default_repo_create_on_write',
427 438 'default_repo_create',
428 439 'default_fork_create',
429 440 'default_inherit_default_permissions',
430 441
431 442 'default_register',
443 'default_password_reset',
432 444 'default_extern_activate'])
433 445
434 446 # overwrite default repo permissions
435 447 if form_result['overwrite_default_repo']:
436 448 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
437 449 _def = Permission.get_by_key('repository.' + _def_name)
438 450 for r2p in self.sa.query(UserRepoToPerm)\
439 451 .filter(UserRepoToPerm.user == perm_user)\
440 452 .all():
441 453 # don't reset PRIVATE repositories
442 454 if not r2p.repository.private:
443 455 r2p.permission = _def
444 456 self.sa.add(r2p)
445 457
446 458 # overwrite default repo group permissions
447 459 if form_result['overwrite_default_group']:
448 460 _def_name = form_result['default_group_perm'].split('group.')[-1]
449 461 _def = Permission.get_by_key('group.' + _def_name)
450 462 for g2p in self.sa.query(UserRepoGroupToPerm)\
451 463 .filter(UserRepoGroupToPerm.user == perm_user)\
452 464 .all():
453 465 g2p.permission = _def
454 466 self.sa.add(g2p)
455 467
456 468 # overwrite default user group permissions
457 469 if form_result['overwrite_default_user_group']:
458 470 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
459 471 # user groups
460 472 _def = Permission.get_by_key('usergroup.' + _def_name)
461 473 for g2p in self.sa.query(UserUserGroupToPerm)\
462 474 .filter(UserUserGroupToPerm.user == perm_user)\
463 475 .all():
464 476 g2p.permission = _def
465 477 self.sa.add(g2p)
466 478 self.sa.commit()
467 479 except (DatabaseError,):
468 480 log.exception('Failed to set default object permissions')
469 481 self.sa.rollback()
470 482 raise
@@ -1,297 +1,301 b''
1 1 //LOGIN
2 2
3 3
4 4 .loginbox {
5 5 max-width: 65%;
6 6 margin: @pagepadding auto;
7 7 font-family: @text-light;
8 8 border: @border-thickness solid @grey5;
9 9 box-sizing: border-box;
10 10
11 11 @media (max-width:1200px) {
12 12 max-width: 85%;
13 13 }
14 14
15 15 @media (max-width:768px) {
16 16 max-width: 100%;
17 17 width: 100%;
18 18 margin: 0;
19 19 }
20 20
21 21 .title {
22 22 float: none;
23 23 }
24 24
25 25 .header {
26 26 width: 100%;
27 27 padding: 0 35px;
28 28 box-sizing: border-box;
29 29
30 30 .title {
31 31 padding: 0;
32 32 }
33 33 }
34 34
35 35 .loginwrapper {
36 36 float: left;
37 37 height: 100%;
38 38 width: 100%;
39 39 padding: 35px 55px 35px 0;
40 40 background-color: white;
41 41 box-sizing: border-box;
42 42
43 43 @media (max-width:414px) {
44 44 padding: 35px;
45 45 }
46 46 }
47 47
48 48 .left-column {
49 49 float: left;
50 50 position: relative;
51 51 width: 50%;
52 52 height: 100%;
53 53
54 54 @media (max-width:414px) {
55 55 display:none;
56 56 }
57 57 }
58 58
59 59 .right-column {
60 60 float: right;
61 61 position: relative;
62 62 width: 50%;
63 63
64 64 @media (max-width:414px) {
65 65 width: 100%;
66 66 }
67 67 }
68 68
69 69 .sign-in-image {
70 70 display: block;
71 71 width: 65%;
72 72 margin: 5% auto;
73 73 }
74 74
75 75 .sign-in-title {
76 76 h1 {
77 77 margin: 0;
78 78 }
79 79
80 80 h4 {
81 81 margin: @padding*2 0;
82 82 }
83 83 }
84 84
85 85 .form {
86 86 label {
87 87 display: block;
88 88 }
89 89
90 90 input {
91 91 width: 100%;
92 92 margin: 0 10% @padding 0;
93 93 .box-sizing(border-box) ;
94 94
95 95 &[type="checkbox"] {
96 96 clear: both;
97 97 width: auto;
98 98 margin: 0 1em @padding 0;
99 99 }
100 100 }
101 101
102 102 .checkbox {
103 103 display: inline;
104 104 width: auto;
105 105 }
106 106
107 107 .sign-in {
108 108 clear: both;
109 109 width: 100%;
110 110 margin: @padding 0;
111 111 }
112 112 }
113 113 .register_message,
114 114 .activation_msg {
115 115 padding: 0 0 @padding;
116 116 }
117 117
118 118 .buttons,
119 119 .links {
120 120 padding: 0;
121 121 }
122 122
123 123 .buttons {
124 124 input {
125 125 margin-right: 0;
126 126 .box-sizing(border-box);
127 127 }
128 128
129 129 #sign_up, #send {
130 130 width: 100%;
131 131 }
132 132 }
133 133
134 134 .fields {
135 135 .field.field-compact {
136 136 margin-bottom: 0px;
137 137 }
138 138
139 139 .buttons {
140 140 margin: 0;
141 141 }
142 142
143 143 .field {
144 144 margin-bottom: 15px;
145 145
146 146 input {
147 147 width: 100%;
148 148 .box-sizing(border-box);
149 149 }
150 150
151 151 .input {
152 152 margin-left: 0;
153 153 }
154 154
155 155 .label {
156 156 padding-top: 0;
157 157 }
158 158 }
159 159 }
160 160
161 161 .checkbox {
162 162 margin: 0 0 @textmargin 0;
163 163
164 164 input[type="checkbox"] {
165 165 width: auto;
166 166 }
167 167
168 168 label {
169 169 padding: 0;
170 170 min-height: 0;
171 171 }
172 172 }
173 173
174 174 .activation_msg {
175 175 padding: @padding 0 0;
176 176 color: @grey4;
177 177 }
178 178
179 179 .links {
180 180 float: right;
181 181 margin: 0;
182 182 padding: 0;
183 183 line-height: 1;
184 184
185 185 p {
186 186 float: right;
187 187 margin: 0;
188 188 line-height: 1.5em;
189 189 }
190 190 }
191
192 p.help-block {
193 margin-left: 0;
194 }
191 195 }
192 196
193 197 .user-menu.submenu {
194 198 right: 0;
195 199 left: auto;
196 200 }
197 201 #quick_login {
198 202 left: auto;
199 203 right: 0;
200 204 padding: @menupadding;
201 205 z-index: 999;
202 206 overflow: hidden;
203 207 background-color: @grey6;
204 208 color: @grey2;
205 209
206 210 h4 {
207 211 margin-bottom: 12px;
208 212 }
209 213
210 214 .form {
211 215 width: auto;
212 216 }
213 217
214 218 label, .field {
215 219 margin-bottom: 0;
216 220 }
217 221
218 222 .label {
219 223 padding-top: 0;
220 224 }
221 225
222 226 input {
223 227 min-width: 215px;
224 228 margin: 8px 0 @padding;
225 229 }
226 230
227 231 input[type="submit"] {
228 232 &:extend(.btn-primary);
229 233 width:100%;
230 234 min-width: 0;
231 235 }
232 236
233 237 .forgot_password,
234 238 .buttons .register {
235 239 a {
236 240 color: @rcblue;
237 241
238 242 &:hover {
239 243 color: @rcdarkblue;
240 244 }
241 245 }
242 246 }
243 247
244 248 .buttons {
245 249 margin: 0;
246 250 }
247 251
248 252 .buttons a {
249 253 padding: 8px 0;
250 254 line-height: 1.4em;
251 255 color: @grey4;
252 256
253 257 &:hover {
254 258 color: @grey2;
255 259 }
256 260 }
257 261
258 262 #sign_in {
259 263 margin-bottom: 0
260 264 }
261 265
262 266 .big_gravatar {
263 267 float: left;
264 268 display: block;
265 269 margin-top: .5em;
266 270 }
267 271
268 272 .full_name,
269 273 .email {
270 274 margin: 0 0 0 65px;
271 275 }
272 276
273 277 .email {
274 278 font-family: @text-light;
275 279 }
276 280
277 281 ol.links {
278 282 clear:both;
279 283 margin: 0;
280 284 padding: @padding 0 0 0;
281 285
282 286 li {
283 287 border-top: @border-thickness solid @grey5;
284 288
285 289 input {
286 290 margin: @padding 0 0 0;
287 291 }
288 292 }
289 293 }
290 294 }
291 295 .submenu #quick_login li:hover {
292 296 background-color: transparent;
293 297 }
294 298
295 299 #quick_login_link:hover + #quick_login {
296 300 display: block;
297 301 }
@@ -1,71 +1,81 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('System Wide Application Permissions')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 ${h.secure_form(url('admin_permissions_application'), method='post')}
7 7 <div class="form">
8 8 <!-- fields -->
9 9 <div class="fields">
10 10 <div class="field">
11 11 <div class="label label-checkbox">
12 12 <label for="anonymous">${_('Anonymous Access')}:</label>
13 13 </div>
14 14 <div class="checkboxes">
15 15 <div class="checkbox">
16 16 ${h.checkbox('anonymous',True)} Allow Anonymous Access
17 17 </div>
18 18 <span class="help-block">${h.literal(_('Allow access to RhodeCode Enterprise without requiring users to login. Anonymous users get the %s permission settings.' % (h.link_to('"default user"',h.url('admin_permissions_object')))))}</span>
19 19 </div>
20 20 </div>
21 21
22 22 <div class="field">
23 23 <div class="label label-select">
24 24 <label for="default_register">${_('Registration')}:</label>
25 25 </div>
26 26 <div class="select">
27 27 ${h.select('default_register','',c.register_choices)}
28 28 </div>
29 29 </div>
30 30
31 31 <div class="field">
32 <div class="label label-select">
33 <label for="default_password_reset">${_('Password Reset')}:</label>
34 </div>
35 <div class="select">
36 ${h.select('default_password_reset','',c.password_reset_choices)}
37 </div>
38 </div>
39
40 <div class="field">
32 41 <div class="label label-textarea">
33 42 <label for="default_register_message">${_('Registration Page Message')}:</label>
34 43 </div>
35 44 <div class="textarea text-area editor" >
36 45 ${h.textarea('default_register_message', class_="medium", )}
37 46 <span class="help-block">${_('Custom message to be displayed on the registration page. HTML syntax is supported.')}</span>
38 47 </div>
39 48 </div>
40 49
41 50 <div class="field">
42 51 <div class="label">
43 52 <label for="default_extern_activate">${_('External Authentication Account Activation')}:</label>
44 53 </div>
45 54 <div class="select">
46 55 ${h.select('default_extern_activate','',c.extern_activate_choices)}
47 56 </div>
48 57 </div>
49 58 <div class="buttons">
50 59 ${h.submit('save',_('Save'),class_="btn")}
51 60 ${h.reset('reset',_('Reset'),class_="btn")}
52 61 </div>
53 62 </div>
54 63 </div>
55 64 ${h.end_form()}
56 65 </div>
57 66 </div>
58 67
59 68 <script>
60 69 $(document).ready(function(){
61 70 var select2Options = {
62 71 containerCssClass: 'drop-menu',
63 72 dropdownCssClass: 'drop-menu-dropdown',
64 73 dropdownAutoWidth: true,
65 74 minimumResultsForSearch: -1
66 75 };
67 76
68 77 $("#default_register").select2(select2Options);
78 $("#default_password_reset").select2(select2Options);
69 79 $("#default_extern_activate").select2(select2Options);
70 80 });
71 81 </script>
@@ -1,651 +1,653 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26 <div class="main">
27 27 ${next.main()}
28 28 </div>
29 29 </div>
30 30 <!-- END CONTENT -->
31 31
32 32 </div>
33 33 <!-- FOOTER -->
34 34 <div id="footer">
35 35 <div id="footer-inner" class="title wrapper">
36 36 <div>
37 37 <p class="footer-link-right">
38 38 % if c.visual.show_version:
39 39 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
40 40 % endif
41 41 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
42 42 % if c.visual.rhodecode_support_url:
43 43 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
44 44 % endif
45 45 </p>
46 46 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
47 47 <p class="server-instance" style="display:${sid}">
48 48 ## display hidden instance ID if specially defined
49 49 % if c.rhodecode_instanceid:
50 50 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
51 51 % endif
52 52 </p>
53 53 </div>
54 54 </div>
55 55 </div>
56 56
57 57 <!-- END FOOTER -->
58 58
59 59 ### MAKO DEFS ###
60 60
61 61 <%def name="menu_bar_subnav()">
62 62 </%def>
63 63
64 64 <%def name="breadcrumbs(class_='breadcrumbs')">
65 65 <div class="${class_}">
66 66 ${self.breadcrumbs_links()}
67 67 </div>
68 68 </%def>
69 69
70 70 <%def name="admin_menu()">
71 71 <ul class="admin_menu submenu">
72 72 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
73 73 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
74 74 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
75 75 <li><a href="${h.url('users')}">${_('Users')}</a></li>
76 76 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
77 77 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
78 78 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
79 79 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
80 80 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
81 81 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
82 82 </ul>
83 83 </%def>
84 84
85 85
86 86 <%def name="dt_info_panel(elements)">
87 87 <dl class="dl-horizontal">
88 88 %for dt, dd, title, show_items in elements:
89 89 <dt>${dt}:</dt>
90 90 <dd title="${title}">
91 91 %if callable(dd):
92 92 ## allow lazy evaluation of elements
93 93 ${dd()}
94 94 %else:
95 95 ${dd}
96 96 %endif
97 97 %if show_items:
98 98 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
99 99 %endif
100 100 </dd>
101 101
102 102 %if show_items:
103 103 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
104 104 %for item in show_items:
105 105 <dt></dt>
106 106 <dd>${item}</dd>
107 107 %endfor
108 108 </div>
109 109 %endif
110 110
111 111 %endfor
112 112 </dl>
113 113 </%def>
114 114
115 115
116 116 <%def name="gravatar(email, size=16)">
117 117 <%
118 118 if (size > 16):
119 119 gravatar_class = 'gravatar gravatar-large'
120 120 else:
121 121 gravatar_class = 'gravatar'
122 122 %>
123 123 <%doc>
124 124 TODO: johbo: For now we serve double size images to make it smooth
125 125 for retina. This is how it worked until now. Should be replaced
126 126 with a better solution at some point.
127 127 </%doc>
128 128 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
129 129 </%def>
130 130
131 131
132 132 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
133 133 <% email = h.email_or_none(contact) %>
134 134 <div class="rc-user tooltip" title="${h.author_string(email)}">
135 135 ${self.gravatar(email, size)}
136 136 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
137 137 </div>
138 138 </%def>
139 139
140 140
141 141 ## admin menu used for people that have some admin resources
142 142 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
143 143 <ul class="submenu">
144 144 %if repositories:
145 145 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
146 146 %endif
147 147 %if repository_groups:
148 148 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
149 149 %endif
150 150 %if user_groups:
151 151 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
152 152 %endif
153 153 </ul>
154 154 </%def>
155 155
156 156 <%def name="repo_page_title(repo_instance)">
157 157 <div class="title-content">
158 158 <div class="title-main">
159 159 ## SVN/HG/GIT icons
160 160 %if h.is_hg(repo_instance):
161 161 <i class="icon-hg"></i>
162 162 %endif
163 163 %if h.is_git(repo_instance):
164 164 <i class="icon-git"></i>
165 165 %endif
166 166 %if h.is_svn(repo_instance):
167 167 <i class="icon-svn"></i>
168 168 %endif
169 169
170 170 ## public/private
171 171 %if repo_instance.private:
172 172 <i class="icon-repo-private"></i>
173 173 %else:
174 174 <i class="icon-repo-public"></i>
175 175 %endif
176 176
177 177 ## repo name with group name
178 178 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
179 179
180 180 </div>
181 181
182 182 ## FORKED
183 183 %if repo_instance.fork:
184 184 <p>
185 185 <i class="icon-code-fork"></i> ${_('Fork of')}
186 186 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
187 187 </p>
188 188 %endif
189 189
190 190 ## IMPORTED FROM REMOTE
191 191 %if repo_instance.clone_uri:
192 192 <p>
193 193 <i class="icon-code-fork"></i> ${_('Clone from')}
194 194 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
195 195 </p>
196 196 %endif
197 197
198 198 ## LOCKING STATUS
199 199 %if repo_instance.locked[0]:
200 200 <p class="locking_locked">
201 201 <i class="icon-repo-lock"></i>
202 202 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
203 203 </p>
204 204 %elif repo_instance.enable_locking:
205 205 <p class="locking_unlocked">
206 206 <i class="icon-repo-unlock"></i>
207 207 ${_('Repository not locked. Pull repository to lock it.')}
208 208 </p>
209 209 %endif
210 210
211 211 </div>
212 212 </%def>
213 213
214 214 <%def name="repo_menu(active=None)">
215 215 <%
216 216 def is_active(selected):
217 217 if selected == active:
218 218 return "active"
219 219 %>
220 220
221 221 <!--- CONTEXT BAR -->
222 222 <div id="context-bar">
223 223 <div class="wrapper">
224 224 <ul id="context-pages" class="horizontal-list navigation">
225 225 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
226 226 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
227 227 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
228 228 <li class="${is_active('compare')}">
229 229 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
230 230 </li>
231 231 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 232 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 233 <li class="${is_active('showpullrequest')}">
234 234 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
235 235 %if c.repository_pull_requests:
236 236 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 237 %endif
238 238 <div class="menulabel">${_('Pull Requests')}</div>
239 239 </a>
240 240 </li>
241 241 %endif
242 242 <li class="${is_active('options')}">
243 243 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
244 244 <ul class="submenu">
245 245 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
246 246 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
247 247 %endif
248 248 %if c.rhodecode_db_repo.fork:
249 249 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
250 250 ${_('Compare fork')}</a></li>
251 251 %endif
252 252
253 253 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
254 254
255 255 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
256 256 %if c.rhodecode_db_repo.locked[0]:
257 257 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
258 258 %else:
259 259 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
260 260 %endif
261 261 %endif
262 262 %if c.rhodecode_user.username != h.DEFAULT_USER:
263 263 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
264 264 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
265 265 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
266 266 %endif
267 267 %endif
268 268 </ul>
269 269 </li>
270 270 </ul>
271 271 </div>
272 272 <div class="clear"></div>
273 273 </div>
274 274 <!--- END CONTEXT BAR -->
275 275
276 276 </%def>
277 277
278 278 <%def name="usermenu()">
279 279 ## USER MENU
280 280 <li id="quick_login_li">
281 281 <a id="quick_login_link" class="menulink childs">
282 282 ${gravatar(c.rhodecode_user.email, 20)}
283 283 <span class="user">
284 284 %if c.rhodecode_user.username != h.DEFAULT_USER:
285 285 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
286 286 %else:
287 287 <span>${_('Sign in')}</span>
288 288 %endif
289 289 </span>
290 290 </a>
291 291
292 292 <div class="user-menu submenu">
293 293 <div id="quick_login">
294 294 %if c.rhodecode_user.username == h.DEFAULT_USER:
295 295 <h4>${_('Sign in to your account')}</h4>
296 296 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
297 297 <div class="form form-vertical">
298 298 <div class="fields">
299 299 <div class="field">
300 300 <div class="label">
301 301 <label for="username">${_('Username')}:</label>
302 302 </div>
303 303 <div class="input">
304 304 ${h.text('username',class_='focus',tabindex=1)}
305 305 </div>
306 306
307 307 </div>
308 308 <div class="field">
309 309 <div class="label">
310 310 <label for="password">${_('Password')}:</label>
311 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
311 %if h.HasPermissionAny('hg.password_reset.enabled')():
312 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
313 %endif
312 314 </div>
313 315 <div class="input">
314 316 ${h.password('password',class_='focus',tabindex=2)}
315 317 </div>
316 318 </div>
317 319 <div class="buttons">
318 320 <div class="register">
319 321 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
320 322 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
321 323 %endif
322 324 </div>
323 325 <div class="submit">
324 326 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
325 327 </div>
326 328 </div>
327 329 </div>
328 330 </div>
329 331 ${h.end_form()}
330 332 %else:
331 333 <div class="">
332 334 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
333 335 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
334 336 <div class="email">${c.rhodecode_user.email}</div>
335 337 </div>
336 338 <div class="">
337 339 <ol class="links">
338 340 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
339 341 <li class="logout">
340 342 ${h.secure_form(h.route_path('logout'))}
341 343 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
342 344 ${h.end_form()}
343 345 </li>
344 346 </ol>
345 347 </div>
346 348 %endif
347 349 </div>
348 350 </div>
349 351 %if c.rhodecode_user.username != h.DEFAULT_USER:
350 352 <div class="pill_container">
351 353 % if c.unread_notifications == 0:
352 354 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
353 355 % else:
354 356 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
355 357 % endif
356 358 </div>
357 359 % endif
358 360 </li>
359 361 </%def>
360 362
361 363 <%def name="menu_items(active=None)">
362 364 <%
363 365 def is_active(selected):
364 366 if selected == active:
365 367 return "active"
366 368 return ""
367 369 %>
368 370 <ul id="quick" class="main_nav navigation horizontal-list">
369 371 <!-- repo switcher -->
370 372 <li class="${is_active('repositories')} repo_switcher_li has_select2">
371 373 <input id="repo_switcher" name="repo_switcher" type="hidden">
372 374 </li>
373 375
374 376 ## ROOT MENU
375 377 %if c.rhodecode_user.username != h.DEFAULT_USER:
376 378 <li class="${is_active('journal')}">
377 379 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
378 380 <div class="menulabel">${_('Journal')}</div>
379 381 </a>
380 382 </li>
381 383 %else:
382 384 <li class="${is_active('journal')}">
383 385 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
384 386 <div class="menulabel">${_('Public journal')}</div>
385 387 </a>
386 388 </li>
387 389 %endif
388 390 <li class="${is_active('gists')}">
389 391 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
390 392 <div class="menulabel">${_('Gists')}</div>
391 393 </a>
392 394 </li>
393 395 <li class="${is_active('search')}">
394 396 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
395 397 <div class="menulabel">${_('Search')}</div>
396 398 </a>
397 399 </li>
398 400 % if h.HasPermissionAll('hg.admin')('access admin main page'):
399 401 <li class="${is_active('admin')}">
400 402 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
401 403 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
402 404 </a>
403 405 ${admin_menu()}
404 406 </li>
405 407 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
406 408 <li class="${is_active('admin')}">
407 409 <a class="menulink childs" title="${_('Delegated Admin settings')}">
408 410 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 411 </a>
410 412 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
411 413 c.rhodecode_user.repository_groups_admin,
412 414 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
413 415 </li>
414 416 % endif
415 417 % if c.debug_style:
416 418 <li class="${is_active('debug_style')}">
417 419 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
418 420 <div class="menulabel">${_('Style')}</div>
419 421 </a>
420 422 </li>
421 423 % endif
422 424 ## render extra user menu
423 425 ${usermenu()}
424 426 </ul>
425 427
426 428 <script type="text/javascript">
427 429 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
428 430
429 431 /*format the look of items in the list*/
430 432 var format = function(state, escapeMarkup){
431 433 if (!state.id){
432 434 return state.text; // optgroup
433 435 }
434 436 var obj_dict = state.obj;
435 437 var tmpl = '';
436 438
437 439 if(obj_dict && state.type == 'repo'){
438 440 if(obj_dict['repo_type'] === 'hg'){
439 441 tmpl += '<i class="icon-hg"></i> ';
440 442 }
441 443 else if(obj_dict['repo_type'] === 'git'){
442 444 tmpl += '<i class="icon-git"></i> ';
443 445 }
444 446 else if(obj_dict['repo_type'] === 'svn'){
445 447 tmpl += '<i class="icon-svn"></i> ';
446 448 }
447 449 if(obj_dict['private']){
448 450 tmpl += '<i class="icon-lock" ></i> ';
449 451 }
450 452 else if(visual_show_public_icon){
451 453 tmpl += '<i class="icon-unlock-alt"></i> ';
452 454 }
453 455 }
454 456 if(obj_dict && state.type == 'commit') {
455 457 tmpl += '<i class="icon-tag"></i>';
456 458 }
457 459 if(obj_dict && state.type == 'group'){
458 460 tmpl += '<i class="icon-folder-close"></i> ';
459 461 }
460 462 tmpl += escapeMarkup(state.text);
461 463 return tmpl;
462 464 };
463 465
464 466 var formatResult = function(result, container, query, escapeMarkup) {
465 467 return format(result, escapeMarkup);
466 468 };
467 469
468 470 var formatSelection = function(data, container, escapeMarkup) {
469 471 return format(data, escapeMarkup);
470 472 };
471 473
472 474 $("#repo_switcher").select2({
473 475 cachedDataSource: {},
474 476 minimumInputLength: 2,
475 477 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
476 478 dropdownAutoWidth: true,
477 479 formatResult: formatResult,
478 480 formatSelection: formatSelection,
479 481 containerCssClass: "repo-switcher",
480 482 dropdownCssClass: "repo-switcher-dropdown",
481 483 escapeMarkup: function(m){
482 484 // don't escape our custom placeholder
483 485 if(m.substr(0,23) == '<div class="menulabel">'){
484 486 return m;
485 487 }
486 488
487 489 return Select2.util.escapeMarkup(m);
488 490 },
489 491 query: $.debounce(250, function(query){
490 492 self = this;
491 493 var cacheKey = query.term;
492 494 var cachedData = self.cachedDataSource[cacheKey];
493 495
494 496 if (cachedData) {
495 497 query.callback({results: cachedData.results});
496 498 } else {
497 499 $.ajax({
498 500 url: "${h.url('goto_switcher_data')}",
499 501 data: {'query': query.term},
500 502 dataType: 'json',
501 503 type: 'GET',
502 504 success: function(data) {
503 505 self.cachedDataSource[cacheKey] = data;
504 506 query.callback({results: data.results});
505 507 },
506 508 error: function(data, textStatus, errorThrown) {
507 509 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
508 510 }
509 511 })
510 512 }
511 513 })
512 514 });
513 515
514 516 $("#repo_switcher").on('select2-selecting', function(e){
515 517 e.preventDefault();
516 518 window.location = e.choice.url;
517 519 });
518 520
519 521 ## Global mouse bindings ##
520 522
521 523 // general help "?"
522 524 Mousetrap.bind(['?'], function(e) {
523 525 $('#help_kb').modal({})
524 526 });
525 527
526 528 // / open the quick filter
527 529 Mousetrap.bind(['/'], function(e) {
528 530 $("#repo_switcher").select2("open");
529 531
530 532 // return false to prevent default browser behavior
531 533 // and stop event from bubbling
532 534 return false;
533 535 });
534 536
535 537 // general nav g + action
536 538 Mousetrap.bind(['g h'], function(e) {
537 539 window.location = pyroutes.url('home');
538 540 });
539 541 Mousetrap.bind(['g g'], function(e) {
540 542 window.location = pyroutes.url('gists', {'private':1});
541 543 });
542 544 Mousetrap.bind(['g G'], function(e) {
543 545 window.location = pyroutes.url('gists', {'public':1});
544 546 });
545 547 Mousetrap.bind(['n g'], function(e) {
546 548 window.location = pyroutes.url('new_gist');
547 549 });
548 550 Mousetrap.bind(['n r'], function(e) {
549 551 window.location = pyroutes.url('new_repo');
550 552 });
551 553
552 554 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
553 555 // nav in repo context
554 556 Mousetrap.bind(['g s'], function(e) {
555 557 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
556 558 });
557 559 Mousetrap.bind(['g c'], function(e) {
558 560 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
559 561 });
560 562 Mousetrap.bind(['g F'], function(e) {
561 563 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
562 564 });
563 565 Mousetrap.bind(['g f'], function(e) {
564 566 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
565 567 });
566 568 Mousetrap.bind(['g p'], function(e) {
567 569 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
568 570 });
569 571 Mousetrap.bind(['g o'], function(e) {
570 572 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
571 573 });
572 574 Mousetrap.bind(['g O'], function(e) {
573 575 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
574 576 });
575 577 % endif
576 578
577 579 </script>
578 580 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
579 581 </%def>
580 582
581 583 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
582 584 <div class="modal-dialog">
583 585 <div class="modal-content">
584 586 <div class="modal-header">
585 587 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
586 588 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
587 589 </div>
588 590 <div class="modal-body">
589 591 <div class="block-left">
590 592 <table class="keyboard-mappings">
591 593 <tbody>
592 594 <tr>
593 595 <th></th>
594 596 <th>${_('Site-wide shortcuts')}</th>
595 597 </tr>
596 598 <%
597 599 elems = [
598 600 ('/', 'Open quick search box'),
599 601 ('g h', 'Goto home page'),
600 602 ('g g', 'Goto my private gists page'),
601 603 ('g G', 'Goto my public gists page'),
602 604 ('n r', 'New repository page'),
603 605 ('n g', 'New gist page'),
604 606 ]
605 607 %>
606 608 %for key, desc in elems:
607 609 <tr>
608 610 <td class="keys">
609 611 <span class="key tag">${key}</span>
610 612 </td>
611 613 <td>${desc}</td>
612 614 </tr>
613 615 %endfor
614 616 </tbody>
615 617 </table>
616 618 </div>
617 619 <div class="block-left">
618 620 <table class="keyboard-mappings">
619 621 <tbody>
620 622 <tr>
621 623 <th></th>
622 624 <th>${_('Repositories')}</th>
623 625 </tr>
624 626 <%
625 627 elems = [
626 628 ('g s', 'Goto summary page'),
627 629 ('g c', 'Goto changelog page'),
628 630 ('g f', 'Goto files page'),
629 631 ('g F', 'Goto files page with file search activated'),
630 632 ('g p', 'Goto pull requests page'),
631 633 ('g o', 'Goto repository settings'),
632 634 ('g O', 'Goto repository permissions settings'),
633 635 ]
634 636 %>
635 637 %for key, desc in elems:
636 638 <tr>
637 639 <td class="keys">
638 640 <span class="key tag">${key}</span>
639 641 </td>
640 642 <td>${desc}</td>
641 643 </tr>
642 644 %endfor
643 645 </tbody>
644 646 </table>
645 647 </div>
646 648 </div>
647 649 <div class="modal-footer">
648 650 </div>
649 651 </div><!-- /.modal-content -->
650 652 </div><!-- /.modal-dialog -->
651 653 </div><!-- /.modal -->
@@ -1,76 +1,84 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/root.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Sign In')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <style>body{background-color:#eeeeee;}</style>
12 12 <div class="loginbox">
13 13 <div class="header">
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
21 21 %endif
22 22 </div>
23 23 </div>
24 24 </div>
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30 30 <%block name="above_login_button" />
31 31 <div id="login" class="right-column">
32 32 <!-- login -->
33 33 <div class="sign-in-title">
34 34 <h1>${_('Sign In')}</h1>
35 35 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
36 36 <h4>${h.link_to(_("Go to the registration page to create a new account."), request.route_path('register'))}</h4>
37 37 %endif
38 38 </div>
39 39 <div class="inner form">
40 40 ${h.form(request.route_path('login', _query={'came_from': came_from}), needs_csrf_token=False)}
41 41
42 42 <label for="username">${_('Username')}:</label>
43 43 ${h.text('username', class_='focus', value=defaults.get('username'))}
44 44 %if 'username' in errors:
45 45 <span class="error-message">${errors.get('username')}</span>
46 46 <br />
47 47 %endif
48 48
49 49 <label for="password">${_('Password')}:</label>
50 50 ${h.password('password', class_='focus')}
51 51 %if 'password' in errors:
52 52 <span class="error-message">${errors.get('password')}</span>
53 53 <br />
54 54 %endif
55 55
56 56 ${h.checkbox('remember', value=True, checked=defaults.get('remember'))}
57 57 <label class="checkbox" for="remember">${_('Remember me')}</label>
58 58
59 <p class="links">
60 ${h.link_to(_('Forgot your password?'), h.route_path('reset_password'))}
61 </p>
59
60 %if h.HasPermissionAny('hg.password_reset.enable')():
61 <p class="links">
62 ${h.link_to(_('Forgot your password?'), h.route_path('reset_password'))}
63 </p>
64 %elif h.HasPermissionAny('hg.password_reset.hidden')():
65 <p class="help-block">
66 ${_('Contact an administrator if you have forgotten your password.')}
67 </p>
68 %endif
69
62 70
63 71 ${h.submit('sign_in', _('Sign In'), class_="btn sign-in")}
64 72
65 73 ${h.end_form()}
66 74 <script type="text/javascript">
67 75 $(document).ready(function(){
68 76 $('#username').focus();
69 77 })
70 78 </script>
71 79 </div>
72 80 <!-- end login -->
73 81 <%block name="below_login_button" />
74 82 </div>
75 83 </div>
76 84 </div>
@@ -1,77 +1,83 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/root.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Create an Account')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <style>body{background-color:#eeeeee;}</style>
11 11
12 12 <div class="loginbox">
13 13 <div class="header">
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
21 21 %endif
22 22 </div>
23 23 </div>
24 24 </div>
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30 30
31 <div id="register" class="right-column">
32 <!-- login -->
33 <div class="sign-in-title">
34 <h1>${_('Reset your Password')}</h1>
35 <h4>${h.link_to(_("Go to the login page to sign in."), request.route_path('login'))}</h4>
31 %if h.HasPermissionAny('hg.password_reset.disabled')():
32 <div class="right-column">
33 <p>${_('Password reset has been disabled.')}</p>
36 34 </div>
37 <div class="inner form">
38 ${h.form(request.route_path('reset_password'), needs_csrf_token=False)}
39 <label for="email">${_('Email Address')}:</label>
40 ${h.text('email', defaults.get('email'))}
41 %if 'email' in errors:
42 <span class="error-message">${errors.get('email')}</span>
43 <br />
44 %endif
45
46 %if captcha_active:
47 <div class="login-captcha"
48 <label for="email">${_('Captcha')}:</label>
49 ${h.hidden('recaptcha_field')}
50 <div id="recaptcha"></div>
51 %if 'recaptcha_field' in errors:
52 <span class="error-message">${errors.get('recaptcha_field')}</span>
35 %else:
36 <div id="register" class="right-column">
37 <!-- login -->
38 <div class="sign-in-title">
39 <h1>${_('Reset your Password')}</h1>
40 <h4>${h.link_to(_("Go to the login page to sign in."), request.route_path('login'))}</h4>
41 </div>
42 <div class="inner form">
43 ${h.form(request.route_path('reset_password'), needs_csrf_token=False)}
44 <label for="email">${_('Email Address')}:</label>
45 ${h.text('email', defaults.get('email'))}
46 %if 'email' in errors:
47 <span class="error-message">${errors.get('email')}</span>
53 48 <br />
54 49 %endif
55 </div>
56 %endif
57
58 ${h.submit('send', _('Send password reset email'), class_="btn sign-in")}
59 <div class="activation_msg">${_('Password reset link will be sent to matching email address')}</div>
60
61 ${h.end_form()}
50
51 %if captcha_active:
52 <div class="login-captcha"
53 <label for="email">${_('Captcha')}:</label>
54 ${h.hidden('recaptcha_field')}
55 <div id="recaptcha"></div>
56 %if 'recaptcha_field' in errors:
57 <span class="error-message">${errors.get('recaptcha_field')}</span>
58 <br />
59 %endif
60 </div>
61 %endif
62
63 ${h.submit('send', _('Send password reset email'), class_="btn sign-in")}
64 <div class="activation_msg">${_('Password reset link will be sent to matching email address')}</div>
65
66 ${h.end_form()}
67 </div>
62 68 </div>
63 </div>
69 %endif
64 70 </div>
65 71 </div>
66 72
67 73 %if captcha_active:
68 74 <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
69 75 %endif
70 76 <script type="text/javascript">
71 77 $(document).ready(function(){
72 78 $('#email').focus();
73 79 %if captcha_active:
74 80 Recaptcha.create("${captcha_public_key}", "recaptcha", {theme: "white"});
75 81 %endif
76 82 });
77 83 </script>
General Comments 0
You need to be logged in to leave comments. Login now