##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
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 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'http://rhodecode.com'
58 __url__ = 'http://rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,465 +1,466 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
24 from rhodecode.api.utils import (
24 from rhodecode.api.utils import (
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 from rhodecode.lib.exceptions import DefaultUserException
27 from rhodecode.lib.exceptions import DefaultUserException
28 from rhodecode.lib.utils2 import safe_int
28 from rhodecode.lib.utils2 import safe_int
29 from rhodecode.model.db import Session, User, Repository
29 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 @jsonrpc_method()
36 @jsonrpc_method()
37 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
37 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
38 """
38 """
39 Returns the information associated with a username or userid.
39 Returns the information associated with a username or userid.
40
40
41 * If the ``userid`` is not set, this command returns the information
41 * If the ``userid`` is not set, this command returns the information
42 for the ``userid`` calling the method.
42 for the ``userid`` calling the method.
43
43
44 .. note::
44 .. note::
45
45
46 Normal users may only run this command against their ``userid``. For
46 Normal users may only run this command against their ``userid``. For
47 full privileges you must run this command using an |authtoken| with
47 full privileges you must run this command using an |authtoken| with
48 admin rights.
48 admin rights.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param userid: Sets the userid for which data will be returned.
52 :param userid: Sets the userid for which data will be returned.
53 :type userid: Optional(str or int)
53 :type userid: Optional(str or int)
54
54
55 Example output:
55 Example output:
56
56
57 .. code-block:: bash
57 .. code-block:: bash
58
58
59 {
59 {
60 "error": null,
60 "error": null,
61 "id": <id>,
61 "id": <id>,
62 "result": {
62 "result": {
63 "active": true,
63 "active": true,
64 "admin": false,
64 "admin": false,
65 "api_key": "api-key",
65 "api_key": "api-key",
66 "api_keys": [ list of keys ],
66 "api_keys": [ list of keys ],
67 "email": "user@example.com",
67 "email": "user@example.com",
68 "emails": [
68 "emails": [
69 "user@example.com"
69 "user@example.com"
70 ],
70 ],
71 "extern_name": "rhodecode",
71 "extern_name": "rhodecode",
72 "extern_type": "rhodecode",
72 "extern_type": "rhodecode",
73 "firstname": "username",
73 "firstname": "username",
74 "ip_addresses": [],
74 "ip_addresses": [],
75 "language": null,
75 "language": null,
76 "last_login": "Timestamp",
76 "last_login": "Timestamp",
77 "lastname": "surnae",
77 "lastname": "surnae",
78 "permissions": {
78 "permissions": {
79 "global": [
79 "global": [
80 "hg.inherit_default_perms.true",
80 "hg.inherit_default_perms.true",
81 "usergroup.read",
81 "usergroup.read",
82 "hg.repogroup.create.false",
82 "hg.repogroup.create.false",
83 "hg.create.none",
83 "hg.create.none",
84 "hg.password_reset.enabled",
84 "hg.extern_activate.manual",
85 "hg.extern_activate.manual",
85 "hg.create.write_on_repogroup.false",
86 "hg.create.write_on_repogroup.false",
86 "hg.usergroup.create.false",
87 "hg.usergroup.create.false",
87 "group.none",
88 "group.none",
88 "repository.none",
89 "repository.none",
89 "hg.register.none",
90 "hg.register.none",
90 "hg.fork.repository"
91 "hg.fork.repository"
91 ],
92 ],
92 "repositories": { "username/example": "repository.write"},
93 "repositories": { "username/example": "repository.write"},
93 "repositories_groups": { "user-group/repo": "group.none" },
94 "repositories_groups": { "user-group/repo": "group.none" },
94 "user_groups": { "user_group_name": "usergroup.read" }
95 "user_groups": { "user_group_name": "usergroup.read" }
95 },
96 },
96 "user_id": 32,
97 "user_id": 32,
97 "username": "username"
98 "username": "username"
98 }
99 }
99 }
100 }
100 """
101 """
101
102
102 if not has_superadmin_permission(apiuser):
103 if not has_superadmin_permission(apiuser):
103 # make sure normal user does not pass someone else userid,
104 # make sure normal user does not pass someone else userid,
104 # he is not allowed to do that
105 # he is not allowed to do that
105 if not isinstance(userid, Optional) and userid != apiuser.user_id:
106 if not isinstance(userid, Optional) and userid != apiuser.user_id:
106 raise JSONRPCError('userid is not the same as your user')
107 raise JSONRPCError('userid is not the same as your user')
107
108
108 userid = Optional.extract(userid, evaluate_locals=locals())
109 userid = Optional.extract(userid, evaluate_locals=locals())
109 userid = getattr(userid, 'user_id', userid)
110 userid = getattr(userid, 'user_id', userid)
110
111
111 user = get_user_or_error(userid)
112 user = get_user_or_error(userid)
112 data = user.get_api_data(include_secrets=True)
113 data = user.get_api_data(include_secrets=True)
113 data['permissions'] = AuthUser(user_id=user.user_id).permissions
114 data['permissions'] = AuthUser(user_id=user.user_id).permissions
114 return data
115 return data
115
116
116
117
117 @jsonrpc_method()
118 @jsonrpc_method()
118 def get_users(request, apiuser):
119 def get_users(request, apiuser):
119 """
120 """
120 Lists all users in the |RCE| user database.
121 Lists all users in the |RCE| user database.
121
122
122 This command can only be run using an |authtoken| with admin rights to
123 This command can only be run using an |authtoken| with admin rights to
123 the specified repository.
124 the specified repository.
124
125
125 This command takes the following options:
126 This command takes the following options:
126
127
127 :param apiuser: This is filled automatically from the |authtoken|.
128 :param apiuser: This is filled automatically from the |authtoken|.
128 :type apiuser: AuthUser
129 :type apiuser: AuthUser
129
130
130 Example output:
131 Example output:
131
132
132 .. code-block:: bash
133 .. code-block:: bash
133
134
134 id : <id_given_in_input>
135 id : <id_given_in_input>
135 result: [<user_object>, ...]
136 result: [<user_object>, ...]
136 error: null
137 error: null
137 """
138 """
138
139
139 if not has_superadmin_permission(apiuser):
140 if not has_superadmin_permission(apiuser):
140 raise JSONRPCForbidden()
141 raise JSONRPCForbidden()
141
142
142 result = []
143 result = []
143 users_list = User.query().order_by(User.username) \
144 users_list = User.query().order_by(User.username) \
144 .filter(User.username != User.DEFAULT_USER) \
145 .filter(User.username != User.DEFAULT_USER) \
145 .all()
146 .all()
146 for user in users_list:
147 for user in users_list:
147 result.append(user.get_api_data(include_secrets=True))
148 result.append(user.get_api_data(include_secrets=True))
148 return result
149 return result
149
150
150
151
151 @jsonrpc_method()
152 @jsonrpc_method()
152 def create_user(request, apiuser, username, email, password=Optional(''),
153 def create_user(request, apiuser, username, email, password=Optional(''),
153 firstname=Optional(''), lastname=Optional(''),
154 firstname=Optional(''), lastname=Optional(''),
154 active=Optional(True), admin=Optional(False),
155 active=Optional(True), admin=Optional(False),
155 extern_name=Optional('rhodecode'),
156 extern_name=Optional('rhodecode'),
156 extern_type=Optional('rhodecode'),
157 extern_type=Optional('rhodecode'),
157 force_password_change=Optional(False)):
158 force_password_change=Optional(False)):
158 """
159 """
159 Creates a new user and returns the new user object.
160 Creates a new user and returns the new user object.
160
161
161 This command can only be run using an |authtoken| with admin rights to
162 This command can only be run using an |authtoken| with admin rights to
162 the specified repository.
163 the specified repository.
163
164
164 This command takes the following options:
165 This command takes the following options:
165
166
166 :param apiuser: This is filled automatically from the |authtoken|.
167 :param apiuser: This is filled automatically from the |authtoken|.
167 :type apiuser: AuthUser
168 :type apiuser: AuthUser
168 :param username: Set the new username.
169 :param username: Set the new username.
169 :type username: str or int
170 :type username: str or int
170 :param email: Set the user email address.
171 :param email: Set the user email address.
171 :type email: str
172 :type email: str
172 :param password: Set the new user password.
173 :param password: Set the new user password.
173 :type password: Optional(str)
174 :type password: Optional(str)
174 :param firstname: Set the new user firstname.
175 :param firstname: Set the new user firstname.
175 :type firstname: Optional(str)
176 :type firstname: Optional(str)
176 :param lastname: Set the new user surname.
177 :param lastname: Set the new user surname.
177 :type lastname: Optional(str)
178 :type lastname: Optional(str)
178 :param active: Set the user as active.
179 :param active: Set the user as active.
179 :type active: Optional(``True`` | ``False``)
180 :type active: Optional(``True`` | ``False``)
180 :param admin: Give the new user admin rights.
181 :param admin: Give the new user admin rights.
181 :type admin: Optional(``True`` | ``False``)
182 :type admin: Optional(``True`` | ``False``)
182 :param extern_name: Set the authentication plugin name.
183 :param extern_name: Set the authentication plugin name.
183 Using LDAP this is filled with LDAP UID.
184 Using LDAP this is filled with LDAP UID.
184 :type extern_name: Optional(str)
185 :type extern_name: Optional(str)
185 :param extern_type: Set the new user authentication plugin.
186 :param extern_type: Set the new user authentication plugin.
186 :type extern_type: Optional(str)
187 :type extern_type: Optional(str)
187 :param force_password_change: Force the new user to change password
188 :param force_password_change: Force the new user to change password
188 on next login.
189 on next login.
189 :type force_password_change: Optional(``True`` | ``False``)
190 :type force_password_change: Optional(``True`` | ``False``)
190
191
191 Example output:
192 Example output:
192
193
193 .. code-block:: bash
194 .. code-block:: bash
194
195
195 id : <id_given_in_input>
196 id : <id_given_in_input>
196 result: {
197 result: {
197 "msg" : "created new user `<username>`",
198 "msg" : "created new user `<username>`",
198 "user": <user_obj>
199 "user": <user_obj>
199 }
200 }
200 error: null
201 error: null
201
202
202 Example error output:
203 Example error output:
203
204
204 .. code-block:: bash
205 .. code-block:: bash
205
206
206 id : <id_given_in_input>
207 id : <id_given_in_input>
207 result : null
208 result : null
208 error : {
209 error : {
209 "user `<username>` already exist"
210 "user `<username>` already exist"
210 or
211 or
211 "email `<email>` already exist"
212 "email `<email>` already exist"
212 or
213 or
213 "failed to create user `<username>`"
214 "failed to create user `<username>`"
214 }
215 }
215
216
216 """
217 """
217 if not has_superadmin_permission(apiuser):
218 if not has_superadmin_permission(apiuser):
218 raise JSONRPCForbidden()
219 raise JSONRPCForbidden()
219
220
220 if UserModel().get_by_username(username):
221 if UserModel().get_by_username(username):
221 raise JSONRPCError("user `%s` already exist" % (username,))
222 raise JSONRPCError("user `%s` already exist" % (username,))
222
223
223 if UserModel().get_by_email(email, case_insensitive=True):
224 if UserModel().get_by_email(email, case_insensitive=True):
224 raise JSONRPCError("email `%s` already exist" % (email,))
225 raise JSONRPCError("email `%s` already exist" % (email,))
225
226
226 # generate random password if we actually given the
227 # generate random password if we actually given the
227 # extern_name and it's not rhodecode
228 # extern_name and it's not rhodecode
228 if (not isinstance(extern_name, Optional) and
229 if (not isinstance(extern_name, Optional) and
229 Optional.extract(extern_name) != 'rhodecode'):
230 Optional.extract(extern_name) != 'rhodecode'):
230 # generate temporary password if user is external
231 # generate temporary password if user is external
231 password = PasswordGenerator().gen_password(length=16)
232 password = PasswordGenerator().gen_password(length=16)
232
233
233 try:
234 try:
234 user = UserModel().create_or_update(
235 user = UserModel().create_or_update(
235 username=Optional.extract(username),
236 username=Optional.extract(username),
236 password=Optional.extract(password),
237 password=Optional.extract(password),
237 email=Optional.extract(email),
238 email=Optional.extract(email),
238 firstname=Optional.extract(firstname),
239 firstname=Optional.extract(firstname),
239 lastname=Optional.extract(lastname),
240 lastname=Optional.extract(lastname),
240 active=Optional.extract(active),
241 active=Optional.extract(active),
241 admin=Optional.extract(admin),
242 admin=Optional.extract(admin),
242 extern_type=Optional.extract(extern_type),
243 extern_type=Optional.extract(extern_type),
243 extern_name=Optional.extract(extern_name),
244 extern_name=Optional.extract(extern_name),
244 force_password_change=Optional.extract(force_password_change),
245 force_password_change=Optional.extract(force_password_change),
245 )
246 )
246 Session().commit()
247 Session().commit()
247 return {
248 return {
248 'msg': 'created new user `%s`' % username,
249 'msg': 'created new user `%s`' % username,
249 'user': user.get_api_data(include_secrets=True)
250 'user': user.get_api_data(include_secrets=True)
250 }
251 }
251 except Exception:
252 except Exception:
252 log.exception('Error occurred during creation of user')
253 log.exception('Error occurred during creation of user')
253 raise JSONRPCError('failed to create user `%s`' % (username,))
254 raise JSONRPCError('failed to create user `%s`' % (username,))
254
255
255
256
256 @jsonrpc_method()
257 @jsonrpc_method()
257 def update_user(request, apiuser, userid, username=Optional(None),
258 def update_user(request, apiuser, userid, username=Optional(None),
258 email=Optional(None), password=Optional(None),
259 email=Optional(None), password=Optional(None),
259 firstname=Optional(None), lastname=Optional(None),
260 firstname=Optional(None), lastname=Optional(None),
260 active=Optional(None), admin=Optional(None),
261 active=Optional(None), admin=Optional(None),
261 extern_type=Optional(None), extern_name=Optional(None), ):
262 extern_type=Optional(None), extern_name=Optional(None), ):
262 """
263 """
263 Updates the details for the specified user, if that user exists.
264 Updates the details for the specified user, if that user exists.
264
265
265 This command can only be run using an |authtoken| with admin rights to
266 This command can only be run using an |authtoken| with admin rights to
266 the specified repository.
267 the specified repository.
267
268
268 This command takes the following options:
269 This command takes the following options:
269
270
270 :param apiuser: This is filled automatically from |authtoken|.
271 :param apiuser: This is filled automatically from |authtoken|.
271 :type apiuser: AuthUser
272 :type apiuser: AuthUser
272 :param userid: Set the ``userid`` to update.
273 :param userid: Set the ``userid`` to update.
273 :type userid: str or int
274 :type userid: str or int
274 :param username: Set the new username.
275 :param username: Set the new username.
275 :type username: str or int
276 :type username: str or int
276 :param email: Set the new email.
277 :param email: Set the new email.
277 :type email: str
278 :type email: str
278 :param password: Set the new password.
279 :param password: Set the new password.
279 :type password: Optional(str)
280 :type password: Optional(str)
280 :param firstname: Set the new first name.
281 :param firstname: Set the new first name.
281 :type firstname: Optional(str)
282 :type firstname: Optional(str)
282 :param lastname: Set the new surname.
283 :param lastname: Set the new surname.
283 :type lastname: Optional(str)
284 :type lastname: Optional(str)
284 :param active: Set the new user as active.
285 :param active: Set the new user as active.
285 :type active: Optional(``True`` | ``False``)
286 :type active: Optional(``True`` | ``False``)
286 :param admin: Give the user admin rights.
287 :param admin: Give the user admin rights.
287 :type admin: Optional(``True`` | ``False``)
288 :type admin: Optional(``True`` | ``False``)
288 :param extern_name: Set the authentication plugin user name.
289 :param extern_name: Set the authentication plugin user name.
289 Using LDAP this is filled with LDAP UID.
290 Using LDAP this is filled with LDAP UID.
290 :type extern_name: Optional(str)
291 :type extern_name: Optional(str)
291 :param extern_type: Set the authentication plugin type.
292 :param extern_type: Set the authentication plugin type.
292 :type extern_type: Optional(str)
293 :type extern_type: Optional(str)
293
294
294
295
295 Example output:
296 Example output:
296
297
297 .. code-block:: bash
298 .. code-block:: bash
298
299
299 id : <id_given_in_input>
300 id : <id_given_in_input>
300 result: {
301 result: {
301 "msg" : "updated user ID:<userid> <username>",
302 "msg" : "updated user ID:<userid> <username>",
302 "user": <user_object>,
303 "user": <user_object>,
303 }
304 }
304 error: null
305 error: null
305
306
306 Example error output:
307 Example error output:
307
308
308 .. code-block:: bash
309 .. code-block:: bash
309
310
310 id : <id_given_in_input>
311 id : <id_given_in_input>
311 result : null
312 result : null
312 error : {
313 error : {
313 "failed to update user `<username>`"
314 "failed to update user `<username>`"
314 }
315 }
315
316
316 """
317 """
317 if not has_superadmin_permission(apiuser):
318 if not has_superadmin_permission(apiuser):
318 raise JSONRPCForbidden()
319 raise JSONRPCForbidden()
319
320
320 user = get_user_or_error(userid)
321 user = get_user_or_error(userid)
321
322
322 # only non optional arguments will be stored in updates
323 # only non optional arguments will be stored in updates
323 updates = {}
324 updates = {}
324
325
325 try:
326 try:
326
327
327 store_update(updates, username, 'username')
328 store_update(updates, username, 'username')
328 store_update(updates, password, 'password')
329 store_update(updates, password, 'password')
329 store_update(updates, email, 'email')
330 store_update(updates, email, 'email')
330 store_update(updates, firstname, 'name')
331 store_update(updates, firstname, 'name')
331 store_update(updates, lastname, 'lastname')
332 store_update(updates, lastname, 'lastname')
332 store_update(updates, active, 'active')
333 store_update(updates, active, 'active')
333 store_update(updates, admin, 'admin')
334 store_update(updates, admin, 'admin')
334 store_update(updates, extern_name, 'extern_name')
335 store_update(updates, extern_name, 'extern_name')
335 store_update(updates, extern_type, 'extern_type')
336 store_update(updates, extern_type, 'extern_type')
336
337
337 user = UserModel().update_user(user, **updates)
338 user = UserModel().update_user(user, **updates)
338 Session().commit()
339 Session().commit()
339 return {
340 return {
340 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
341 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
341 'user': user.get_api_data(include_secrets=True)
342 'user': user.get_api_data(include_secrets=True)
342 }
343 }
343 except DefaultUserException:
344 except DefaultUserException:
344 log.exception("Default user edit exception")
345 log.exception("Default user edit exception")
345 raise JSONRPCError('editing default user is forbidden')
346 raise JSONRPCError('editing default user is forbidden')
346 except Exception:
347 except Exception:
347 log.exception("Error occurred during update of user")
348 log.exception("Error occurred during update of user")
348 raise JSONRPCError('failed to update user `%s`' % (userid,))
349 raise JSONRPCError('failed to update user `%s`' % (userid,))
349
350
350
351
351 @jsonrpc_method()
352 @jsonrpc_method()
352 def delete_user(request, apiuser, userid):
353 def delete_user(request, apiuser, userid):
353 """
354 """
354 Deletes the specified user from the |RCE| user database.
355 Deletes the specified user from the |RCE| user database.
355
356
356 This command can only be run using an |authtoken| with admin rights to
357 This command can only be run using an |authtoken| with admin rights to
357 the specified repository.
358 the specified repository.
358
359
359 .. important::
360 .. important::
360
361
361 Ensure all open pull requests and open code review
362 Ensure all open pull requests and open code review
362 requests to this user are close.
363 requests to this user are close.
363
364
364 Also ensure all repositories, or repository groups owned by this
365 Also ensure all repositories, or repository groups owned by this
365 user are reassigned before deletion.
366 user are reassigned before deletion.
366
367
367 This command takes the following options:
368 This command takes the following options:
368
369
369 :param apiuser: This is filled automatically from the |authtoken|.
370 :param apiuser: This is filled automatically from the |authtoken|.
370 :type apiuser: AuthUser
371 :type apiuser: AuthUser
371 :param userid: Set the user to delete.
372 :param userid: Set the user to delete.
372 :type userid: str or int
373 :type userid: str or int
373
374
374 Example output:
375 Example output:
375
376
376 .. code-block:: bash
377 .. code-block:: bash
377
378
378 id : <id_given_in_input>
379 id : <id_given_in_input>
379 result: {
380 result: {
380 "msg" : "deleted user ID:<userid> <username>",
381 "msg" : "deleted user ID:<userid> <username>",
381 "user": null
382 "user": null
382 }
383 }
383 error: null
384 error: null
384
385
385 Example error output:
386 Example error output:
386
387
387 .. code-block:: bash
388 .. code-block:: bash
388
389
389 id : <id_given_in_input>
390 id : <id_given_in_input>
390 result : null
391 result : null
391 error : {
392 error : {
392 "failed to delete user ID:<userid> <username>"
393 "failed to delete user ID:<userid> <username>"
393 }
394 }
394
395
395 """
396 """
396 if not has_superadmin_permission(apiuser):
397 if not has_superadmin_permission(apiuser):
397 raise JSONRPCForbidden()
398 raise JSONRPCForbidden()
398
399
399 user = get_user_or_error(userid)
400 user = get_user_or_error(userid)
400
401
401 try:
402 try:
402 UserModel().delete(userid)
403 UserModel().delete(userid)
403 Session().commit()
404 Session().commit()
404 return {
405 return {
405 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
406 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
406 'user': None
407 'user': None
407 }
408 }
408 except Exception:
409 except Exception:
409 log.exception("Error occurred during deleting of user")
410 log.exception("Error occurred during deleting of user")
410 raise JSONRPCError(
411 raise JSONRPCError(
411 'failed to delete user ID:%s %s' % (user.user_id, user.username))
412 'failed to delete user ID:%s %s' % (user.user_id, user.username))
412
413
413
414
414 @jsonrpc_method()
415 @jsonrpc_method()
415 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
416 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
416 """
417 """
417 Displays all repositories locked by the specified user.
418 Displays all repositories locked by the specified user.
418
419
419 * If this command is run by a non-admin user, it returns
420 * If this command is run by a non-admin user, it returns
420 a list of |repos| locked by that user.
421 a list of |repos| locked by that user.
421
422
422 This command takes the following options:
423 This command takes the following options:
423
424
424 :param apiuser: This is filled automatically from the |authtoken|.
425 :param apiuser: This is filled automatically from the |authtoken|.
425 :type apiuser: AuthUser
426 :type apiuser: AuthUser
426 :param userid: Sets the userid whose list of locked |repos| will be
427 :param userid: Sets the userid whose list of locked |repos| will be
427 displayed.
428 displayed.
428 :type userid: Optional(str or int)
429 :type userid: Optional(str or int)
429
430
430 Example output:
431 Example output:
431
432
432 .. code-block:: bash
433 .. code-block:: bash
433
434
434 id : <id_given_in_input>
435 id : <id_given_in_input>
435 result : {
436 result : {
436 [repo_object, repo_object,...]
437 [repo_object, repo_object,...]
437 }
438 }
438 error : null
439 error : null
439 """
440 """
440
441
441 include_secrets = False
442 include_secrets = False
442 if not has_superadmin_permission(apiuser):
443 if not has_superadmin_permission(apiuser):
443 # make sure normal user does not pass someone else userid,
444 # make sure normal user does not pass someone else userid,
444 # he is not allowed to do that
445 # he is not allowed to do that
445 if not isinstance(userid, Optional) and userid != apiuser.user_id:
446 if not isinstance(userid, Optional) and userid != apiuser.user_id:
446 raise JSONRPCError('userid is not the same as your user')
447 raise JSONRPCError('userid is not the same as your user')
447 else:
448 else:
448 include_secrets = True
449 include_secrets = True
449
450
450 userid = Optional.extract(userid, evaluate_locals=locals())
451 userid = Optional.extract(userid, evaluate_locals=locals())
451 userid = getattr(userid, 'user_id', userid)
452 userid = getattr(userid, 'user_id', userid)
452 user = get_user_or_error(userid)
453 user = get_user_or_error(userid)
453
454
454 ret = []
455 ret = []
455
456
456 # show all locks
457 # show all locks
457 for r in Repository.getAll():
458 for r in Repository.getAll():
458 _user_id, _time, _reason = r.locked
459 _user_id, _time, _reason = r.locked
459 if _user_id and _time:
460 if _user_id and _time:
460 _api_data = r.get_api_data(include_secrets=include_secrets)
461 _api_data = r.get_api_data(include_secrets=include_secrets)
461 # if we use user filter just show the locks for this user
462 # if we use user filter just show the locks for this user
462 if safe_int(_user_id) == user.user_id:
463 if safe_int(_user_id) == user.user_id:
463 ret.append(_api_data)
464 ret.append(_api_data)
464
465
465 return ret
466 return ret
@@ -1,248 +1,249 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 permissions controller for RhodeCode Enterprise
23 permissions controller for RhodeCode Enterprise
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pylons import request, tmpl_context as c, url
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
37 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
38 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib.base import BaseController, render
39 from rhodecode.model.db import User, UserIpMap
39 from rhodecode.model.db import User, UserIpMap
40 from rhodecode.model.forms import (
40 from rhodecode.model.forms import (
41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class PermissionsController(BaseController):
49 class PermissionsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
50 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
51 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
52 # file has a resource setup:
53 # map.resource('permission', 'permissions')
53 # map.resource('permission', 'permissions')
54
54
55 @LoginRequired()
55 @LoginRequired()
56 def __before__(self):
56 def __before__(self):
57 super(PermissionsController, self).__before__()
57 super(PermissionsController, self).__before__()
58
58
59 def __load_data(self):
59 def __load_data(self):
60 PermissionModel().set_global_permission_choices(c, translator=_)
60 PermissionModel().set_global_permission_choices(c, translator=_)
61
61
62 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
63 def permission_application(self):
63 def permission_application(self):
64 c.active = 'application'
64 c.active = 'application'
65 self.__load_data()
65 self.__load_data()
66
66
67 c.user = User.get_default_user()
67 c.user = User.get_default_user()
68
68
69 # TODO: johbo: The default user might be based on outdated state which
69 # TODO: johbo: The default user might be based on outdated state which
70 # has been loaded from the cache. A call to refresh() ensures that the
70 # has been loaded from the cache. A call to refresh() ensures that the
71 # latest state from the database is used.
71 # latest state from the database is used.
72 Session().refresh(c.user)
72 Session().refresh(c.user)
73
73
74 app_settings = SettingsModel().get_all_settings()
74 app_settings = SettingsModel().get_all_settings()
75 defaults = {
75 defaults = {
76 'anonymous': c.user.active,
76 'anonymous': c.user.active,
77 'default_register_message': app_settings.get(
77 'default_register_message': app_settings.get(
78 'rhodecode_register_message')
78 'rhodecode_register_message')
79 }
79 }
80 defaults.update(c.user.get_default_perms())
80 defaults.update(c.user.get_default_perms())
81
81
82 return htmlfill.render(
82 return htmlfill.render(
83 render('admin/permissions/permissions.html'),
83 render('admin/permissions/permissions.html'),
84 defaults=defaults,
84 defaults=defaults,
85 encoding="UTF-8",
85 encoding="UTF-8",
86 force_defaults=False)
86 force_defaults=False)
87
87
88 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
89 @auth.CSRFRequired()
89 @auth.CSRFRequired()
90 def permission_application_update(self):
90 def permission_application_update(self):
91 c.active = 'application'
91 c.active = 'application'
92 self.__load_data()
92 self.__load_data()
93 _form = ApplicationPermissionsForm(
93 _form = ApplicationPermissionsForm(
94 [x[0] for x in c.register_choices],
94 [x[0] for x in c.register_choices],
95 [x[0] for x in c.password_reset_choices],
95 [x[0] for x in c.extern_activate_choices])()
96 [x[0] for x in c.extern_activate_choices])()
96
97
97 try:
98 try:
98 form_result = _form.to_python(dict(request.POST))
99 form_result = _form.to_python(dict(request.POST))
99 form_result.update({'perm_user_name': User.DEFAULT_USER})
100 form_result.update({'perm_user_name': User.DEFAULT_USER})
100 PermissionModel().update_application_permissions(form_result)
101 PermissionModel().update_application_permissions(form_result)
101
102
102 settings = [
103 settings = [
103 ('register_message', 'default_register_message'),
104 ('register_message', 'default_register_message'),
104 ]
105 ]
105 for setting, form_key in settings:
106 for setting, form_key in settings:
106 sett = SettingsModel().create_or_update_setting(
107 sett = SettingsModel().create_or_update_setting(
107 setting, form_result[form_key])
108 setting, form_result[form_key])
108 Session().add(sett)
109 Session().add(sett)
109
110
110 Session().commit()
111 Session().commit()
111 h.flash(_('Application permissions updated successfully'),
112 h.flash(_('Application permissions updated successfully'),
112 category='success')
113 category='success')
113
114
114 except formencode.Invalid as errors:
115 except formencode.Invalid as errors:
115 defaults = errors.value
116 defaults = errors.value
116
117
117 return htmlfill.render(
118 return htmlfill.render(
118 render('admin/permissions/permissions.html'),
119 render('admin/permissions/permissions.html'),
119 defaults=defaults,
120 defaults=defaults,
120 errors=errors.error_dict or {},
121 errors=errors.error_dict or {},
121 prefix_error=False,
122 prefix_error=False,
122 encoding="UTF-8",
123 encoding="UTF-8",
123 force_defaults=False)
124 force_defaults=False)
124 except Exception:
125 except Exception:
125 log.exception("Exception during update of permissions")
126 log.exception("Exception during update of permissions")
126 h.flash(_('Error occurred during update of permissions'),
127 h.flash(_('Error occurred during update of permissions'),
127 category='error')
128 category='error')
128
129
129 return redirect(url('admin_permissions_application'))
130 return redirect(url('admin_permissions_application'))
130
131
131 @HasPermissionAllDecorator('hg.admin')
132 @HasPermissionAllDecorator('hg.admin')
132 def permission_objects(self):
133 def permission_objects(self):
133 c.active = 'objects'
134 c.active = 'objects'
134 self.__load_data()
135 self.__load_data()
135 c.user = User.get_default_user()
136 c.user = User.get_default_user()
136 defaults = {}
137 defaults = {}
137 defaults.update(c.user.get_default_perms())
138 defaults.update(c.user.get_default_perms())
138 return htmlfill.render(
139 return htmlfill.render(
139 render('admin/permissions/permissions.html'),
140 render('admin/permissions/permissions.html'),
140 defaults=defaults,
141 defaults=defaults,
141 encoding="UTF-8",
142 encoding="UTF-8",
142 force_defaults=False)
143 force_defaults=False)
143
144
144 @HasPermissionAllDecorator('hg.admin')
145 @HasPermissionAllDecorator('hg.admin')
145 @auth.CSRFRequired()
146 @auth.CSRFRequired()
146 def permission_objects_update(self):
147 def permission_objects_update(self):
147 c.active = 'objects'
148 c.active = 'objects'
148 self.__load_data()
149 self.__load_data()
149 _form = ObjectPermissionsForm(
150 _form = ObjectPermissionsForm(
150 [x[0] for x in c.repo_perms_choices],
151 [x[0] for x in c.repo_perms_choices],
151 [x[0] for x in c.group_perms_choices],
152 [x[0] for x in c.group_perms_choices],
152 [x[0] for x in c.user_group_perms_choices])()
153 [x[0] for x in c.user_group_perms_choices])()
153
154
154 try:
155 try:
155 form_result = _form.to_python(dict(request.POST))
156 form_result = _form.to_python(dict(request.POST))
156 form_result.update({'perm_user_name': User.DEFAULT_USER})
157 form_result.update({'perm_user_name': User.DEFAULT_USER})
157 PermissionModel().update_object_permissions(form_result)
158 PermissionModel().update_object_permissions(form_result)
158
159
159 Session().commit()
160 Session().commit()
160 h.flash(_('Object permissions updated successfully'),
161 h.flash(_('Object permissions updated successfully'),
161 category='success')
162 category='success')
162
163
163 except formencode.Invalid as errors:
164 except formencode.Invalid as errors:
164 defaults = errors.value
165 defaults = errors.value
165
166
166 return htmlfill.render(
167 return htmlfill.render(
167 render('admin/permissions/permissions.html'),
168 render('admin/permissions/permissions.html'),
168 defaults=defaults,
169 defaults=defaults,
169 errors=errors.error_dict or {},
170 errors=errors.error_dict or {},
170 prefix_error=False,
171 prefix_error=False,
171 encoding="UTF-8",
172 encoding="UTF-8",
172 force_defaults=False)
173 force_defaults=False)
173 except Exception:
174 except Exception:
174 log.exception("Exception during update of permissions")
175 log.exception("Exception during update of permissions")
175 h.flash(_('Error occurred during update of permissions'),
176 h.flash(_('Error occurred during update of permissions'),
176 category='error')
177 category='error')
177
178
178 return redirect(url('admin_permissions_object'))
179 return redirect(url('admin_permissions_object'))
179
180
180 @HasPermissionAllDecorator('hg.admin')
181 @HasPermissionAllDecorator('hg.admin')
181 def permission_global(self):
182 def permission_global(self):
182 c.active = 'global'
183 c.active = 'global'
183 self.__load_data()
184 self.__load_data()
184
185
185 c.user = User.get_default_user()
186 c.user = User.get_default_user()
186 defaults = {}
187 defaults = {}
187 defaults.update(c.user.get_default_perms())
188 defaults.update(c.user.get_default_perms())
188
189
189 return htmlfill.render(
190 return htmlfill.render(
190 render('admin/permissions/permissions.html'),
191 render('admin/permissions/permissions.html'),
191 defaults=defaults,
192 defaults=defaults,
192 encoding="UTF-8",
193 encoding="UTF-8",
193 force_defaults=False)
194 force_defaults=False)
194
195
195 @HasPermissionAllDecorator('hg.admin')
196 @HasPermissionAllDecorator('hg.admin')
196 @auth.CSRFRequired()
197 @auth.CSRFRequired()
197 def permission_global_update(self):
198 def permission_global_update(self):
198 c.active = 'global'
199 c.active = 'global'
199 self.__load_data()
200 self.__load_data()
200 _form = UserPermissionsForm(
201 _form = UserPermissionsForm(
201 [x[0] for x in c.repo_create_choices],
202 [x[0] for x in c.repo_create_choices],
202 [x[0] for x in c.repo_create_on_write_choices],
203 [x[0] for x in c.repo_create_on_write_choices],
203 [x[0] for x in c.repo_group_create_choices],
204 [x[0] for x in c.repo_group_create_choices],
204 [x[0] for x in c.user_group_create_choices],
205 [x[0] for x in c.user_group_create_choices],
205 [x[0] for x in c.fork_choices],
206 [x[0] for x in c.fork_choices],
206 [x[0] for x in c.inherit_default_permission_choices])()
207 [x[0] for x in c.inherit_default_permission_choices])()
207
208
208 try:
209 try:
209 form_result = _form.to_python(dict(request.POST))
210 form_result = _form.to_python(dict(request.POST))
210 form_result.update({'perm_user_name': User.DEFAULT_USER})
211 form_result.update({'perm_user_name': User.DEFAULT_USER})
211 PermissionModel().update_user_permissions(form_result)
212 PermissionModel().update_user_permissions(form_result)
212
213
213 Session().commit()
214 Session().commit()
214 h.flash(_('Global permissions updated successfully'),
215 h.flash(_('Global permissions updated successfully'),
215 category='success')
216 category='success')
216
217
217 except formencode.Invalid as errors:
218 except formencode.Invalid as errors:
218 defaults = errors.value
219 defaults = errors.value
219
220
220 return htmlfill.render(
221 return htmlfill.render(
221 render('admin/permissions/permissions.html'),
222 render('admin/permissions/permissions.html'),
222 defaults=defaults,
223 defaults=defaults,
223 errors=errors.error_dict or {},
224 errors=errors.error_dict or {},
224 prefix_error=False,
225 prefix_error=False,
225 encoding="UTF-8",
226 encoding="UTF-8",
226 force_defaults=False)
227 force_defaults=False)
227 except Exception:
228 except Exception:
228 log.exception("Exception during update of permissions")
229 log.exception("Exception during update of permissions")
229 h.flash(_('Error occurred during update of permissions'),
230 h.flash(_('Error occurred during update of permissions'),
230 category='error')
231 category='error')
231
232
232 return redirect(url('admin_permissions_global'))
233 return redirect(url('admin_permissions_global'))
233
234
234 @HasPermissionAllDecorator('hg.admin')
235 @HasPermissionAllDecorator('hg.admin')
235 def permission_ips(self):
236 def permission_ips(self):
236 c.active = 'ips'
237 c.active = 'ips'
237 c.user = User.get_default_user()
238 c.user = User.get_default_user()
238 c.user_ip_map = (
239 c.user_ip_map = (
239 UserIpMap.query().filter(UserIpMap.user == c.user).all())
240 UserIpMap.query().filter(UserIpMap.user == c.user).all())
240
241
241 return render('admin/permissions/permissions.html')
242 return render('admin/permissions/permissions.html')
242
243
243 @HasPermissionAllDecorator('hg.admin')
244 @HasPermissionAllDecorator('hg.admin')
244 def permission_perms(self):
245 def permission_perms(self):
245 c.active = 'perms'
246 c.active = 'perms'
246 c.user = User.get_default_user()
247 c.user = User.get_default_user()
247 c.perm_user = c.user.AuthUser
248 c.perm_user = c.user.AuthUser
248 return render('admin/permissions/permissions.html')
249 return render('admin/permissions/permissions.html')
@@ -1,3658 +1,3663 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re)
56 glob2re)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PUSH = 'changegroup.push_logger'
353 HOOK_PUSH = 'changegroup.push_logger'
354
354
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # git part is currently hardcoded.
356 # git part is currently hardcoded.
357
357
358 # SVN PATTERNS
358 # SVN PATTERNS
359 SVN_BRANCH_ID = 'vcs_svn_branch'
359 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_TAG_ID = 'vcs_svn_tag'
360 SVN_TAG_ID = 'vcs_svn_tag'
361
361
362 ui_id = Column(
362 ui_id = Column(
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 primary_key=True)
364 primary_key=True)
365 ui_section = Column(
365 ui_section = Column(
366 "ui_section", String(255), nullable=True, unique=None, default=None)
366 "ui_section", String(255), nullable=True, unique=None, default=None)
367 ui_key = Column(
367 ui_key = Column(
368 "ui_key", String(255), nullable=True, unique=None, default=None)
368 "ui_key", String(255), nullable=True, unique=None, default=None)
369 ui_value = Column(
369 ui_value = Column(
370 "ui_value", String(255), nullable=True, unique=None, default=None)
370 "ui_value", String(255), nullable=True, unique=None, default=None)
371 ui_active = Column(
371 ui_active = Column(
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373
373
374 def __repr__(self):
374 def __repr__(self):
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 self.ui_key, self.ui_value)
376 self.ui_key, self.ui_value)
377
377
378
378
379 class RepoRhodeCodeSetting(Base, BaseModel):
379 class RepoRhodeCodeSetting(Base, BaseModel):
380 __tablename__ = 'repo_rhodecode_settings'
380 __tablename__ = 'repo_rhodecode_settings'
381 __table_args__ = (
381 __table_args__ = (
382 UniqueConstraint(
382 UniqueConstraint(
383 'app_settings_name', 'repository_id',
383 'app_settings_name', 'repository_id',
384 name='uq_repo_rhodecode_setting_name_repo_id'),
384 name='uq_repo_rhodecode_setting_name_repo_id'),
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 )
387 )
388
388
389 repository_id = Column(
389 repository_id = Column(
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 nullable=False)
391 nullable=False)
392 app_settings_id = Column(
392 app_settings_id = Column(
393 "app_settings_id", Integer(), nullable=False, unique=True,
393 "app_settings_id", Integer(), nullable=False, unique=True,
394 default=None, primary_key=True)
394 default=None, primary_key=True)
395 app_settings_name = Column(
395 app_settings_name = Column(
396 "app_settings_name", String(255), nullable=True, unique=None,
396 "app_settings_name", String(255), nullable=True, unique=None,
397 default=None)
397 default=None)
398 _app_settings_value = Column(
398 _app_settings_value = Column(
399 "app_settings_value", String(4096), nullable=True, unique=None,
399 "app_settings_value", String(4096), nullable=True, unique=None,
400 default=None)
400 default=None)
401 _app_settings_type = Column(
401 _app_settings_type = Column(
402 "app_settings_type", String(255), nullable=True, unique=None,
402 "app_settings_type", String(255), nullable=True, unique=None,
403 default=None)
403 default=None)
404
404
405 repository = relationship('Repository')
405 repository = relationship('Repository')
406
406
407 def __init__(self, repository_id, key='', val='', type='unicode'):
407 def __init__(self, repository_id, key='', val='', type='unicode'):
408 self.repository_id = repository_id
408 self.repository_id = repository_id
409 self.app_settings_name = key
409 self.app_settings_name = key
410 self.app_settings_type = type
410 self.app_settings_type = type
411 self.app_settings_value = val
411 self.app_settings_value = val
412
412
413 @validates('_app_settings_value')
413 @validates('_app_settings_value')
414 def validate_settings_value(self, key, val):
414 def validate_settings_value(self, key, val):
415 assert type(val) == unicode
415 assert type(val) == unicode
416 return val
416 return val
417
417
418 @hybrid_property
418 @hybrid_property
419 def app_settings_value(self):
419 def app_settings_value(self):
420 v = self._app_settings_value
420 v = self._app_settings_value
421 type_ = self.app_settings_type
421 type_ = self.app_settings_type
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 return converter(v)
424 return converter(v)
425
425
426 @app_settings_value.setter
426 @app_settings_value.setter
427 def app_settings_value(self, val):
427 def app_settings_value(self, val):
428 """
428 """
429 Setter that will always make sure we use unicode in app_settings_value
429 Setter that will always make sure we use unicode in app_settings_value
430
430
431 :param val:
431 :param val:
432 """
432 """
433 self._app_settings_value = safe_unicode(val)
433 self._app_settings_value = safe_unicode(val)
434
434
435 @hybrid_property
435 @hybrid_property
436 def app_settings_type(self):
436 def app_settings_type(self):
437 return self._app_settings_type
437 return self._app_settings_type
438
438
439 @app_settings_type.setter
439 @app_settings_type.setter
440 def app_settings_type(self, val):
440 def app_settings_type(self, val):
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 if val not in SETTINGS_TYPES:
442 if val not in SETTINGS_TYPES:
443 raise Exception('type must be one of %s got %s'
443 raise Exception('type must be one of %s got %s'
444 % (SETTINGS_TYPES.keys(), val))
444 % (SETTINGS_TYPES.keys(), val))
445 self._app_settings_type = val
445 self._app_settings_type = val
446
446
447 def __unicode__(self):
447 def __unicode__(self):
448 return u"<%s('%s:%s:%s[%s]')>" % (
448 return u"<%s('%s:%s:%s[%s]')>" % (
449 self.__class__.__name__, self.repository.repo_name,
449 self.__class__.__name__, self.repository.repo_name,
450 self.app_settings_name, self.app_settings_value,
450 self.app_settings_name, self.app_settings_value,
451 self.app_settings_type
451 self.app_settings_type
452 )
452 )
453
453
454
454
455 class RepoRhodeCodeUi(Base, BaseModel):
455 class RepoRhodeCodeUi(Base, BaseModel):
456 __tablename__ = 'repo_rhodecode_ui'
456 __tablename__ = 'repo_rhodecode_ui'
457 __table_args__ = (
457 __table_args__ = (
458 UniqueConstraint(
458 UniqueConstraint(
459 'repository_id', 'ui_section', 'ui_key',
459 'repository_id', 'ui_section', 'ui_key',
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 )
463 )
464
464
465 repository_id = Column(
465 repository_id = Column(
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 nullable=False)
467 nullable=False)
468 ui_id = Column(
468 ui_id = Column(
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 primary_key=True)
470 primary_key=True)
471 ui_section = Column(
471 ui_section = Column(
472 "ui_section", String(255), nullable=True, unique=None, default=None)
472 "ui_section", String(255), nullable=True, unique=None, default=None)
473 ui_key = Column(
473 ui_key = Column(
474 "ui_key", String(255), nullable=True, unique=None, default=None)
474 "ui_key", String(255), nullable=True, unique=None, default=None)
475 ui_value = Column(
475 ui_value = Column(
476 "ui_value", String(255), nullable=True, unique=None, default=None)
476 "ui_value", String(255), nullable=True, unique=None, default=None)
477 ui_active = Column(
477 ui_active = Column(
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479
479
480 repository = relationship('Repository')
480 repository = relationship('Repository')
481
481
482 def __repr__(self):
482 def __repr__(self):
483 return '<%s[%s:%s]%s=>%s]>' % (
483 return '<%s[%s:%s]%s=>%s]>' % (
484 self.__class__.__name__, self.repository.repo_name,
484 self.__class__.__name__, self.repository.repo_name,
485 self.ui_section, self.ui_key, self.ui_value)
485 self.ui_section, self.ui_key, self.ui_value)
486
486
487
487
488 class User(Base, BaseModel):
488 class User(Base, BaseModel):
489 __tablename__ = 'users'
489 __tablename__ = 'users'
490 __table_args__ = (
490 __table_args__ = (
491 UniqueConstraint('username'), UniqueConstraint('email'),
491 UniqueConstraint('username'), UniqueConstraint('email'),
492 Index('u_username_idx', 'username'),
492 Index('u_username_idx', 'username'),
493 Index('u_email_idx', 'email'),
493 Index('u_email_idx', 'email'),
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 )
496 )
497 DEFAULT_USER = 'default'
497 DEFAULT_USER = 'default'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500
500
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516
516
517 user_log = relationship('UserLog')
517 user_log = relationship('UserLog')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519
519
520 repositories = relationship('Repository')
520 repositories = relationship('Repository')
521 repository_groups = relationship('RepoGroup')
521 repository_groups = relationship('RepoGroup')
522 user_groups = relationship('UserGroup')
522 user_groups = relationship('UserGroup')
523
523
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526
526
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530
530
531 group_member = relationship('UserGroupMember', cascade='all')
531 group_member = relationship('UserGroupMember', cascade='all')
532
532
533 notifications = relationship('UserNotification', cascade='all')
533 notifications = relationship('UserNotification', cascade='all')
534 # notifications assigned to this user
534 # notifications assigned to this user
535 user_created_notifications = relationship('Notification', cascade='all')
535 user_created_notifications = relationship('Notification', cascade='all')
536 # comments created by this user
536 # comments created by this user
537 user_comments = relationship('ChangesetComment', cascade='all')
537 user_comments = relationship('ChangesetComment', cascade='all')
538 # user profile extra info
538 # user profile extra info
539 user_emails = relationship('UserEmailMap', cascade='all')
539 user_emails = relationship('UserEmailMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 # gists
542 # gists
543 user_gists = relationship('Gist', cascade='all')
543 user_gists = relationship('Gist', cascade='all')
544 # user pull requests
544 # user pull requests
545 user_pull_requests = relationship('PullRequest', cascade='all')
545 user_pull_requests = relationship('PullRequest', cascade='all')
546 # external identities
546 # external identities
547 extenal_identities = relationship(
547 extenal_identities = relationship(
548 'ExternalIdentity',
548 'ExternalIdentity',
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 cascade='all')
550 cascade='all')
551
551
552 def __unicode__(self):
552 def __unicode__(self):
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 self.user_id, self.username)
554 self.user_id, self.username)
555
555
556 @hybrid_property
556 @hybrid_property
557 def email(self):
557 def email(self):
558 return self._email
558 return self._email
559
559
560 @email.setter
560 @email.setter
561 def email(self, val):
561 def email(self, val):
562 self._email = val.lower() if val else None
562 self._email = val.lower() if val else None
563
563
564 @property
564 @property
565 def firstname(self):
565 def firstname(self):
566 # alias for future
566 # alias for future
567 return self.name
567 return self.name
568
568
569 @property
569 @property
570 def emails(self):
570 def emails(self):
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 return [self.email] + [x.email for x in other]
572 return [self.email] + [x.email for x in other]
573
573
574 @property
574 @property
575 def auth_tokens(self):
575 def auth_tokens(self):
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577
577
578 @property
578 @property
579 def extra_auth_tokens(self):
579 def extra_auth_tokens(self):
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581
581
582 @property
582 @property
583 def feed_token(self):
583 def feed_token(self):
584 feed_tokens = UserApiKeys.query()\
584 feed_tokens = UserApiKeys.query()\
585 .filter(UserApiKeys.user == self)\
585 .filter(UserApiKeys.user == self)\
586 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
586 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
587 .all()
587 .all()
588 if feed_tokens:
588 if feed_tokens:
589 return feed_tokens[0].api_key
589 return feed_tokens[0].api_key
590 else:
590 else:
591 # use the main token so we don't end up with nothing...
591 # use the main token so we don't end up with nothing...
592 return self.api_key
592 return self.api_key
593
593
594 @classmethod
594 @classmethod
595 def extra_valid_auth_tokens(cls, user, role=None):
595 def extra_valid_auth_tokens(cls, user, role=None):
596 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
596 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
597 .filter(or_(UserApiKeys.expires == -1,
597 .filter(or_(UserApiKeys.expires == -1,
598 UserApiKeys.expires >= time.time()))
598 UserApiKeys.expires >= time.time()))
599 if role:
599 if role:
600 tokens = tokens.filter(or_(UserApiKeys.role == role,
600 tokens = tokens.filter(or_(UserApiKeys.role == role,
601 UserApiKeys.role == UserApiKeys.ROLE_ALL))
601 UserApiKeys.role == UserApiKeys.ROLE_ALL))
602 return tokens.all()
602 return tokens.all()
603
603
604 @property
604 @property
605 def ip_addresses(self):
605 def ip_addresses(self):
606 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
606 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
607 return [x.ip_addr for x in ret]
607 return [x.ip_addr for x in ret]
608
608
609 @property
609 @property
610 def username_and_name(self):
610 def username_and_name(self):
611 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
611 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
612
612
613 @property
613 @property
614 def username_or_name_or_email(self):
614 def username_or_name_or_email(self):
615 full_name = self.full_name if self.full_name is not ' ' else None
615 full_name = self.full_name if self.full_name is not ' ' else None
616 return self.username or full_name or self.email
616 return self.username or full_name or self.email
617
617
618 @property
618 @property
619 def full_name(self):
619 def full_name(self):
620 return '%s %s' % (self.firstname, self.lastname)
620 return '%s %s' % (self.firstname, self.lastname)
621
621
622 @property
622 @property
623 def full_name_or_username(self):
623 def full_name_or_username(self):
624 return ('%s %s' % (self.firstname, self.lastname)
624 return ('%s %s' % (self.firstname, self.lastname)
625 if (self.firstname and self.lastname) else self.username)
625 if (self.firstname and self.lastname) else self.username)
626
626
627 @property
627 @property
628 def full_contact(self):
628 def full_contact(self):
629 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
629 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
630
630
631 @property
631 @property
632 def short_contact(self):
632 def short_contact(self):
633 return '%s %s' % (self.firstname, self.lastname)
633 return '%s %s' % (self.firstname, self.lastname)
634
634
635 @property
635 @property
636 def is_admin(self):
636 def is_admin(self):
637 return self.admin
637 return self.admin
638
638
639 @property
639 @property
640 def AuthUser(self):
640 def AuthUser(self):
641 """
641 """
642 Returns instance of AuthUser for this user
642 Returns instance of AuthUser for this user
643 """
643 """
644 from rhodecode.lib.auth import AuthUser
644 from rhodecode.lib.auth import AuthUser
645 return AuthUser(user_id=self.user_id, api_key=self.api_key,
645 return AuthUser(user_id=self.user_id, api_key=self.api_key,
646 username=self.username)
646 username=self.username)
647
647
648 @hybrid_property
648 @hybrid_property
649 def user_data(self):
649 def user_data(self):
650 if not self._user_data:
650 if not self._user_data:
651 return {}
651 return {}
652
652
653 try:
653 try:
654 return json.loads(self._user_data)
654 return json.loads(self._user_data)
655 except TypeError:
655 except TypeError:
656 return {}
656 return {}
657
657
658 @user_data.setter
658 @user_data.setter
659 def user_data(self, val):
659 def user_data(self, val):
660 if not isinstance(val, dict):
660 if not isinstance(val, dict):
661 raise Exception('user_data must be dict, got %s' % type(val))
661 raise Exception('user_data must be dict, got %s' % type(val))
662 try:
662 try:
663 self._user_data = json.dumps(val)
663 self._user_data = json.dumps(val)
664 except Exception:
664 except Exception:
665 log.error(traceback.format_exc())
665 log.error(traceback.format_exc())
666
666
667 @classmethod
667 @classmethod
668 def get_by_username(cls, username, case_insensitive=False,
668 def get_by_username(cls, username, case_insensitive=False,
669 cache=False, identity_cache=False):
669 cache=False, identity_cache=False):
670 session = Session()
670 session = Session()
671
671
672 if case_insensitive:
672 if case_insensitive:
673 q = cls.query().filter(
673 q = cls.query().filter(
674 func.lower(cls.username) == func.lower(username))
674 func.lower(cls.username) == func.lower(username))
675 else:
675 else:
676 q = cls.query().filter(cls.username == username)
676 q = cls.query().filter(cls.username == username)
677
677
678 if cache:
678 if cache:
679 if identity_cache:
679 if identity_cache:
680 val = cls.identity_cache(session, 'username', username)
680 val = cls.identity_cache(session, 'username', username)
681 if val:
681 if val:
682 return val
682 return val
683 else:
683 else:
684 q = q.options(
684 q = q.options(
685 FromCache("sql_cache_short",
685 FromCache("sql_cache_short",
686 "get_user_by_name_%s" % _hash_key(username)))
686 "get_user_by_name_%s" % _hash_key(username)))
687
687
688 return q.scalar()
688 return q.scalar()
689
689
690 @classmethod
690 @classmethod
691 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
691 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
692 q = cls.query().filter(cls.api_key == auth_token)
692 q = cls.query().filter(cls.api_key == auth_token)
693
693
694 if cache:
694 if cache:
695 q = q.options(FromCache("sql_cache_short",
695 q = q.options(FromCache("sql_cache_short",
696 "get_auth_token_%s" % auth_token))
696 "get_auth_token_%s" % auth_token))
697 res = q.scalar()
697 res = q.scalar()
698
698
699 if fallback and not res:
699 if fallback and not res:
700 #fallback to additional keys
700 #fallback to additional keys
701 _res = UserApiKeys.query()\
701 _res = UserApiKeys.query()\
702 .filter(UserApiKeys.api_key == auth_token)\
702 .filter(UserApiKeys.api_key == auth_token)\
703 .filter(or_(UserApiKeys.expires == -1,
703 .filter(or_(UserApiKeys.expires == -1,
704 UserApiKeys.expires >= time.time()))\
704 UserApiKeys.expires >= time.time()))\
705 .first()
705 .first()
706 if _res:
706 if _res:
707 res = _res.user
707 res = _res.user
708 return res
708 return res
709
709
710 @classmethod
710 @classmethod
711 def get_by_email(cls, email, case_insensitive=False, cache=False):
711 def get_by_email(cls, email, case_insensitive=False, cache=False):
712
712
713 if case_insensitive:
713 if case_insensitive:
714 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
714 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
715
715
716 else:
716 else:
717 q = cls.query().filter(cls.email == email)
717 q = cls.query().filter(cls.email == email)
718
718
719 if cache:
719 if cache:
720 q = q.options(FromCache("sql_cache_short",
720 q = q.options(FromCache("sql_cache_short",
721 "get_email_key_%s" % _hash_key(email)))
721 "get_email_key_%s" % _hash_key(email)))
722
722
723 ret = q.scalar()
723 ret = q.scalar()
724 if ret is None:
724 if ret is None:
725 q = UserEmailMap.query()
725 q = UserEmailMap.query()
726 # try fetching in alternate email map
726 # try fetching in alternate email map
727 if case_insensitive:
727 if case_insensitive:
728 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
728 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
729 else:
729 else:
730 q = q.filter(UserEmailMap.email == email)
730 q = q.filter(UserEmailMap.email == email)
731 q = q.options(joinedload(UserEmailMap.user))
731 q = q.options(joinedload(UserEmailMap.user))
732 if cache:
732 if cache:
733 q = q.options(FromCache("sql_cache_short",
733 q = q.options(FromCache("sql_cache_short",
734 "get_email_map_key_%s" % email))
734 "get_email_map_key_%s" % email))
735 ret = getattr(q.scalar(), 'user', None)
735 ret = getattr(q.scalar(), 'user', None)
736
736
737 return ret
737 return ret
738
738
739 @classmethod
739 @classmethod
740 def get_from_cs_author(cls, author):
740 def get_from_cs_author(cls, author):
741 """
741 """
742 Tries to get User objects out of commit author string
742 Tries to get User objects out of commit author string
743
743
744 :param author:
744 :param author:
745 """
745 """
746 from rhodecode.lib.helpers import email, author_name
746 from rhodecode.lib.helpers import email, author_name
747 # Valid email in the attribute passed, see if they're in the system
747 # Valid email in the attribute passed, see if they're in the system
748 _email = email(author)
748 _email = email(author)
749 if _email:
749 if _email:
750 user = cls.get_by_email(_email, case_insensitive=True)
750 user = cls.get_by_email(_email, case_insensitive=True)
751 if user:
751 if user:
752 return user
752 return user
753 # Maybe we can match by username?
753 # Maybe we can match by username?
754 _author = author_name(author)
754 _author = author_name(author)
755 user = cls.get_by_username(_author, case_insensitive=True)
755 user = cls.get_by_username(_author, case_insensitive=True)
756 if user:
756 if user:
757 return user
757 return user
758
758
759 def update_userdata(self, **kwargs):
759 def update_userdata(self, **kwargs):
760 usr = self
760 usr = self
761 old = usr.user_data
761 old = usr.user_data
762 old.update(**kwargs)
762 old.update(**kwargs)
763 usr.user_data = old
763 usr.user_data = old
764 Session().add(usr)
764 Session().add(usr)
765 log.debug('updated userdata with ', kwargs)
765 log.debug('updated userdata with ', kwargs)
766
766
767 def update_lastlogin(self):
767 def update_lastlogin(self):
768 """Update user lastlogin"""
768 """Update user lastlogin"""
769 self.last_login = datetime.datetime.now()
769 self.last_login = datetime.datetime.now()
770 Session().add(self)
770 Session().add(self)
771 log.debug('updated user %s lastlogin', self.username)
771 log.debug('updated user %s lastlogin', self.username)
772
772
773 def update_lastactivity(self):
773 def update_lastactivity(self):
774 """Update user lastactivity"""
774 """Update user lastactivity"""
775 usr = self
775 usr = self
776 old = usr.user_data
776 old = usr.user_data
777 old.update({'last_activity': time.time()})
777 old.update({'last_activity': time.time()})
778 usr.user_data = old
778 usr.user_data = old
779 Session().add(usr)
779 Session().add(usr)
780 log.debug('updated user %s lastactivity', usr.username)
780 log.debug('updated user %s lastactivity', usr.username)
781
781
782 def update_password(self, new_password, change_api_key=False):
782 def update_password(self, new_password, change_api_key=False):
783 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
783 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
784
784
785 self.password = get_crypt_password(new_password)
785 self.password = get_crypt_password(new_password)
786 if change_api_key:
786 if change_api_key:
787 self.api_key = generate_auth_token(self.username)
787 self.api_key = generate_auth_token(self.username)
788 Session().add(self)
788 Session().add(self)
789
789
790 @classmethod
790 @classmethod
791 def get_first_super_admin(cls):
791 def get_first_super_admin(cls):
792 user = User.query().filter(User.admin == true()).first()
792 user = User.query().filter(User.admin == true()).first()
793 if user is None:
793 if user is None:
794 raise Exception('FATAL: Missing administrative account!')
794 raise Exception('FATAL: Missing administrative account!')
795 return user
795 return user
796
796
797 @classmethod
797 @classmethod
798 def get_all_super_admins(cls):
798 def get_all_super_admins(cls):
799 """
799 """
800 Returns all admin accounts sorted by username
800 Returns all admin accounts sorted by username
801 """
801 """
802 return User.query().filter(User.admin == true())\
802 return User.query().filter(User.admin == true())\
803 .order_by(User.username.asc()).all()
803 .order_by(User.username.asc()).all()
804
804
805 @classmethod
805 @classmethod
806 def get_default_user(cls, cache=False):
806 def get_default_user(cls, cache=False):
807 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
807 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
808 if user is None:
808 if user is None:
809 raise Exception('FATAL: Missing default account!')
809 raise Exception('FATAL: Missing default account!')
810 return user
810 return user
811
811
812 def _get_default_perms(self, user, suffix=''):
812 def _get_default_perms(self, user, suffix=''):
813 from rhodecode.model.permission import PermissionModel
813 from rhodecode.model.permission import PermissionModel
814 return PermissionModel().get_default_perms(user.user_perms, suffix)
814 return PermissionModel().get_default_perms(user.user_perms, suffix)
815
815
816 def get_default_perms(self, suffix=''):
816 def get_default_perms(self, suffix=''):
817 return self._get_default_perms(self, suffix)
817 return self._get_default_perms(self, suffix)
818
818
819 def get_api_data(self, include_secrets=False, details='full'):
819 def get_api_data(self, include_secrets=False, details='full'):
820 """
820 """
821 Common function for generating user related data for API
821 Common function for generating user related data for API
822
822
823 :param include_secrets: By default secrets in the API data will be replaced
823 :param include_secrets: By default secrets in the API data will be replaced
824 by a placeholder value to prevent exposing this data by accident. In case
824 by a placeholder value to prevent exposing this data by accident. In case
825 this data shall be exposed, set this flag to ``True``.
825 this data shall be exposed, set this flag to ``True``.
826
826
827 :param details: details can be 'basic|full' basic gives only a subset of
827 :param details: details can be 'basic|full' basic gives only a subset of
828 the available user information that includes user_id, name and emails.
828 the available user information that includes user_id, name and emails.
829 """
829 """
830 user = self
830 user = self
831 user_data = self.user_data
831 user_data = self.user_data
832 data = {
832 data = {
833 'user_id': user.user_id,
833 'user_id': user.user_id,
834 'username': user.username,
834 'username': user.username,
835 'firstname': user.name,
835 'firstname': user.name,
836 'lastname': user.lastname,
836 'lastname': user.lastname,
837 'email': user.email,
837 'email': user.email,
838 'emails': user.emails,
838 'emails': user.emails,
839 }
839 }
840 if details == 'basic':
840 if details == 'basic':
841 return data
841 return data
842
842
843 api_key_length = 40
843 api_key_length = 40
844 api_key_replacement = '*' * api_key_length
844 api_key_replacement = '*' * api_key_length
845
845
846 extras = {
846 extras = {
847 'api_key': api_key_replacement,
847 'api_key': api_key_replacement,
848 'api_keys': [api_key_replacement],
848 'api_keys': [api_key_replacement],
849 'active': user.active,
849 'active': user.active,
850 'admin': user.admin,
850 'admin': user.admin,
851 'extern_type': user.extern_type,
851 'extern_type': user.extern_type,
852 'extern_name': user.extern_name,
852 'extern_name': user.extern_name,
853 'last_login': user.last_login,
853 'last_login': user.last_login,
854 'ip_addresses': user.ip_addresses,
854 'ip_addresses': user.ip_addresses,
855 'language': user_data.get('language')
855 'language': user_data.get('language')
856 }
856 }
857 data.update(extras)
857 data.update(extras)
858
858
859 if include_secrets:
859 if include_secrets:
860 data['api_key'] = user.api_key
860 data['api_key'] = user.api_key
861 data['api_keys'] = user.auth_tokens
861 data['api_keys'] = user.auth_tokens
862 return data
862 return data
863
863
864 def __json__(self):
864 def __json__(self):
865 data = {
865 data = {
866 'full_name': self.full_name,
866 'full_name': self.full_name,
867 'full_name_or_username': self.full_name_or_username,
867 'full_name_or_username': self.full_name_or_username,
868 'short_contact': self.short_contact,
868 'short_contact': self.short_contact,
869 'full_contact': self.full_contact,
869 'full_contact': self.full_contact,
870 }
870 }
871 data.update(self.get_api_data())
871 data.update(self.get_api_data())
872 return data
872 return data
873
873
874
874
875 class UserApiKeys(Base, BaseModel):
875 class UserApiKeys(Base, BaseModel):
876 __tablename__ = 'user_api_keys'
876 __tablename__ = 'user_api_keys'
877 __table_args__ = (
877 __table_args__ = (
878 Index('uak_api_key_idx', 'api_key'),
878 Index('uak_api_key_idx', 'api_key'),
879 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
879 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
880 UniqueConstraint('api_key'),
880 UniqueConstraint('api_key'),
881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
883 )
883 )
884 __mapper_args__ = {}
884 __mapper_args__ = {}
885
885
886 # ApiKey role
886 # ApiKey role
887 ROLE_ALL = 'token_role_all'
887 ROLE_ALL = 'token_role_all'
888 ROLE_HTTP = 'token_role_http'
888 ROLE_HTTP = 'token_role_http'
889 ROLE_VCS = 'token_role_vcs'
889 ROLE_VCS = 'token_role_vcs'
890 ROLE_API = 'token_role_api'
890 ROLE_API = 'token_role_api'
891 ROLE_FEED = 'token_role_feed'
891 ROLE_FEED = 'token_role_feed'
892 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
892 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
893
893
894 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
894 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
895 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
895 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
896 api_key = Column("api_key", String(255), nullable=False, unique=True)
896 api_key = Column("api_key", String(255), nullable=False, unique=True)
897 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
897 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
898 expires = Column('expires', Float(53), nullable=False)
898 expires = Column('expires', Float(53), nullable=False)
899 role = Column('role', String(255), nullable=True)
899 role = Column('role', String(255), nullable=True)
900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
901
901
902 user = relationship('User', lazy='joined')
902 user = relationship('User', lazy='joined')
903
903
904 @classmethod
904 @classmethod
905 def _get_role_name(cls, role):
905 def _get_role_name(cls, role):
906 return {
906 return {
907 cls.ROLE_ALL: _('all'),
907 cls.ROLE_ALL: _('all'),
908 cls.ROLE_HTTP: _('http/web interface'),
908 cls.ROLE_HTTP: _('http/web interface'),
909 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
909 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
910 cls.ROLE_API: _('api calls'),
910 cls.ROLE_API: _('api calls'),
911 cls.ROLE_FEED: _('feed access'),
911 cls.ROLE_FEED: _('feed access'),
912 }.get(role, role)
912 }.get(role, role)
913
913
914 @property
914 @property
915 def expired(self):
915 def expired(self):
916 if self.expires == -1:
916 if self.expires == -1:
917 return False
917 return False
918 return time.time() > self.expires
918 return time.time() > self.expires
919
919
920 @property
920 @property
921 def role_humanized(self):
921 def role_humanized(self):
922 return self._get_role_name(self.role)
922 return self._get_role_name(self.role)
923
923
924
924
925 class UserEmailMap(Base, BaseModel):
925 class UserEmailMap(Base, BaseModel):
926 __tablename__ = 'user_email_map'
926 __tablename__ = 'user_email_map'
927 __table_args__ = (
927 __table_args__ = (
928 Index('uem_email_idx', 'email'),
928 Index('uem_email_idx', 'email'),
929 UniqueConstraint('email'),
929 UniqueConstraint('email'),
930 {'extend_existing': True, 'mysql_engine': 'InnoDB',
930 {'extend_existing': True, 'mysql_engine': 'InnoDB',
931 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
931 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
932 )
932 )
933 __mapper_args__ = {}
933 __mapper_args__ = {}
934
934
935 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
935 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
936 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
937 _email = Column("email", String(255), nullable=True, unique=False, default=None)
937 _email = Column("email", String(255), nullable=True, unique=False, default=None)
938 user = relationship('User', lazy='joined')
938 user = relationship('User', lazy='joined')
939
939
940 @validates('_email')
940 @validates('_email')
941 def validate_email(self, key, email):
941 def validate_email(self, key, email):
942 # check if this email is not main one
942 # check if this email is not main one
943 main_email = Session().query(User).filter(User.email == email).scalar()
943 main_email = Session().query(User).filter(User.email == email).scalar()
944 if main_email is not None:
944 if main_email is not None:
945 raise AttributeError('email %s is present is user table' % email)
945 raise AttributeError('email %s is present is user table' % email)
946 return email
946 return email
947
947
948 @hybrid_property
948 @hybrid_property
949 def email(self):
949 def email(self):
950 return self._email
950 return self._email
951
951
952 @email.setter
952 @email.setter
953 def email(self, val):
953 def email(self, val):
954 self._email = val.lower() if val else None
954 self._email = val.lower() if val else None
955
955
956
956
957 class UserIpMap(Base, BaseModel):
957 class UserIpMap(Base, BaseModel):
958 __tablename__ = 'user_ip_map'
958 __tablename__ = 'user_ip_map'
959 __table_args__ = (
959 __table_args__ = (
960 UniqueConstraint('user_id', 'ip_addr'),
960 UniqueConstraint('user_id', 'ip_addr'),
961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
963 )
963 )
964 __mapper_args__ = {}
964 __mapper_args__ = {}
965
965
966 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
966 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
967 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
968 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
968 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
969 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
969 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
970 description = Column("description", String(10000), nullable=True, unique=None, default=None)
970 description = Column("description", String(10000), nullable=True, unique=None, default=None)
971 user = relationship('User', lazy='joined')
971 user = relationship('User', lazy='joined')
972
972
973 @classmethod
973 @classmethod
974 def _get_ip_range(cls, ip_addr):
974 def _get_ip_range(cls, ip_addr):
975 net = ipaddress.ip_network(ip_addr, strict=False)
975 net = ipaddress.ip_network(ip_addr, strict=False)
976 return [str(net.network_address), str(net.broadcast_address)]
976 return [str(net.network_address), str(net.broadcast_address)]
977
977
978 def __json__(self):
978 def __json__(self):
979 return {
979 return {
980 'ip_addr': self.ip_addr,
980 'ip_addr': self.ip_addr,
981 'ip_range': self._get_ip_range(self.ip_addr),
981 'ip_range': self._get_ip_range(self.ip_addr),
982 }
982 }
983
983
984 def __unicode__(self):
984 def __unicode__(self):
985 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
985 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
986 self.user_id, self.ip_addr)
986 self.user_id, self.ip_addr)
987
987
988 class UserLog(Base, BaseModel):
988 class UserLog(Base, BaseModel):
989 __tablename__ = 'user_logs'
989 __tablename__ = 'user_logs'
990 __table_args__ = (
990 __table_args__ = (
991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
993 )
993 )
994 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
994 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
995 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
996 username = Column("username", String(255), nullable=True, unique=None, default=None)
996 username = Column("username", String(255), nullable=True, unique=None, default=None)
997 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
997 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
998 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
998 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
999 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
999 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1000 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1000 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1001 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1001 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1002
1002
1003 def __unicode__(self):
1003 def __unicode__(self):
1004 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1004 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1005 self.repository_name,
1005 self.repository_name,
1006 self.action)
1006 self.action)
1007
1007
1008 @property
1008 @property
1009 def action_as_day(self):
1009 def action_as_day(self):
1010 return datetime.date(*self.action_date.timetuple()[:3])
1010 return datetime.date(*self.action_date.timetuple()[:3])
1011
1011
1012 user = relationship('User')
1012 user = relationship('User')
1013 repository = relationship('Repository', cascade='')
1013 repository = relationship('Repository', cascade='')
1014
1014
1015
1015
1016 class UserGroup(Base, BaseModel):
1016 class UserGroup(Base, BaseModel):
1017 __tablename__ = 'users_groups'
1017 __tablename__ = 'users_groups'
1018 __table_args__ = (
1018 __table_args__ = (
1019 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1019 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1020 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1020 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1021 )
1021 )
1022
1022
1023 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1023 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1024 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1024 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1025 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1025 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1026 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1026 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1027 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1027 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1028 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1028 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1029 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1029 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1030 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1030 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1031
1031
1032 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1032 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1033 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1033 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1034 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1034 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1035 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1035 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1036 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1036 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1037 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1037 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1038
1038
1039 user = relationship('User')
1039 user = relationship('User')
1040
1040
1041 @hybrid_property
1041 @hybrid_property
1042 def group_data(self):
1042 def group_data(self):
1043 if not self._group_data:
1043 if not self._group_data:
1044 return {}
1044 return {}
1045
1045
1046 try:
1046 try:
1047 return json.loads(self._group_data)
1047 return json.loads(self._group_data)
1048 except TypeError:
1048 except TypeError:
1049 return {}
1049 return {}
1050
1050
1051 @group_data.setter
1051 @group_data.setter
1052 def group_data(self, val):
1052 def group_data(self, val):
1053 try:
1053 try:
1054 self._group_data = json.dumps(val)
1054 self._group_data = json.dumps(val)
1055 except Exception:
1055 except Exception:
1056 log.error(traceback.format_exc())
1056 log.error(traceback.format_exc())
1057
1057
1058 def __unicode__(self):
1058 def __unicode__(self):
1059 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1059 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1060 self.users_group_id,
1060 self.users_group_id,
1061 self.users_group_name)
1061 self.users_group_name)
1062
1062
1063 @classmethod
1063 @classmethod
1064 def get_by_group_name(cls, group_name, cache=False,
1064 def get_by_group_name(cls, group_name, cache=False,
1065 case_insensitive=False):
1065 case_insensitive=False):
1066 if case_insensitive:
1066 if case_insensitive:
1067 q = cls.query().filter(func.lower(cls.users_group_name) ==
1067 q = cls.query().filter(func.lower(cls.users_group_name) ==
1068 func.lower(group_name))
1068 func.lower(group_name))
1069
1069
1070 else:
1070 else:
1071 q = cls.query().filter(cls.users_group_name == group_name)
1071 q = cls.query().filter(cls.users_group_name == group_name)
1072 if cache:
1072 if cache:
1073 q = q.options(FromCache(
1073 q = q.options(FromCache(
1074 "sql_cache_short",
1074 "sql_cache_short",
1075 "get_group_%s" % _hash_key(group_name)))
1075 "get_group_%s" % _hash_key(group_name)))
1076 return q.scalar()
1076 return q.scalar()
1077
1077
1078 @classmethod
1078 @classmethod
1079 def get(cls, user_group_id, cache=False):
1079 def get(cls, user_group_id, cache=False):
1080 user_group = cls.query()
1080 user_group = cls.query()
1081 if cache:
1081 if cache:
1082 user_group = user_group.options(FromCache("sql_cache_short",
1082 user_group = user_group.options(FromCache("sql_cache_short",
1083 "get_users_group_%s" % user_group_id))
1083 "get_users_group_%s" % user_group_id))
1084 return user_group.get(user_group_id)
1084 return user_group.get(user_group_id)
1085
1085
1086 def permissions(self, with_admins=True, with_owner=True):
1086 def permissions(self, with_admins=True, with_owner=True):
1087 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1087 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1088 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1088 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1089 joinedload(UserUserGroupToPerm.user),
1089 joinedload(UserUserGroupToPerm.user),
1090 joinedload(UserUserGroupToPerm.permission),)
1090 joinedload(UserUserGroupToPerm.permission),)
1091
1091
1092 # get owners and admins and permissions. We do a trick of re-writing
1092 # get owners and admins and permissions. We do a trick of re-writing
1093 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1093 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1094 # has a global reference and changing one object propagates to all
1094 # has a global reference and changing one object propagates to all
1095 # others. This means if admin is also an owner admin_row that change
1095 # others. This means if admin is also an owner admin_row that change
1096 # would propagate to both objects
1096 # would propagate to both objects
1097 perm_rows = []
1097 perm_rows = []
1098 for _usr in q.all():
1098 for _usr in q.all():
1099 usr = AttributeDict(_usr.user.get_dict())
1099 usr = AttributeDict(_usr.user.get_dict())
1100 usr.permission = _usr.permission.permission_name
1100 usr.permission = _usr.permission.permission_name
1101 perm_rows.append(usr)
1101 perm_rows.append(usr)
1102
1102
1103 # filter the perm rows by 'default' first and then sort them by
1103 # filter the perm rows by 'default' first and then sort them by
1104 # admin,write,read,none permissions sorted again alphabetically in
1104 # admin,write,read,none permissions sorted again alphabetically in
1105 # each group
1105 # each group
1106 perm_rows = sorted(perm_rows, key=display_sort)
1106 perm_rows = sorted(perm_rows, key=display_sort)
1107
1107
1108 _admin_perm = 'usergroup.admin'
1108 _admin_perm = 'usergroup.admin'
1109 owner_row = []
1109 owner_row = []
1110 if with_owner:
1110 if with_owner:
1111 usr = AttributeDict(self.user.get_dict())
1111 usr = AttributeDict(self.user.get_dict())
1112 usr.owner_row = True
1112 usr.owner_row = True
1113 usr.permission = _admin_perm
1113 usr.permission = _admin_perm
1114 owner_row.append(usr)
1114 owner_row.append(usr)
1115
1115
1116 super_admin_rows = []
1116 super_admin_rows = []
1117 if with_admins:
1117 if with_admins:
1118 for usr in User.get_all_super_admins():
1118 for usr in User.get_all_super_admins():
1119 # if this admin is also owner, don't double the record
1119 # if this admin is also owner, don't double the record
1120 if usr.user_id == owner_row[0].user_id:
1120 if usr.user_id == owner_row[0].user_id:
1121 owner_row[0].admin_row = True
1121 owner_row[0].admin_row = True
1122 else:
1122 else:
1123 usr = AttributeDict(usr.get_dict())
1123 usr = AttributeDict(usr.get_dict())
1124 usr.admin_row = True
1124 usr.admin_row = True
1125 usr.permission = _admin_perm
1125 usr.permission = _admin_perm
1126 super_admin_rows.append(usr)
1126 super_admin_rows.append(usr)
1127
1127
1128 return super_admin_rows + owner_row + perm_rows
1128 return super_admin_rows + owner_row + perm_rows
1129
1129
1130 def permission_user_groups(self):
1130 def permission_user_groups(self):
1131 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1131 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1132 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1132 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1133 joinedload(UserGroupUserGroupToPerm.target_user_group),
1133 joinedload(UserGroupUserGroupToPerm.target_user_group),
1134 joinedload(UserGroupUserGroupToPerm.permission),)
1134 joinedload(UserGroupUserGroupToPerm.permission),)
1135
1135
1136 perm_rows = []
1136 perm_rows = []
1137 for _user_group in q.all():
1137 for _user_group in q.all():
1138 usr = AttributeDict(_user_group.user_group.get_dict())
1138 usr = AttributeDict(_user_group.user_group.get_dict())
1139 usr.permission = _user_group.permission.permission_name
1139 usr.permission = _user_group.permission.permission_name
1140 perm_rows.append(usr)
1140 perm_rows.append(usr)
1141
1141
1142 return perm_rows
1142 return perm_rows
1143
1143
1144 def _get_default_perms(self, user_group, suffix=''):
1144 def _get_default_perms(self, user_group, suffix=''):
1145 from rhodecode.model.permission import PermissionModel
1145 from rhodecode.model.permission import PermissionModel
1146 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1146 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1147
1147
1148 def get_default_perms(self, suffix=''):
1148 def get_default_perms(self, suffix=''):
1149 return self._get_default_perms(self, suffix)
1149 return self._get_default_perms(self, suffix)
1150
1150
1151 def get_api_data(self, with_group_members=True, include_secrets=False):
1151 def get_api_data(self, with_group_members=True, include_secrets=False):
1152 """
1152 """
1153 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1153 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1154 basically forwarded.
1154 basically forwarded.
1155
1155
1156 """
1156 """
1157 user_group = self
1157 user_group = self
1158
1158
1159 data = {
1159 data = {
1160 'users_group_id': user_group.users_group_id,
1160 'users_group_id': user_group.users_group_id,
1161 'group_name': user_group.users_group_name,
1161 'group_name': user_group.users_group_name,
1162 'group_description': user_group.user_group_description,
1162 'group_description': user_group.user_group_description,
1163 'active': user_group.users_group_active,
1163 'active': user_group.users_group_active,
1164 'owner': user_group.user.username,
1164 'owner': user_group.user.username,
1165 }
1165 }
1166 if with_group_members:
1166 if with_group_members:
1167 users = []
1167 users = []
1168 for user in user_group.members:
1168 for user in user_group.members:
1169 user = user.user
1169 user = user.user
1170 users.append(user.get_api_data(include_secrets=include_secrets))
1170 users.append(user.get_api_data(include_secrets=include_secrets))
1171 data['users'] = users
1171 data['users'] = users
1172
1172
1173 return data
1173 return data
1174
1174
1175
1175
1176 class UserGroupMember(Base, BaseModel):
1176 class UserGroupMember(Base, BaseModel):
1177 __tablename__ = 'users_groups_members'
1177 __tablename__ = 'users_groups_members'
1178 __table_args__ = (
1178 __table_args__ = (
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1181 )
1181 )
1182
1182
1183 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1183 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1184 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1184 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1185 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1185 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1186
1186
1187 user = relationship('User', lazy='joined')
1187 user = relationship('User', lazy='joined')
1188 users_group = relationship('UserGroup')
1188 users_group = relationship('UserGroup')
1189
1189
1190 def __init__(self, gr_id='', u_id=''):
1190 def __init__(self, gr_id='', u_id=''):
1191 self.users_group_id = gr_id
1191 self.users_group_id = gr_id
1192 self.user_id = u_id
1192 self.user_id = u_id
1193
1193
1194
1194
1195 class RepositoryField(Base, BaseModel):
1195 class RepositoryField(Base, BaseModel):
1196 __tablename__ = 'repositories_fields'
1196 __tablename__ = 'repositories_fields'
1197 __table_args__ = (
1197 __table_args__ = (
1198 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1198 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1200 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1200 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1201 )
1201 )
1202 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1202 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1203
1203
1204 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1204 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1205 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1205 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1206 field_key = Column("field_key", String(250))
1206 field_key = Column("field_key", String(250))
1207 field_label = Column("field_label", String(1024), nullable=False)
1207 field_label = Column("field_label", String(1024), nullable=False)
1208 field_value = Column("field_value", String(10000), nullable=False)
1208 field_value = Column("field_value", String(10000), nullable=False)
1209 field_desc = Column("field_desc", String(1024), nullable=False)
1209 field_desc = Column("field_desc", String(1024), nullable=False)
1210 field_type = Column("field_type", String(255), nullable=False, unique=None)
1210 field_type = Column("field_type", String(255), nullable=False, unique=None)
1211 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1211 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1212
1212
1213 repository = relationship('Repository')
1213 repository = relationship('Repository')
1214
1214
1215 @property
1215 @property
1216 def field_key_prefixed(self):
1216 def field_key_prefixed(self):
1217 return 'ex_%s' % self.field_key
1217 return 'ex_%s' % self.field_key
1218
1218
1219 @classmethod
1219 @classmethod
1220 def un_prefix_key(cls, key):
1220 def un_prefix_key(cls, key):
1221 if key.startswith(cls.PREFIX):
1221 if key.startswith(cls.PREFIX):
1222 return key[len(cls.PREFIX):]
1222 return key[len(cls.PREFIX):]
1223 return key
1223 return key
1224
1224
1225 @classmethod
1225 @classmethod
1226 def get_by_key_name(cls, key, repo):
1226 def get_by_key_name(cls, key, repo):
1227 row = cls.query()\
1227 row = cls.query()\
1228 .filter(cls.repository == repo)\
1228 .filter(cls.repository == repo)\
1229 .filter(cls.field_key == key).scalar()
1229 .filter(cls.field_key == key).scalar()
1230 return row
1230 return row
1231
1231
1232
1232
1233 class Repository(Base, BaseModel):
1233 class Repository(Base, BaseModel):
1234 __tablename__ = 'repositories'
1234 __tablename__ = 'repositories'
1235 __table_args__ = (
1235 __table_args__ = (
1236 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1236 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1238 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1238 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1239 )
1239 )
1240 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1240 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1241 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1241 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1242
1242
1243 STATE_CREATED = 'repo_state_created'
1243 STATE_CREATED = 'repo_state_created'
1244 STATE_PENDING = 'repo_state_pending'
1244 STATE_PENDING = 'repo_state_pending'
1245 STATE_ERROR = 'repo_state_error'
1245 STATE_ERROR = 'repo_state_error'
1246
1246
1247 LOCK_AUTOMATIC = 'lock_auto'
1247 LOCK_AUTOMATIC = 'lock_auto'
1248 LOCK_API = 'lock_api'
1248 LOCK_API = 'lock_api'
1249 LOCK_WEB = 'lock_web'
1249 LOCK_WEB = 'lock_web'
1250 LOCK_PULL = 'lock_pull'
1250 LOCK_PULL = 'lock_pull'
1251
1251
1252 NAME_SEP = URL_SEP
1252 NAME_SEP = URL_SEP
1253
1253
1254 repo_id = Column(
1254 repo_id = Column(
1255 "repo_id", Integer(), nullable=False, unique=True, default=None,
1255 "repo_id", Integer(), nullable=False, unique=True, default=None,
1256 primary_key=True)
1256 primary_key=True)
1257 _repo_name = Column(
1257 _repo_name = Column(
1258 "repo_name", Text(), nullable=False, default=None)
1258 "repo_name", Text(), nullable=False, default=None)
1259 _repo_name_hash = Column(
1259 _repo_name_hash = Column(
1260 "repo_name_hash", String(255), nullable=False, unique=True)
1260 "repo_name_hash", String(255), nullable=False, unique=True)
1261 repo_state = Column("repo_state", String(255), nullable=True)
1261 repo_state = Column("repo_state", String(255), nullable=True)
1262
1262
1263 clone_uri = Column(
1263 clone_uri = Column(
1264 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1264 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1265 default=None)
1265 default=None)
1266 repo_type = Column(
1266 repo_type = Column(
1267 "repo_type", String(255), nullable=False, unique=False, default=None)
1267 "repo_type", String(255), nullable=False, unique=False, default=None)
1268 user_id = Column(
1268 user_id = Column(
1269 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1269 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1270 unique=False, default=None)
1270 unique=False, default=None)
1271 private = Column(
1271 private = Column(
1272 "private", Boolean(), nullable=True, unique=None, default=None)
1272 "private", Boolean(), nullable=True, unique=None, default=None)
1273 enable_statistics = Column(
1273 enable_statistics = Column(
1274 "statistics", Boolean(), nullable=True, unique=None, default=True)
1274 "statistics", Boolean(), nullable=True, unique=None, default=True)
1275 enable_downloads = Column(
1275 enable_downloads = Column(
1276 "downloads", Boolean(), nullable=True, unique=None, default=True)
1276 "downloads", Boolean(), nullable=True, unique=None, default=True)
1277 description = Column(
1277 description = Column(
1278 "description", String(10000), nullable=True, unique=None, default=None)
1278 "description", String(10000), nullable=True, unique=None, default=None)
1279 created_on = Column(
1279 created_on = Column(
1280 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1280 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1281 default=datetime.datetime.now)
1281 default=datetime.datetime.now)
1282 updated_on = Column(
1282 updated_on = Column(
1283 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1283 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1284 default=datetime.datetime.now)
1284 default=datetime.datetime.now)
1285 _landing_revision = Column(
1285 _landing_revision = Column(
1286 "landing_revision", String(255), nullable=False, unique=False,
1286 "landing_revision", String(255), nullable=False, unique=False,
1287 default=None)
1287 default=None)
1288 enable_locking = Column(
1288 enable_locking = Column(
1289 "enable_locking", Boolean(), nullable=False, unique=None,
1289 "enable_locking", Boolean(), nullable=False, unique=None,
1290 default=False)
1290 default=False)
1291 _locked = Column(
1291 _locked = Column(
1292 "locked", String(255), nullable=True, unique=False, default=None)
1292 "locked", String(255), nullable=True, unique=False, default=None)
1293 _changeset_cache = Column(
1293 _changeset_cache = Column(
1294 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1294 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1295
1295
1296 fork_id = Column(
1296 fork_id = Column(
1297 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1297 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1298 nullable=True, unique=False, default=None)
1298 nullable=True, unique=False, default=None)
1299 group_id = Column(
1299 group_id = Column(
1300 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1300 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1301 unique=False, default=None)
1301 unique=False, default=None)
1302
1302
1303 user = relationship('User', lazy='joined')
1303 user = relationship('User', lazy='joined')
1304 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1304 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1305 group = relationship('RepoGroup', lazy='joined')
1305 group = relationship('RepoGroup', lazy='joined')
1306 repo_to_perm = relationship(
1306 repo_to_perm = relationship(
1307 'UserRepoToPerm', cascade='all',
1307 'UserRepoToPerm', cascade='all',
1308 order_by='UserRepoToPerm.repo_to_perm_id')
1308 order_by='UserRepoToPerm.repo_to_perm_id')
1309 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1309 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 stats = relationship('Statistics', cascade='all', uselist=False)
1310 stats = relationship('Statistics', cascade='all', uselist=False)
1311
1311
1312 followers = relationship(
1312 followers = relationship(
1313 'UserFollowing',
1313 'UserFollowing',
1314 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1314 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1315 cascade='all')
1315 cascade='all')
1316 extra_fields = relationship(
1316 extra_fields = relationship(
1317 'RepositoryField', cascade="all, delete, delete-orphan")
1317 'RepositoryField', cascade="all, delete, delete-orphan")
1318 logs = relationship('UserLog')
1318 logs = relationship('UserLog')
1319 comments = relationship(
1319 comments = relationship(
1320 'ChangesetComment', cascade="all, delete, delete-orphan")
1320 'ChangesetComment', cascade="all, delete, delete-orphan")
1321 pull_requests_source = relationship(
1321 pull_requests_source = relationship(
1322 'PullRequest',
1322 'PullRequest',
1323 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1323 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1324 cascade="all, delete, delete-orphan")
1324 cascade="all, delete, delete-orphan")
1325 pull_requests_target = relationship(
1325 pull_requests_target = relationship(
1326 'PullRequest',
1326 'PullRequest',
1327 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1327 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1328 cascade="all, delete, delete-orphan")
1328 cascade="all, delete, delete-orphan")
1329 ui = relationship('RepoRhodeCodeUi', cascade="all")
1329 ui = relationship('RepoRhodeCodeUi', cascade="all")
1330 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1330 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1331 integrations = relationship('Integration',
1331 integrations = relationship('Integration',
1332 cascade="all, delete, delete-orphan")
1332 cascade="all, delete, delete-orphan")
1333
1333
1334 def __unicode__(self):
1334 def __unicode__(self):
1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1336 safe_unicode(self.repo_name))
1336 safe_unicode(self.repo_name))
1337
1337
1338 @hybrid_property
1338 @hybrid_property
1339 def landing_rev(self):
1339 def landing_rev(self):
1340 # always should return [rev_type, rev]
1340 # always should return [rev_type, rev]
1341 if self._landing_revision:
1341 if self._landing_revision:
1342 _rev_info = self._landing_revision.split(':')
1342 _rev_info = self._landing_revision.split(':')
1343 if len(_rev_info) < 2:
1343 if len(_rev_info) < 2:
1344 _rev_info.insert(0, 'rev')
1344 _rev_info.insert(0, 'rev')
1345 return [_rev_info[0], _rev_info[1]]
1345 return [_rev_info[0], _rev_info[1]]
1346 return [None, None]
1346 return [None, None]
1347
1347
1348 @landing_rev.setter
1348 @landing_rev.setter
1349 def landing_rev(self, val):
1349 def landing_rev(self, val):
1350 if ':' not in val:
1350 if ':' not in val:
1351 raise ValueError('value must be delimited with `:` and consist '
1351 raise ValueError('value must be delimited with `:` and consist '
1352 'of <rev_type>:<rev>, got %s instead' % val)
1352 'of <rev_type>:<rev>, got %s instead' % val)
1353 self._landing_revision = val
1353 self._landing_revision = val
1354
1354
1355 @hybrid_property
1355 @hybrid_property
1356 def locked(self):
1356 def locked(self):
1357 if self._locked:
1357 if self._locked:
1358 user_id, timelocked, reason = self._locked.split(':')
1358 user_id, timelocked, reason = self._locked.split(':')
1359 lock_values = int(user_id), timelocked, reason
1359 lock_values = int(user_id), timelocked, reason
1360 else:
1360 else:
1361 lock_values = [None, None, None]
1361 lock_values = [None, None, None]
1362 return lock_values
1362 return lock_values
1363
1363
1364 @locked.setter
1364 @locked.setter
1365 def locked(self, val):
1365 def locked(self, val):
1366 if val and isinstance(val, (list, tuple)):
1366 if val and isinstance(val, (list, tuple)):
1367 self._locked = ':'.join(map(str, val))
1367 self._locked = ':'.join(map(str, val))
1368 else:
1368 else:
1369 self._locked = None
1369 self._locked = None
1370
1370
1371 @hybrid_property
1371 @hybrid_property
1372 def changeset_cache(self):
1372 def changeset_cache(self):
1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1374 dummy = EmptyCommit().__json__()
1374 dummy = EmptyCommit().__json__()
1375 if not self._changeset_cache:
1375 if not self._changeset_cache:
1376 return dummy
1376 return dummy
1377 try:
1377 try:
1378 return json.loads(self._changeset_cache)
1378 return json.loads(self._changeset_cache)
1379 except TypeError:
1379 except TypeError:
1380 return dummy
1380 return dummy
1381 except Exception:
1381 except Exception:
1382 log.error(traceback.format_exc())
1382 log.error(traceback.format_exc())
1383 return dummy
1383 return dummy
1384
1384
1385 @changeset_cache.setter
1385 @changeset_cache.setter
1386 def changeset_cache(self, val):
1386 def changeset_cache(self, val):
1387 try:
1387 try:
1388 self._changeset_cache = json.dumps(val)
1388 self._changeset_cache = json.dumps(val)
1389 except Exception:
1389 except Exception:
1390 log.error(traceback.format_exc())
1390 log.error(traceback.format_exc())
1391
1391
1392 @hybrid_property
1392 @hybrid_property
1393 def repo_name(self):
1393 def repo_name(self):
1394 return self._repo_name
1394 return self._repo_name
1395
1395
1396 @repo_name.setter
1396 @repo_name.setter
1397 def repo_name(self, value):
1397 def repo_name(self, value):
1398 self._repo_name = value
1398 self._repo_name = value
1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1400
1400
1401 @classmethod
1401 @classmethod
1402 def normalize_repo_name(cls, repo_name):
1402 def normalize_repo_name(cls, repo_name):
1403 """
1403 """
1404 Normalizes os specific repo_name to the format internally stored inside
1404 Normalizes os specific repo_name to the format internally stored inside
1405 database using URL_SEP
1405 database using URL_SEP
1406
1406
1407 :param cls:
1407 :param cls:
1408 :param repo_name:
1408 :param repo_name:
1409 """
1409 """
1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1411
1411
1412 @classmethod
1412 @classmethod
1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1414 session = Session()
1414 session = Session()
1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1416
1416
1417 if cache:
1417 if cache:
1418 if identity_cache:
1418 if identity_cache:
1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1420 if val:
1420 if val:
1421 return val
1421 return val
1422 else:
1422 else:
1423 q = q.options(
1423 q = q.options(
1424 FromCache("sql_cache_short",
1424 FromCache("sql_cache_short",
1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1426
1426
1427 return q.scalar()
1427 return q.scalar()
1428
1428
1429 @classmethod
1429 @classmethod
1430 def get_by_full_path(cls, repo_full_path):
1430 def get_by_full_path(cls, repo_full_path):
1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1432 repo_name = cls.normalize_repo_name(repo_name)
1432 repo_name = cls.normalize_repo_name(repo_name)
1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1434
1434
1435 @classmethod
1435 @classmethod
1436 def get_repo_forks(cls, repo_id):
1436 def get_repo_forks(cls, repo_id):
1437 return cls.query().filter(Repository.fork_id == repo_id)
1437 return cls.query().filter(Repository.fork_id == repo_id)
1438
1438
1439 @classmethod
1439 @classmethod
1440 def base_path(cls):
1440 def base_path(cls):
1441 """
1441 """
1442 Returns base path when all repos are stored
1442 Returns base path when all repos are stored
1443
1443
1444 :param cls:
1444 :param cls:
1445 """
1445 """
1446 q = Session().query(RhodeCodeUi)\
1446 q = Session().query(RhodeCodeUi)\
1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1449 return q.one().ui_value
1449 return q.one().ui_value
1450
1450
1451 @classmethod
1451 @classmethod
1452 def is_valid(cls, repo_name):
1452 def is_valid(cls, repo_name):
1453 """
1453 """
1454 returns True if given repo name is a valid filesystem repository
1454 returns True if given repo name is a valid filesystem repository
1455
1455
1456 :param cls:
1456 :param cls:
1457 :param repo_name:
1457 :param repo_name:
1458 """
1458 """
1459 from rhodecode.lib.utils import is_valid_repo
1459 from rhodecode.lib.utils import is_valid_repo
1460
1460
1461 return is_valid_repo(repo_name, cls.base_path())
1461 return is_valid_repo(repo_name, cls.base_path())
1462
1462
1463 @classmethod
1463 @classmethod
1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1465 case_insensitive=True):
1465 case_insensitive=True):
1466 q = Repository.query()
1466 q = Repository.query()
1467
1467
1468 if not isinstance(user_id, Optional):
1468 if not isinstance(user_id, Optional):
1469 q = q.filter(Repository.user_id == user_id)
1469 q = q.filter(Repository.user_id == user_id)
1470
1470
1471 if not isinstance(group_id, Optional):
1471 if not isinstance(group_id, Optional):
1472 q = q.filter(Repository.group_id == group_id)
1472 q = q.filter(Repository.group_id == group_id)
1473
1473
1474 if case_insensitive:
1474 if case_insensitive:
1475 q = q.order_by(func.lower(Repository.repo_name))
1475 q = q.order_by(func.lower(Repository.repo_name))
1476 else:
1476 else:
1477 q = q.order_by(Repository.repo_name)
1477 q = q.order_by(Repository.repo_name)
1478 return q.all()
1478 return q.all()
1479
1479
1480 @property
1480 @property
1481 def forks(self):
1481 def forks(self):
1482 """
1482 """
1483 Return forks of this repo
1483 Return forks of this repo
1484 """
1484 """
1485 return Repository.get_repo_forks(self.repo_id)
1485 return Repository.get_repo_forks(self.repo_id)
1486
1486
1487 @property
1487 @property
1488 def parent(self):
1488 def parent(self):
1489 """
1489 """
1490 Returns fork parent
1490 Returns fork parent
1491 """
1491 """
1492 return self.fork
1492 return self.fork
1493
1493
1494 @property
1494 @property
1495 def just_name(self):
1495 def just_name(self):
1496 return self.repo_name.split(self.NAME_SEP)[-1]
1496 return self.repo_name.split(self.NAME_SEP)[-1]
1497
1497
1498 @property
1498 @property
1499 def groups_with_parents(self):
1499 def groups_with_parents(self):
1500 groups = []
1500 groups = []
1501 if self.group is None:
1501 if self.group is None:
1502 return groups
1502 return groups
1503
1503
1504 cur_gr = self.group
1504 cur_gr = self.group
1505 groups.insert(0, cur_gr)
1505 groups.insert(0, cur_gr)
1506 while 1:
1506 while 1:
1507 gr = getattr(cur_gr, 'parent_group', None)
1507 gr = getattr(cur_gr, 'parent_group', None)
1508 cur_gr = cur_gr.parent_group
1508 cur_gr = cur_gr.parent_group
1509 if gr is None:
1509 if gr is None:
1510 break
1510 break
1511 groups.insert(0, gr)
1511 groups.insert(0, gr)
1512
1512
1513 return groups
1513 return groups
1514
1514
1515 @property
1515 @property
1516 def groups_and_repo(self):
1516 def groups_and_repo(self):
1517 return self.groups_with_parents, self
1517 return self.groups_with_parents, self
1518
1518
1519 @LazyProperty
1519 @LazyProperty
1520 def repo_path(self):
1520 def repo_path(self):
1521 """
1521 """
1522 Returns base full path for that repository means where it actually
1522 Returns base full path for that repository means where it actually
1523 exists on a filesystem
1523 exists on a filesystem
1524 """
1524 """
1525 q = Session().query(RhodeCodeUi).filter(
1525 q = Session().query(RhodeCodeUi).filter(
1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1528 return q.one().ui_value
1528 return q.one().ui_value
1529
1529
1530 @property
1530 @property
1531 def repo_full_path(self):
1531 def repo_full_path(self):
1532 p = [self.repo_path]
1532 p = [self.repo_path]
1533 # we need to split the name by / since this is how we store the
1533 # we need to split the name by / since this is how we store the
1534 # names in the database, but that eventually needs to be converted
1534 # names in the database, but that eventually needs to be converted
1535 # into a valid system path
1535 # into a valid system path
1536 p += self.repo_name.split(self.NAME_SEP)
1536 p += self.repo_name.split(self.NAME_SEP)
1537 return os.path.join(*map(safe_unicode, p))
1537 return os.path.join(*map(safe_unicode, p))
1538
1538
1539 @property
1539 @property
1540 def cache_keys(self):
1540 def cache_keys(self):
1541 """
1541 """
1542 Returns associated cache keys for that repo
1542 Returns associated cache keys for that repo
1543 """
1543 """
1544 return CacheKey.query()\
1544 return CacheKey.query()\
1545 .filter(CacheKey.cache_args == self.repo_name)\
1545 .filter(CacheKey.cache_args == self.repo_name)\
1546 .order_by(CacheKey.cache_key)\
1546 .order_by(CacheKey.cache_key)\
1547 .all()
1547 .all()
1548
1548
1549 def get_new_name(self, repo_name):
1549 def get_new_name(self, repo_name):
1550 """
1550 """
1551 returns new full repository name based on assigned group and new new
1551 returns new full repository name based on assigned group and new new
1552
1552
1553 :param group_name:
1553 :param group_name:
1554 """
1554 """
1555 path_prefix = self.group.full_path_splitted if self.group else []
1555 path_prefix = self.group.full_path_splitted if self.group else []
1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1557
1557
1558 @property
1558 @property
1559 def _config(self):
1559 def _config(self):
1560 """
1560 """
1561 Returns db based config object.
1561 Returns db based config object.
1562 """
1562 """
1563 from rhodecode.lib.utils import make_db_config
1563 from rhodecode.lib.utils import make_db_config
1564 return make_db_config(clear_session=False, repo=self)
1564 return make_db_config(clear_session=False, repo=self)
1565
1565
1566 def permissions(self, with_admins=True, with_owner=True):
1566 def permissions(self, with_admins=True, with_owner=True):
1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1568 q = q.options(joinedload(UserRepoToPerm.repository),
1568 q = q.options(joinedload(UserRepoToPerm.repository),
1569 joinedload(UserRepoToPerm.user),
1569 joinedload(UserRepoToPerm.user),
1570 joinedload(UserRepoToPerm.permission),)
1570 joinedload(UserRepoToPerm.permission),)
1571
1571
1572 # get owners and admins and permissions. We do a trick of re-writing
1572 # get owners and admins and permissions. We do a trick of re-writing
1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1574 # has a global reference and changing one object propagates to all
1574 # has a global reference and changing one object propagates to all
1575 # others. This means if admin is also an owner admin_row that change
1575 # others. This means if admin is also an owner admin_row that change
1576 # would propagate to both objects
1576 # would propagate to both objects
1577 perm_rows = []
1577 perm_rows = []
1578 for _usr in q.all():
1578 for _usr in q.all():
1579 usr = AttributeDict(_usr.user.get_dict())
1579 usr = AttributeDict(_usr.user.get_dict())
1580 usr.permission = _usr.permission.permission_name
1580 usr.permission = _usr.permission.permission_name
1581 perm_rows.append(usr)
1581 perm_rows.append(usr)
1582
1582
1583 # filter the perm rows by 'default' first and then sort them by
1583 # filter the perm rows by 'default' first and then sort them by
1584 # admin,write,read,none permissions sorted again alphabetically in
1584 # admin,write,read,none permissions sorted again alphabetically in
1585 # each group
1585 # each group
1586 perm_rows = sorted(perm_rows, key=display_sort)
1586 perm_rows = sorted(perm_rows, key=display_sort)
1587
1587
1588 _admin_perm = 'repository.admin'
1588 _admin_perm = 'repository.admin'
1589 owner_row = []
1589 owner_row = []
1590 if with_owner:
1590 if with_owner:
1591 usr = AttributeDict(self.user.get_dict())
1591 usr = AttributeDict(self.user.get_dict())
1592 usr.owner_row = True
1592 usr.owner_row = True
1593 usr.permission = _admin_perm
1593 usr.permission = _admin_perm
1594 owner_row.append(usr)
1594 owner_row.append(usr)
1595
1595
1596 super_admin_rows = []
1596 super_admin_rows = []
1597 if with_admins:
1597 if with_admins:
1598 for usr in User.get_all_super_admins():
1598 for usr in User.get_all_super_admins():
1599 # if this admin is also owner, don't double the record
1599 # if this admin is also owner, don't double the record
1600 if usr.user_id == owner_row[0].user_id:
1600 if usr.user_id == owner_row[0].user_id:
1601 owner_row[0].admin_row = True
1601 owner_row[0].admin_row = True
1602 else:
1602 else:
1603 usr = AttributeDict(usr.get_dict())
1603 usr = AttributeDict(usr.get_dict())
1604 usr.admin_row = True
1604 usr.admin_row = True
1605 usr.permission = _admin_perm
1605 usr.permission = _admin_perm
1606 super_admin_rows.append(usr)
1606 super_admin_rows.append(usr)
1607
1607
1608 return super_admin_rows + owner_row + perm_rows
1608 return super_admin_rows + owner_row + perm_rows
1609
1609
1610 def permission_user_groups(self):
1610 def permission_user_groups(self):
1611 q = UserGroupRepoToPerm.query().filter(
1611 q = UserGroupRepoToPerm.query().filter(
1612 UserGroupRepoToPerm.repository == self)
1612 UserGroupRepoToPerm.repository == self)
1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1614 joinedload(UserGroupRepoToPerm.users_group),
1614 joinedload(UserGroupRepoToPerm.users_group),
1615 joinedload(UserGroupRepoToPerm.permission),)
1615 joinedload(UserGroupRepoToPerm.permission),)
1616
1616
1617 perm_rows = []
1617 perm_rows = []
1618 for _user_group in q.all():
1618 for _user_group in q.all():
1619 usr = AttributeDict(_user_group.users_group.get_dict())
1619 usr = AttributeDict(_user_group.users_group.get_dict())
1620 usr.permission = _user_group.permission.permission_name
1620 usr.permission = _user_group.permission.permission_name
1621 perm_rows.append(usr)
1621 perm_rows.append(usr)
1622
1622
1623 return perm_rows
1623 return perm_rows
1624
1624
1625 def get_api_data(self, include_secrets=False):
1625 def get_api_data(self, include_secrets=False):
1626 """
1626 """
1627 Common function for generating repo api data
1627 Common function for generating repo api data
1628
1628
1629 :param include_secrets: See :meth:`User.get_api_data`.
1629 :param include_secrets: See :meth:`User.get_api_data`.
1630
1630
1631 """
1631 """
1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1633 # move this methods on models level.
1633 # move this methods on models level.
1634 from rhodecode.model.settings import SettingsModel
1634 from rhodecode.model.settings import SettingsModel
1635
1635
1636 repo = self
1636 repo = self
1637 _user_id, _time, _reason = self.locked
1637 _user_id, _time, _reason = self.locked
1638
1638
1639 data = {
1639 data = {
1640 'repo_id': repo.repo_id,
1640 'repo_id': repo.repo_id,
1641 'repo_name': repo.repo_name,
1641 'repo_name': repo.repo_name,
1642 'repo_type': repo.repo_type,
1642 'repo_type': repo.repo_type,
1643 'clone_uri': repo.clone_uri or '',
1643 'clone_uri': repo.clone_uri or '',
1644 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1644 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1645 'private': repo.private,
1645 'private': repo.private,
1646 'created_on': repo.created_on,
1646 'created_on': repo.created_on,
1647 'description': repo.description,
1647 'description': repo.description,
1648 'landing_rev': repo.landing_rev,
1648 'landing_rev': repo.landing_rev,
1649 'owner': repo.user.username,
1649 'owner': repo.user.username,
1650 'fork_of': repo.fork.repo_name if repo.fork else None,
1650 'fork_of': repo.fork.repo_name if repo.fork else None,
1651 'enable_statistics': repo.enable_statistics,
1651 'enable_statistics': repo.enable_statistics,
1652 'enable_locking': repo.enable_locking,
1652 'enable_locking': repo.enable_locking,
1653 'enable_downloads': repo.enable_downloads,
1653 'enable_downloads': repo.enable_downloads,
1654 'last_changeset': repo.changeset_cache,
1654 'last_changeset': repo.changeset_cache,
1655 'locked_by': User.get(_user_id).get_api_data(
1655 'locked_by': User.get(_user_id).get_api_data(
1656 include_secrets=include_secrets) if _user_id else None,
1656 include_secrets=include_secrets) if _user_id else None,
1657 'locked_date': time_to_datetime(_time) if _time else None,
1657 'locked_date': time_to_datetime(_time) if _time else None,
1658 'lock_reason': _reason if _reason else None,
1658 'lock_reason': _reason if _reason else None,
1659 }
1659 }
1660
1660
1661 # TODO: mikhail: should be per-repo settings here
1661 # TODO: mikhail: should be per-repo settings here
1662 rc_config = SettingsModel().get_all_settings()
1662 rc_config = SettingsModel().get_all_settings()
1663 repository_fields = str2bool(
1663 repository_fields = str2bool(
1664 rc_config.get('rhodecode_repository_fields'))
1664 rc_config.get('rhodecode_repository_fields'))
1665 if repository_fields:
1665 if repository_fields:
1666 for f in self.extra_fields:
1666 for f in self.extra_fields:
1667 data[f.field_key_prefixed] = f.field_value
1667 data[f.field_key_prefixed] = f.field_value
1668
1668
1669 return data
1669 return data
1670
1670
1671 @classmethod
1671 @classmethod
1672 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1672 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1673 if not lock_time:
1673 if not lock_time:
1674 lock_time = time.time()
1674 lock_time = time.time()
1675 if not lock_reason:
1675 if not lock_reason:
1676 lock_reason = cls.LOCK_AUTOMATIC
1676 lock_reason = cls.LOCK_AUTOMATIC
1677 repo.locked = [user_id, lock_time, lock_reason]
1677 repo.locked = [user_id, lock_time, lock_reason]
1678 Session().add(repo)
1678 Session().add(repo)
1679 Session().commit()
1679 Session().commit()
1680
1680
1681 @classmethod
1681 @classmethod
1682 def unlock(cls, repo):
1682 def unlock(cls, repo):
1683 repo.locked = None
1683 repo.locked = None
1684 Session().add(repo)
1684 Session().add(repo)
1685 Session().commit()
1685 Session().commit()
1686
1686
1687 @classmethod
1687 @classmethod
1688 def getlock(cls, repo):
1688 def getlock(cls, repo):
1689 return repo.locked
1689 return repo.locked
1690
1690
1691 def is_user_lock(self, user_id):
1691 def is_user_lock(self, user_id):
1692 if self.lock[0]:
1692 if self.lock[0]:
1693 lock_user_id = safe_int(self.lock[0])
1693 lock_user_id = safe_int(self.lock[0])
1694 user_id = safe_int(user_id)
1694 user_id = safe_int(user_id)
1695 # both are ints, and they are equal
1695 # both are ints, and they are equal
1696 return all([lock_user_id, user_id]) and lock_user_id == user_id
1696 return all([lock_user_id, user_id]) and lock_user_id == user_id
1697
1697
1698 return False
1698 return False
1699
1699
1700 def get_locking_state(self, action, user_id, only_when_enabled=True):
1700 def get_locking_state(self, action, user_id, only_when_enabled=True):
1701 """
1701 """
1702 Checks locking on this repository, if locking is enabled and lock is
1702 Checks locking on this repository, if locking is enabled and lock is
1703 present returns a tuple of make_lock, locked, locked_by.
1703 present returns a tuple of make_lock, locked, locked_by.
1704 make_lock can have 3 states None (do nothing) True, make lock
1704 make_lock can have 3 states None (do nothing) True, make lock
1705 False release lock, This value is later propagated to hooks, which
1705 False release lock, This value is later propagated to hooks, which
1706 do the locking. Think about this as signals passed to hooks what to do.
1706 do the locking. Think about this as signals passed to hooks what to do.
1707
1707
1708 """
1708 """
1709 # TODO: johbo: This is part of the business logic and should be moved
1709 # TODO: johbo: This is part of the business logic and should be moved
1710 # into the RepositoryModel.
1710 # into the RepositoryModel.
1711
1711
1712 if action not in ('push', 'pull'):
1712 if action not in ('push', 'pull'):
1713 raise ValueError("Invalid action value: %s" % repr(action))
1713 raise ValueError("Invalid action value: %s" % repr(action))
1714
1714
1715 # defines if locked error should be thrown to user
1715 # defines if locked error should be thrown to user
1716 currently_locked = False
1716 currently_locked = False
1717 # defines if new lock should be made, tri-state
1717 # defines if new lock should be made, tri-state
1718 make_lock = None
1718 make_lock = None
1719 repo = self
1719 repo = self
1720 user = User.get(user_id)
1720 user = User.get(user_id)
1721
1721
1722 lock_info = repo.locked
1722 lock_info = repo.locked
1723
1723
1724 if repo and (repo.enable_locking or not only_when_enabled):
1724 if repo and (repo.enable_locking or not only_when_enabled):
1725 if action == 'push':
1725 if action == 'push':
1726 # check if it's already locked !, if it is compare users
1726 # check if it's already locked !, if it is compare users
1727 locked_by_user_id = lock_info[0]
1727 locked_by_user_id = lock_info[0]
1728 if user.user_id == locked_by_user_id:
1728 if user.user_id == locked_by_user_id:
1729 log.debug(
1729 log.debug(
1730 'Got `push` action from user %s, now unlocking', user)
1730 'Got `push` action from user %s, now unlocking', user)
1731 # unlock if we have push from user who locked
1731 # unlock if we have push from user who locked
1732 make_lock = False
1732 make_lock = False
1733 else:
1733 else:
1734 # we're not the same user who locked, ban with
1734 # we're not the same user who locked, ban with
1735 # code defined in settings (default is 423 HTTP Locked) !
1735 # code defined in settings (default is 423 HTTP Locked) !
1736 log.debug('Repo %s is currently locked by %s', repo, user)
1736 log.debug('Repo %s is currently locked by %s', repo, user)
1737 currently_locked = True
1737 currently_locked = True
1738 elif action == 'pull':
1738 elif action == 'pull':
1739 # [0] user [1] date
1739 # [0] user [1] date
1740 if lock_info[0] and lock_info[1]:
1740 if lock_info[0] and lock_info[1]:
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1742 currently_locked = True
1742 currently_locked = True
1743 else:
1743 else:
1744 log.debug('Setting lock on repo %s by %s', repo, user)
1744 log.debug('Setting lock on repo %s by %s', repo, user)
1745 make_lock = True
1745 make_lock = True
1746
1746
1747 else:
1747 else:
1748 log.debug('Repository %s do not have locking enabled', repo)
1748 log.debug('Repository %s do not have locking enabled', repo)
1749
1749
1750 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1750 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1751 make_lock, currently_locked, lock_info)
1751 make_lock, currently_locked, lock_info)
1752
1752
1753 from rhodecode.lib.auth import HasRepoPermissionAny
1753 from rhodecode.lib.auth import HasRepoPermissionAny
1754 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1754 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1755 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1755 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1756 # if we don't have at least write permission we cannot make a lock
1756 # if we don't have at least write permission we cannot make a lock
1757 log.debug('lock state reset back to FALSE due to lack '
1757 log.debug('lock state reset back to FALSE due to lack '
1758 'of at least read permission')
1758 'of at least read permission')
1759 make_lock = False
1759 make_lock = False
1760
1760
1761 return make_lock, currently_locked, lock_info
1761 return make_lock, currently_locked, lock_info
1762
1762
1763 @property
1763 @property
1764 def last_db_change(self):
1764 def last_db_change(self):
1765 return self.updated_on
1765 return self.updated_on
1766
1766
1767 @property
1767 @property
1768 def clone_uri_hidden(self):
1768 def clone_uri_hidden(self):
1769 clone_uri = self.clone_uri
1769 clone_uri = self.clone_uri
1770 if clone_uri:
1770 if clone_uri:
1771 import urlobject
1771 import urlobject
1772 url_obj = urlobject.URLObject(clone_uri)
1772 url_obj = urlobject.URLObject(clone_uri)
1773 if url_obj.password:
1773 if url_obj.password:
1774 clone_uri = url_obj.with_password('*****')
1774 clone_uri = url_obj.with_password('*****')
1775 return clone_uri
1775 return clone_uri
1776
1776
1777 def clone_url(self, **override):
1777 def clone_url(self, **override):
1778 qualified_home_url = url('home', qualified=True)
1778 qualified_home_url = url('home', qualified=True)
1779
1779
1780 uri_tmpl = None
1780 uri_tmpl = None
1781 if 'with_id' in override:
1781 if 'with_id' in override:
1782 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1782 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1783 del override['with_id']
1783 del override['with_id']
1784
1784
1785 if 'uri_tmpl' in override:
1785 if 'uri_tmpl' in override:
1786 uri_tmpl = override['uri_tmpl']
1786 uri_tmpl = override['uri_tmpl']
1787 del override['uri_tmpl']
1787 del override['uri_tmpl']
1788
1788
1789 # we didn't override our tmpl from **overrides
1789 # we didn't override our tmpl from **overrides
1790 if not uri_tmpl:
1790 if not uri_tmpl:
1791 uri_tmpl = self.DEFAULT_CLONE_URI
1791 uri_tmpl = self.DEFAULT_CLONE_URI
1792 try:
1792 try:
1793 from pylons import tmpl_context as c
1793 from pylons import tmpl_context as c
1794 uri_tmpl = c.clone_uri_tmpl
1794 uri_tmpl = c.clone_uri_tmpl
1795 except Exception:
1795 except Exception:
1796 # in any case if we call this outside of request context,
1796 # in any case if we call this outside of request context,
1797 # ie, not having tmpl_context set up
1797 # ie, not having tmpl_context set up
1798 pass
1798 pass
1799
1799
1800 return get_clone_url(uri_tmpl=uri_tmpl,
1800 return get_clone_url(uri_tmpl=uri_tmpl,
1801 qualifed_home_url=qualified_home_url,
1801 qualifed_home_url=qualified_home_url,
1802 repo_name=self.repo_name,
1802 repo_name=self.repo_name,
1803 repo_id=self.repo_id, **override)
1803 repo_id=self.repo_id, **override)
1804
1804
1805 def set_state(self, state):
1805 def set_state(self, state):
1806 self.repo_state = state
1806 self.repo_state = state
1807 Session().add(self)
1807 Session().add(self)
1808 #==========================================================================
1808 #==========================================================================
1809 # SCM PROPERTIES
1809 # SCM PROPERTIES
1810 #==========================================================================
1810 #==========================================================================
1811
1811
1812 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1812 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1813 return get_commit_safe(
1813 return get_commit_safe(
1814 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1814 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1815
1815
1816 def get_changeset(self, rev=None, pre_load=None):
1816 def get_changeset(self, rev=None, pre_load=None):
1817 warnings.warn("Use get_commit", DeprecationWarning)
1817 warnings.warn("Use get_commit", DeprecationWarning)
1818 commit_id = None
1818 commit_id = None
1819 commit_idx = None
1819 commit_idx = None
1820 if isinstance(rev, basestring):
1820 if isinstance(rev, basestring):
1821 commit_id = rev
1821 commit_id = rev
1822 else:
1822 else:
1823 commit_idx = rev
1823 commit_idx = rev
1824 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1824 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1825 pre_load=pre_load)
1825 pre_load=pre_load)
1826
1826
1827 def get_landing_commit(self):
1827 def get_landing_commit(self):
1828 """
1828 """
1829 Returns landing commit, or if that doesn't exist returns the tip
1829 Returns landing commit, or if that doesn't exist returns the tip
1830 """
1830 """
1831 _rev_type, _rev = self.landing_rev
1831 _rev_type, _rev = self.landing_rev
1832 commit = self.get_commit(_rev)
1832 commit = self.get_commit(_rev)
1833 if isinstance(commit, EmptyCommit):
1833 if isinstance(commit, EmptyCommit):
1834 return self.get_commit()
1834 return self.get_commit()
1835 return commit
1835 return commit
1836
1836
1837 def update_commit_cache(self, cs_cache=None, config=None):
1837 def update_commit_cache(self, cs_cache=None, config=None):
1838 """
1838 """
1839 Update cache of last changeset for repository, keys should be::
1839 Update cache of last changeset for repository, keys should be::
1840
1840
1841 short_id
1841 short_id
1842 raw_id
1842 raw_id
1843 revision
1843 revision
1844 parents
1844 parents
1845 message
1845 message
1846 date
1846 date
1847 author
1847 author
1848
1848
1849 :param cs_cache:
1849 :param cs_cache:
1850 """
1850 """
1851 from rhodecode.lib.vcs.backends.base import BaseChangeset
1851 from rhodecode.lib.vcs.backends.base import BaseChangeset
1852 if cs_cache is None:
1852 if cs_cache is None:
1853 # use no-cache version here
1853 # use no-cache version here
1854 scm_repo = self.scm_instance(cache=False, config=config)
1854 scm_repo = self.scm_instance(cache=False, config=config)
1855 if scm_repo:
1855 if scm_repo:
1856 cs_cache = scm_repo.get_commit(
1856 cs_cache = scm_repo.get_commit(
1857 pre_load=["author", "date", "message", "parents"])
1857 pre_load=["author", "date", "message", "parents"])
1858 else:
1858 else:
1859 cs_cache = EmptyCommit()
1859 cs_cache = EmptyCommit()
1860
1860
1861 if isinstance(cs_cache, BaseChangeset):
1861 if isinstance(cs_cache, BaseChangeset):
1862 cs_cache = cs_cache.__json__()
1862 cs_cache = cs_cache.__json__()
1863
1863
1864 def is_outdated(new_cs_cache):
1864 def is_outdated(new_cs_cache):
1865 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1865 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1866 new_cs_cache['revision'] != self.changeset_cache['revision']):
1866 new_cs_cache['revision'] != self.changeset_cache['revision']):
1867 return True
1867 return True
1868 return False
1868 return False
1869
1869
1870 # check if we have maybe already latest cached revision
1870 # check if we have maybe already latest cached revision
1871 if is_outdated(cs_cache) or not self.changeset_cache:
1871 if is_outdated(cs_cache) or not self.changeset_cache:
1872 _default = datetime.datetime.fromtimestamp(0)
1872 _default = datetime.datetime.fromtimestamp(0)
1873 last_change = cs_cache.get('date') or _default
1873 last_change = cs_cache.get('date') or _default
1874 log.debug('updated repo %s with new cs cache %s',
1874 log.debug('updated repo %s with new cs cache %s',
1875 self.repo_name, cs_cache)
1875 self.repo_name, cs_cache)
1876 self.updated_on = last_change
1876 self.updated_on = last_change
1877 self.changeset_cache = cs_cache
1877 self.changeset_cache = cs_cache
1878 Session().add(self)
1878 Session().add(self)
1879 Session().commit()
1879 Session().commit()
1880 else:
1880 else:
1881 log.debug('Skipping update_commit_cache for repo:`%s` '
1881 log.debug('Skipping update_commit_cache for repo:`%s` '
1882 'commit already with latest changes', self.repo_name)
1882 'commit already with latest changes', self.repo_name)
1883
1883
1884 @property
1884 @property
1885 def tip(self):
1885 def tip(self):
1886 return self.get_commit('tip')
1886 return self.get_commit('tip')
1887
1887
1888 @property
1888 @property
1889 def author(self):
1889 def author(self):
1890 return self.tip.author
1890 return self.tip.author
1891
1891
1892 @property
1892 @property
1893 def last_change(self):
1893 def last_change(self):
1894 return self.scm_instance().last_change
1894 return self.scm_instance().last_change
1895
1895
1896 def get_comments(self, revisions=None):
1896 def get_comments(self, revisions=None):
1897 """
1897 """
1898 Returns comments for this repository grouped by revisions
1898 Returns comments for this repository grouped by revisions
1899
1899
1900 :param revisions: filter query by revisions only
1900 :param revisions: filter query by revisions only
1901 """
1901 """
1902 cmts = ChangesetComment.query()\
1902 cmts = ChangesetComment.query()\
1903 .filter(ChangesetComment.repo == self)
1903 .filter(ChangesetComment.repo == self)
1904 if revisions:
1904 if revisions:
1905 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1905 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1906 grouped = collections.defaultdict(list)
1906 grouped = collections.defaultdict(list)
1907 for cmt in cmts.all():
1907 for cmt in cmts.all():
1908 grouped[cmt.revision].append(cmt)
1908 grouped[cmt.revision].append(cmt)
1909 return grouped
1909 return grouped
1910
1910
1911 def statuses(self, revisions=None):
1911 def statuses(self, revisions=None):
1912 """
1912 """
1913 Returns statuses for this repository
1913 Returns statuses for this repository
1914
1914
1915 :param revisions: list of revisions to get statuses for
1915 :param revisions: list of revisions to get statuses for
1916 """
1916 """
1917 statuses = ChangesetStatus.query()\
1917 statuses = ChangesetStatus.query()\
1918 .filter(ChangesetStatus.repo == self)\
1918 .filter(ChangesetStatus.repo == self)\
1919 .filter(ChangesetStatus.version == 0)
1919 .filter(ChangesetStatus.version == 0)
1920
1920
1921 if revisions:
1921 if revisions:
1922 # Try doing the filtering in chunks to avoid hitting limits
1922 # Try doing the filtering in chunks to avoid hitting limits
1923 size = 500
1923 size = 500
1924 status_results = []
1924 status_results = []
1925 for chunk in xrange(0, len(revisions), size):
1925 for chunk in xrange(0, len(revisions), size):
1926 status_results += statuses.filter(
1926 status_results += statuses.filter(
1927 ChangesetStatus.revision.in_(
1927 ChangesetStatus.revision.in_(
1928 revisions[chunk: chunk+size])
1928 revisions[chunk: chunk+size])
1929 ).all()
1929 ).all()
1930 else:
1930 else:
1931 status_results = statuses.all()
1931 status_results = statuses.all()
1932
1932
1933 grouped = {}
1933 grouped = {}
1934
1934
1935 # maybe we have open new pullrequest without a status?
1935 # maybe we have open new pullrequest without a status?
1936 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1936 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1937 status_lbl = ChangesetStatus.get_status_lbl(stat)
1937 status_lbl = ChangesetStatus.get_status_lbl(stat)
1938 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1938 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1939 for rev in pr.revisions:
1939 for rev in pr.revisions:
1940 pr_id = pr.pull_request_id
1940 pr_id = pr.pull_request_id
1941 pr_repo = pr.target_repo.repo_name
1941 pr_repo = pr.target_repo.repo_name
1942 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1942 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1943
1943
1944 for stat in status_results:
1944 for stat in status_results:
1945 pr_id = pr_repo = None
1945 pr_id = pr_repo = None
1946 if stat.pull_request:
1946 if stat.pull_request:
1947 pr_id = stat.pull_request.pull_request_id
1947 pr_id = stat.pull_request.pull_request_id
1948 pr_repo = stat.pull_request.target_repo.repo_name
1948 pr_repo = stat.pull_request.target_repo.repo_name
1949 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1949 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1950 pr_id, pr_repo]
1950 pr_id, pr_repo]
1951 return grouped
1951 return grouped
1952
1952
1953 # ==========================================================================
1953 # ==========================================================================
1954 # SCM CACHE INSTANCE
1954 # SCM CACHE INSTANCE
1955 # ==========================================================================
1955 # ==========================================================================
1956
1956
1957 def scm_instance(self, **kwargs):
1957 def scm_instance(self, **kwargs):
1958 import rhodecode
1958 import rhodecode
1959
1959
1960 # Passing a config will not hit the cache currently only used
1960 # Passing a config will not hit the cache currently only used
1961 # for repo2dbmapper
1961 # for repo2dbmapper
1962 config = kwargs.pop('config', None)
1962 config = kwargs.pop('config', None)
1963 cache = kwargs.pop('cache', None)
1963 cache = kwargs.pop('cache', None)
1964 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1964 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1965 # if cache is NOT defined use default global, else we have a full
1965 # if cache is NOT defined use default global, else we have a full
1966 # control over cache behaviour
1966 # control over cache behaviour
1967 if cache is None and full_cache and not config:
1967 if cache is None and full_cache and not config:
1968 return self._get_instance_cached()
1968 return self._get_instance_cached()
1969 return self._get_instance(cache=bool(cache), config=config)
1969 return self._get_instance(cache=bool(cache), config=config)
1970
1970
1971 def _get_instance_cached(self):
1971 def _get_instance_cached(self):
1972 @cache_region('long_term')
1972 @cache_region('long_term')
1973 def _get_repo(cache_key):
1973 def _get_repo(cache_key):
1974 return self._get_instance()
1974 return self._get_instance()
1975
1975
1976 invalidator_context = CacheKey.repo_context_cache(
1976 invalidator_context = CacheKey.repo_context_cache(
1977 _get_repo, self.repo_name, None, thread_scoped=True)
1977 _get_repo, self.repo_name, None, thread_scoped=True)
1978
1978
1979 with invalidator_context as context:
1979 with invalidator_context as context:
1980 context.invalidate()
1980 context.invalidate()
1981 repo = context.compute()
1981 repo = context.compute()
1982
1982
1983 return repo
1983 return repo
1984
1984
1985 def _get_instance(self, cache=True, config=None):
1985 def _get_instance(self, cache=True, config=None):
1986 config = config or self._config
1986 config = config or self._config
1987 custom_wire = {
1987 custom_wire = {
1988 'cache': cache # controls the vcs.remote cache
1988 'cache': cache # controls the vcs.remote cache
1989 }
1989 }
1990
1990
1991 repo = get_vcs_instance(
1991 repo = get_vcs_instance(
1992 repo_path=safe_str(self.repo_full_path),
1992 repo_path=safe_str(self.repo_full_path),
1993 config=config,
1993 config=config,
1994 with_wire=custom_wire,
1994 with_wire=custom_wire,
1995 create=False)
1995 create=False)
1996
1996
1997 return repo
1997 return repo
1998
1998
1999 def __json__(self):
1999 def __json__(self):
2000 return {'landing_rev': self.landing_rev}
2000 return {'landing_rev': self.landing_rev}
2001
2001
2002 def get_dict(self):
2002 def get_dict(self):
2003
2003
2004 # Since we transformed `repo_name` to a hybrid property, we need to
2004 # Since we transformed `repo_name` to a hybrid property, we need to
2005 # keep compatibility with the code which uses `repo_name` field.
2005 # keep compatibility with the code which uses `repo_name` field.
2006
2006
2007 result = super(Repository, self).get_dict()
2007 result = super(Repository, self).get_dict()
2008 result['repo_name'] = result.pop('_repo_name', None)
2008 result['repo_name'] = result.pop('_repo_name', None)
2009 return result
2009 return result
2010
2010
2011
2011
2012 class RepoGroup(Base, BaseModel):
2012 class RepoGroup(Base, BaseModel):
2013 __tablename__ = 'groups'
2013 __tablename__ = 'groups'
2014 __table_args__ = (
2014 __table_args__ = (
2015 UniqueConstraint('group_name', 'group_parent_id'),
2015 UniqueConstraint('group_name', 'group_parent_id'),
2016 CheckConstraint('group_id != group_parent_id'),
2016 CheckConstraint('group_id != group_parent_id'),
2017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2018 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2018 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2019 )
2019 )
2020 __mapper_args__ = {'order_by': 'group_name'}
2020 __mapper_args__ = {'order_by': 'group_name'}
2021
2021
2022 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2022 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2023
2023
2024 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2024 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2025 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2025 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2026 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2026 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2027 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2027 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2028 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2028 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2029 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2029 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2030 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2030 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2031
2031
2032 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2032 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2033 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2033 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2034 parent_group = relationship('RepoGroup', remote_side=group_id)
2034 parent_group = relationship('RepoGroup', remote_side=group_id)
2035 user = relationship('User')
2035 user = relationship('User')
2036 integrations = relationship('Integration',
2036 integrations = relationship('Integration',
2037 cascade="all, delete, delete-orphan")
2037 cascade="all, delete, delete-orphan")
2038
2038
2039 def __init__(self, group_name='', parent_group=None):
2039 def __init__(self, group_name='', parent_group=None):
2040 self.group_name = group_name
2040 self.group_name = group_name
2041 self.parent_group = parent_group
2041 self.parent_group = parent_group
2042
2042
2043 def __unicode__(self):
2043 def __unicode__(self):
2044 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2044 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2045 self.group_name)
2045 self.group_name)
2046
2046
2047 @classmethod
2047 @classmethod
2048 def _generate_choice(cls, repo_group):
2048 def _generate_choice(cls, repo_group):
2049 from webhelpers.html import literal as _literal
2049 from webhelpers.html import literal as _literal
2050 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2050 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2051 return repo_group.group_id, _name(repo_group.full_path_splitted)
2051 return repo_group.group_id, _name(repo_group.full_path_splitted)
2052
2052
2053 @classmethod
2053 @classmethod
2054 def groups_choices(cls, groups=None, show_empty_group=True):
2054 def groups_choices(cls, groups=None, show_empty_group=True):
2055 if not groups:
2055 if not groups:
2056 groups = cls.query().all()
2056 groups = cls.query().all()
2057
2057
2058 repo_groups = []
2058 repo_groups = []
2059 if show_empty_group:
2059 if show_empty_group:
2060 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2060 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2061
2061
2062 repo_groups.extend([cls._generate_choice(x) for x in groups])
2062 repo_groups.extend([cls._generate_choice(x) for x in groups])
2063
2063
2064 repo_groups = sorted(
2064 repo_groups = sorted(
2065 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2065 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2066 return repo_groups
2066 return repo_groups
2067
2067
2068 @classmethod
2068 @classmethod
2069 def url_sep(cls):
2069 def url_sep(cls):
2070 return URL_SEP
2070 return URL_SEP
2071
2071
2072 @classmethod
2072 @classmethod
2073 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2073 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2074 if case_insensitive:
2074 if case_insensitive:
2075 gr = cls.query().filter(func.lower(cls.group_name)
2075 gr = cls.query().filter(func.lower(cls.group_name)
2076 == func.lower(group_name))
2076 == func.lower(group_name))
2077 else:
2077 else:
2078 gr = cls.query().filter(cls.group_name == group_name)
2078 gr = cls.query().filter(cls.group_name == group_name)
2079 if cache:
2079 if cache:
2080 gr = gr.options(FromCache(
2080 gr = gr.options(FromCache(
2081 "sql_cache_short",
2081 "sql_cache_short",
2082 "get_group_%s" % _hash_key(group_name)))
2082 "get_group_%s" % _hash_key(group_name)))
2083 return gr.scalar()
2083 return gr.scalar()
2084
2084
2085 @classmethod
2085 @classmethod
2086 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2086 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2087 case_insensitive=True):
2087 case_insensitive=True):
2088 q = RepoGroup.query()
2088 q = RepoGroup.query()
2089
2089
2090 if not isinstance(user_id, Optional):
2090 if not isinstance(user_id, Optional):
2091 q = q.filter(RepoGroup.user_id == user_id)
2091 q = q.filter(RepoGroup.user_id == user_id)
2092
2092
2093 if not isinstance(group_id, Optional):
2093 if not isinstance(group_id, Optional):
2094 q = q.filter(RepoGroup.group_parent_id == group_id)
2094 q = q.filter(RepoGroup.group_parent_id == group_id)
2095
2095
2096 if case_insensitive:
2096 if case_insensitive:
2097 q = q.order_by(func.lower(RepoGroup.group_name))
2097 q = q.order_by(func.lower(RepoGroup.group_name))
2098 else:
2098 else:
2099 q = q.order_by(RepoGroup.group_name)
2099 q = q.order_by(RepoGroup.group_name)
2100 return q.all()
2100 return q.all()
2101
2101
2102 @property
2102 @property
2103 def parents(self):
2103 def parents(self):
2104 parents_recursion_limit = 10
2104 parents_recursion_limit = 10
2105 groups = []
2105 groups = []
2106 if self.parent_group is None:
2106 if self.parent_group is None:
2107 return groups
2107 return groups
2108 cur_gr = self.parent_group
2108 cur_gr = self.parent_group
2109 groups.insert(0, cur_gr)
2109 groups.insert(0, cur_gr)
2110 cnt = 0
2110 cnt = 0
2111 while 1:
2111 while 1:
2112 cnt += 1
2112 cnt += 1
2113 gr = getattr(cur_gr, 'parent_group', None)
2113 gr = getattr(cur_gr, 'parent_group', None)
2114 cur_gr = cur_gr.parent_group
2114 cur_gr = cur_gr.parent_group
2115 if gr is None:
2115 if gr is None:
2116 break
2116 break
2117 if cnt == parents_recursion_limit:
2117 if cnt == parents_recursion_limit:
2118 # this will prevent accidental infinit loops
2118 # this will prevent accidental infinit loops
2119 log.error(('more than %s parents found for group %s, stopping '
2119 log.error(('more than %s parents found for group %s, stopping '
2120 'recursive parent fetching' % (parents_recursion_limit, self)))
2120 'recursive parent fetching' % (parents_recursion_limit, self)))
2121 break
2121 break
2122
2122
2123 groups.insert(0, gr)
2123 groups.insert(0, gr)
2124 return groups
2124 return groups
2125
2125
2126 @property
2126 @property
2127 def children(self):
2127 def children(self):
2128 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2128 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2129
2129
2130 @property
2130 @property
2131 def name(self):
2131 def name(self):
2132 return self.group_name.split(RepoGroup.url_sep())[-1]
2132 return self.group_name.split(RepoGroup.url_sep())[-1]
2133
2133
2134 @property
2134 @property
2135 def full_path(self):
2135 def full_path(self):
2136 return self.group_name
2136 return self.group_name
2137
2137
2138 @property
2138 @property
2139 def full_path_splitted(self):
2139 def full_path_splitted(self):
2140 return self.group_name.split(RepoGroup.url_sep())
2140 return self.group_name.split(RepoGroup.url_sep())
2141
2141
2142 @property
2142 @property
2143 def repositories(self):
2143 def repositories(self):
2144 return Repository.query()\
2144 return Repository.query()\
2145 .filter(Repository.group == self)\
2145 .filter(Repository.group == self)\
2146 .order_by(Repository.repo_name)
2146 .order_by(Repository.repo_name)
2147
2147
2148 @property
2148 @property
2149 def repositories_recursive_count(self):
2149 def repositories_recursive_count(self):
2150 cnt = self.repositories.count()
2150 cnt = self.repositories.count()
2151
2151
2152 def children_count(group):
2152 def children_count(group):
2153 cnt = 0
2153 cnt = 0
2154 for child in group.children:
2154 for child in group.children:
2155 cnt += child.repositories.count()
2155 cnt += child.repositories.count()
2156 cnt += children_count(child)
2156 cnt += children_count(child)
2157 return cnt
2157 return cnt
2158
2158
2159 return cnt + children_count(self)
2159 return cnt + children_count(self)
2160
2160
2161 def _recursive_objects(self, include_repos=True):
2161 def _recursive_objects(self, include_repos=True):
2162 all_ = []
2162 all_ = []
2163
2163
2164 def _get_members(root_gr):
2164 def _get_members(root_gr):
2165 if include_repos:
2165 if include_repos:
2166 for r in root_gr.repositories:
2166 for r in root_gr.repositories:
2167 all_.append(r)
2167 all_.append(r)
2168 childs = root_gr.children.all()
2168 childs = root_gr.children.all()
2169 if childs:
2169 if childs:
2170 for gr in childs:
2170 for gr in childs:
2171 all_.append(gr)
2171 all_.append(gr)
2172 _get_members(gr)
2172 _get_members(gr)
2173
2173
2174 _get_members(self)
2174 _get_members(self)
2175 return [self] + all_
2175 return [self] + all_
2176
2176
2177 def recursive_groups_and_repos(self):
2177 def recursive_groups_and_repos(self):
2178 """
2178 """
2179 Recursive return all groups, with repositories in those groups
2179 Recursive return all groups, with repositories in those groups
2180 """
2180 """
2181 return self._recursive_objects()
2181 return self._recursive_objects()
2182
2182
2183 def recursive_groups(self):
2183 def recursive_groups(self):
2184 """
2184 """
2185 Returns all children groups for this group including children of children
2185 Returns all children groups for this group including children of children
2186 """
2186 """
2187 return self._recursive_objects(include_repos=False)
2187 return self._recursive_objects(include_repos=False)
2188
2188
2189 def get_new_name(self, group_name):
2189 def get_new_name(self, group_name):
2190 """
2190 """
2191 returns new full group name based on parent and new name
2191 returns new full group name based on parent and new name
2192
2192
2193 :param group_name:
2193 :param group_name:
2194 """
2194 """
2195 path_prefix = (self.parent_group.full_path_splitted if
2195 path_prefix = (self.parent_group.full_path_splitted if
2196 self.parent_group else [])
2196 self.parent_group else [])
2197 return RepoGroup.url_sep().join(path_prefix + [group_name])
2197 return RepoGroup.url_sep().join(path_prefix + [group_name])
2198
2198
2199 def permissions(self, with_admins=True, with_owner=True):
2199 def permissions(self, with_admins=True, with_owner=True):
2200 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2200 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2201 q = q.options(joinedload(UserRepoGroupToPerm.group),
2201 q = q.options(joinedload(UserRepoGroupToPerm.group),
2202 joinedload(UserRepoGroupToPerm.user),
2202 joinedload(UserRepoGroupToPerm.user),
2203 joinedload(UserRepoGroupToPerm.permission),)
2203 joinedload(UserRepoGroupToPerm.permission),)
2204
2204
2205 # get owners and admins and permissions. We do a trick of re-writing
2205 # get owners and admins and permissions. We do a trick of re-writing
2206 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2206 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2207 # has a global reference and changing one object propagates to all
2207 # has a global reference and changing one object propagates to all
2208 # others. This means if admin is also an owner admin_row that change
2208 # others. This means if admin is also an owner admin_row that change
2209 # would propagate to both objects
2209 # would propagate to both objects
2210 perm_rows = []
2210 perm_rows = []
2211 for _usr in q.all():
2211 for _usr in q.all():
2212 usr = AttributeDict(_usr.user.get_dict())
2212 usr = AttributeDict(_usr.user.get_dict())
2213 usr.permission = _usr.permission.permission_name
2213 usr.permission = _usr.permission.permission_name
2214 perm_rows.append(usr)
2214 perm_rows.append(usr)
2215
2215
2216 # filter the perm rows by 'default' first and then sort them by
2216 # filter the perm rows by 'default' first and then sort them by
2217 # admin,write,read,none permissions sorted again alphabetically in
2217 # admin,write,read,none permissions sorted again alphabetically in
2218 # each group
2218 # each group
2219 perm_rows = sorted(perm_rows, key=display_sort)
2219 perm_rows = sorted(perm_rows, key=display_sort)
2220
2220
2221 _admin_perm = 'group.admin'
2221 _admin_perm = 'group.admin'
2222 owner_row = []
2222 owner_row = []
2223 if with_owner:
2223 if with_owner:
2224 usr = AttributeDict(self.user.get_dict())
2224 usr = AttributeDict(self.user.get_dict())
2225 usr.owner_row = True
2225 usr.owner_row = True
2226 usr.permission = _admin_perm
2226 usr.permission = _admin_perm
2227 owner_row.append(usr)
2227 owner_row.append(usr)
2228
2228
2229 super_admin_rows = []
2229 super_admin_rows = []
2230 if with_admins:
2230 if with_admins:
2231 for usr in User.get_all_super_admins():
2231 for usr in User.get_all_super_admins():
2232 # if this admin is also owner, don't double the record
2232 # if this admin is also owner, don't double the record
2233 if usr.user_id == owner_row[0].user_id:
2233 if usr.user_id == owner_row[0].user_id:
2234 owner_row[0].admin_row = True
2234 owner_row[0].admin_row = True
2235 else:
2235 else:
2236 usr = AttributeDict(usr.get_dict())
2236 usr = AttributeDict(usr.get_dict())
2237 usr.admin_row = True
2237 usr.admin_row = True
2238 usr.permission = _admin_perm
2238 usr.permission = _admin_perm
2239 super_admin_rows.append(usr)
2239 super_admin_rows.append(usr)
2240
2240
2241 return super_admin_rows + owner_row + perm_rows
2241 return super_admin_rows + owner_row + perm_rows
2242
2242
2243 def permission_user_groups(self):
2243 def permission_user_groups(self):
2244 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2244 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2245 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2245 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2246 joinedload(UserGroupRepoGroupToPerm.users_group),
2246 joinedload(UserGroupRepoGroupToPerm.users_group),
2247 joinedload(UserGroupRepoGroupToPerm.permission),)
2247 joinedload(UserGroupRepoGroupToPerm.permission),)
2248
2248
2249 perm_rows = []
2249 perm_rows = []
2250 for _user_group in q.all():
2250 for _user_group in q.all():
2251 usr = AttributeDict(_user_group.users_group.get_dict())
2251 usr = AttributeDict(_user_group.users_group.get_dict())
2252 usr.permission = _user_group.permission.permission_name
2252 usr.permission = _user_group.permission.permission_name
2253 perm_rows.append(usr)
2253 perm_rows.append(usr)
2254
2254
2255 return perm_rows
2255 return perm_rows
2256
2256
2257 def get_api_data(self):
2257 def get_api_data(self):
2258 """
2258 """
2259 Common function for generating api data
2259 Common function for generating api data
2260
2260
2261 """
2261 """
2262 group = self
2262 group = self
2263 data = {
2263 data = {
2264 'group_id': group.group_id,
2264 'group_id': group.group_id,
2265 'group_name': group.group_name,
2265 'group_name': group.group_name,
2266 'group_description': group.group_description,
2266 'group_description': group.group_description,
2267 'parent_group': group.parent_group.group_name if group.parent_group else None,
2267 'parent_group': group.parent_group.group_name if group.parent_group else None,
2268 'repositories': [x.repo_name for x in group.repositories],
2268 'repositories': [x.repo_name for x in group.repositories],
2269 'owner': group.user.username,
2269 'owner': group.user.username,
2270 }
2270 }
2271 return data
2271 return data
2272
2272
2273
2273
2274 class Permission(Base, BaseModel):
2274 class Permission(Base, BaseModel):
2275 __tablename__ = 'permissions'
2275 __tablename__ = 'permissions'
2276 __table_args__ = (
2276 __table_args__ = (
2277 Index('p_perm_name_idx', 'permission_name'),
2277 Index('p_perm_name_idx', 'permission_name'),
2278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2280 )
2280 )
2281 PERMS = [
2281 PERMS = [
2282 ('hg.admin', _('RhodeCode Super Administrator')),
2282 ('hg.admin', _('RhodeCode Super Administrator')),
2283
2283
2284 ('repository.none', _('Repository no access')),
2284 ('repository.none', _('Repository no access')),
2285 ('repository.read', _('Repository read access')),
2285 ('repository.read', _('Repository read access')),
2286 ('repository.write', _('Repository write access')),
2286 ('repository.write', _('Repository write access')),
2287 ('repository.admin', _('Repository admin access')),
2287 ('repository.admin', _('Repository admin access')),
2288
2288
2289 ('group.none', _('Repository group no access')),
2289 ('group.none', _('Repository group no access')),
2290 ('group.read', _('Repository group read access')),
2290 ('group.read', _('Repository group read access')),
2291 ('group.write', _('Repository group write access')),
2291 ('group.write', _('Repository group write access')),
2292 ('group.admin', _('Repository group admin access')),
2292 ('group.admin', _('Repository group admin access')),
2293
2293
2294 ('usergroup.none', _('User group no access')),
2294 ('usergroup.none', _('User group no access')),
2295 ('usergroup.read', _('User group read access')),
2295 ('usergroup.read', _('User group read access')),
2296 ('usergroup.write', _('User group write access')),
2296 ('usergroup.write', _('User group write access')),
2297 ('usergroup.admin', _('User group admin access')),
2297 ('usergroup.admin', _('User group admin access')),
2298
2298
2299 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2299 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2300 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2300 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2301
2301
2302 ('hg.usergroup.create.false', _('User Group creation disabled')),
2302 ('hg.usergroup.create.false', _('User Group creation disabled')),
2303 ('hg.usergroup.create.true', _('User Group creation enabled')),
2303 ('hg.usergroup.create.true', _('User Group creation enabled')),
2304
2304
2305 ('hg.create.none', _('Repository creation disabled')),
2305 ('hg.create.none', _('Repository creation disabled')),
2306 ('hg.create.repository', _('Repository creation enabled')),
2306 ('hg.create.repository', _('Repository creation enabled')),
2307 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2307 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2308 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2308 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2309
2309
2310 ('hg.fork.none', _('Repository forking disabled')),
2310 ('hg.fork.none', _('Repository forking disabled')),
2311 ('hg.fork.repository', _('Repository forking enabled')),
2311 ('hg.fork.repository', _('Repository forking enabled')),
2312
2312
2313 ('hg.register.none', _('Registration disabled')),
2313 ('hg.register.none', _('Registration disabled')),
2314 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2314 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2315 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
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 ('hg.extern_activate.manual', _('Manual activation of external account')),
2321 ('hg.extern_activate.manual', _('Manual activation of external account')),
2318 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2322 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2319
2323
2320 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2324 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2321 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2325 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2322 ]
2326 ]
2323
2327
2324 # definition of system default permissions for DEFAULT user
2328 # definition of system default permissions for DEFAULT user
2325 DEFAULT_USER_PERMISSIONS = [
2329 DEFAULT_USER_PERMISSIONS = [
2326 'repository.read',
2330 'repository.read',
2327 'group.read',
2331 'group.read',
2328 'usergroup.read',
2332 'usergroup.read',
2329 'hg.create.repository',
2333 'hg.create.repository',
2330 'hg.repogroup.create.false',
2334 'hg.repogroup.create.false',
2331 'hg.usergroup.create.false',
2335 'hg.usergroup.create.false',
2332 'hg.create.write_on_repogroup.true',
2336 'hg.create.write_on_repogroup.true',
2333 'hg.fork.repository',
2337 'hg.fork.repository',
2334 'hg.register.manual_activate',
2338 'hg.register.manual_activate',
2339 'hg.password_reset.enabled',
2335 'hg.extern_activate.auto',
2340 'hg.extern_activate.auto',
2336 'hg.inherit_default_perms.true',
2341 'hg.inherit_default_perms.true',
2337 ]
2342 ]
2338
2343
2339 # defines which permissions are more important higher the more important
2344 # defines which permissions are more important higher the more important
2340 # Weight defines which permissions are more important.
2345 # Weight defines which permissions are more important.
2341 # The higher number the more important.
2346 # The higher number the more important.
2342 PERM_WEIGHTS = {
2347 PERM_WEIGHTS = {
2343 'repository.none': 0,
2348 'repository.none': 0,
2344 'repository.read': 1,
2349 'repository.read': 1,
2345 'repository.write': 3,
2350 'repository.write': 3,
2346 'repository.admin': 4,
2351 'repository.admin': 4,
2347
2352
2348 'group.none': 0,
2353 'group.none': 0,
2349 'group.read': 1,
2354 'group.read': 1,
2350 'group.write': 3,
2355 'group.write': 3,
2351 'group.admin': 4,
2356 'group.admin': 4,
2352
2357
2353 'usergroup.none': 0,
2358 'usergroup.none': 0,
2354 'usergroup.read': 1,
2359 'usergroup.read': 1,
2355 'usergroup.write': 3,
2360 'usergroup.write': 3,
2356 'usergroup.admin': 4,
2361 'usergroup.admin': 4,
2357
2362
2358 'hg.repogroup.create.false': 0,
2363 'hg.repogroup.create.false': 0,
2359 'hg.repogroup.create.true': 1,
2364 'hg.repogroup.create.true': 1,
2360
2365
2361 'hg.usergroup.create.false': 0,
2366 'hg.usergroup.create.false': 0,
2362 'hg.usergroup.create.true': 1,
2367 'hg.usergroup.create.true': 1,
2363
2368
2364 'hg.fork.none': 0,
2369 'hg.fork.none': 0,
2365 'hg.fork.repository': 1,
2370 'hg.fork.repository': 1,
2366 'hg.create.none': 0,
2371 'hg.create.none': 0,
2367 'hg.create.repository': 1
2372 'hg.create.repository': 1
2368 }
2373 }
2369
2374
2370 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2375 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2371 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2376 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2372 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2377 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2373
2378
2374 def __unicode__(self):
2379 def __unicode__(self):
2375 return u"<%s('%s:%s')>" % (
2380 return u"<%s('%s:%s')>" % (
2376 self.__class__.__name__, self.permission_id, self.permission_name
2381 self.__class__.__name__, self.permission_id, self.permission_name
2377 )
2382 )
2378
2383
2379 @classmethod
2384 @classmethod
2380 def get_by_key(cls, key):
2385 def get_by_key(cls, key):
2381 return cls.query().filter(cls.permission_name == key).scalar()
2386 return cls.query().filter(cls.permission_name == key).scalar()
2382
2387
2383 @classmethod
2388 @classmethod
2384 def get_default_repo_perms(cls, user_id, repo_id=None):
2389 def get_default_repo_perms(cls, user_id, repo_id=None):
2385 q = Session().query(UserRepoToPerm, Repository, Permission)\
2390 q = Session().query(UserRepoToPerm, Repository, Permission)\
2386 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2391 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2387 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2392 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2388 .filter(UserRepoToPerm.user_id == user_id)
2393 .filter(UserRepoToPerm.user_id == user_id)
2389 if repo_id:
2394 if repo_id:
2390 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2395 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2391 return q.all()
2396 return q.all()
2392
2397
2393 @classmethod
2398 @classmethod
2394 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2399 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2395 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2400 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2396 .join(
2401 .join(
2397 Permission,
2402 Permission,
2398 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2403 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2399 .join(
2404 .join(
2400 Repository,
2405 Repository,
2401 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2406 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2402 .join(
2407 .join(
2403 UserGroup,
2408 UserGroup,
2404 UserGroupRepoToPerm.users_group_id ==
2409 UserGroupRepoToPerm.users_group_id ==
2405 UserGroup.users_group_id)\
2410 UserGroup.users_group_id)\
2406 .join(
2411 .join(
2407 UserGroupMember,
2412 UserGroupMember,
2408 UserGroupRepoToPerm.users_group_id ==
2413 UserGroupRepoToPerm.users_group_id ==
2409 UserGroupMember.users_group_id)\
2414 UserGroupMember.users_group_id)\
2410 .filter(
2415 .filter(
2411 UserGroupMember.user_id == user_id,
2416 UserGroupMember.user_id == user_id,
2412 UserGroup.users_group_active == true())
2417 UserGroup.users_group_active == true())
2413 if repo_id:
2418 if repo_id:
2414 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2419 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2415 return q.all()
2420 return q.all()
2416
2421
2417 @classmethod
2422 @classmethod
2418 def get_default_group_perms(cls, user_id, repo_group_id=None):
2423 def get_default_group_perms(cls, user_id, repo_group_id=None):
2419 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2424 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2420 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2425 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2421 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2426 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2422 .filter(UserRepoGroupToPerm.user_id == user_id)
2427 .filter(UserRepoGroupToPerm.user_id == user_id)
2423 if repo_group_id:
2428 if repo_group_id:
2424 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2429 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2425 return q.all()
2430 return q.all()
2426
2431
2427 @classmethod
2432 @classmethod
2428 def get_default_group_perms_from_user_group(
2433 def get_default_group_perms_from_user_group(
2429 cls, user_id, repo_group_id=None):
2434 cls, user_id, repo_group_id=None):
2430 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2435 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2431 .join(
2436 .join(
2432 Permission,
2437 Permission,
2433 UserGroupRepoGroupToPerm.permission_id ==
2438 UserGroupRepoGroupToPerm.permission_id ==
2434 Permission.permission_id)\
2439 Permission.permission_id)\
2435 .join(
2440 .join(
2436 RepoGroup,
2441 RepoGroup,
2437 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2442 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2438 .join(
2443 .join(
2439 UserGroup,
2444 UserGroup,
2440 UserGroupRepoGroupToPerm.users_group_id ==
2445 UserGroupRepoGroupToPerm.users_group_id ==
2441 UserGroup.users_group_id)\
2446 UserGroup.users_group_id)\
2442 .join(
2447 .join(
2443 UserGroupMember,
2448 UserGroupMember,
2444 UserGroupRepoGroupToPerm.users_group_id ==
2449 UserGroupRepoGroupToPerm.users_group_id ==
2445 UserGroupMember.users_group_id)\
2450 UserGroupMember.users_group_id)\
2446 .filter(
2451 .filter(
2447 UserGroupMember.user_id == user_id,
2452 UserGroupMember.user_id == user_id,
2448 UserGroup.users_group_active == true())
2453 UserGroup.users_group_active == true())
2449 if repo_group_id:
2454 if repo_group_id:
2450 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2455 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2451 return q.all()
2456 return q.all()
2452
2457
2453 @classmethod
2458 @classmethod
2454 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2459 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2455 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2460 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2456 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2461 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2457 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2462 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2458 .filter(UserUserGroupToPerm.user_id == user_id)
2463 .filter(UserUserGroupToPerm.user_id == user_id)
2459 if user_group_id:
2464 if user_group_id:
2460 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2465 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2461 return q.all()
2466 return q.all()
2462
2467
2463 @classmethod
2468 @classmethod
2464 def get_default_user_group_perms_from_user_group(
2469 def get_default_user_group_perms_from_user_group(
2465 cls, user_id, user_group_id=None):
2470 cls, user_id, user_group_id=None):
2466 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2471 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2467 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2472 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2468 .join(
2473 .join(
2469 Permission,
2474 Permission,
2470 UserGroupUserGroupToPerm.permission_id ==
2475 UserGroupUserGroupToPerm.permission_id ==
2471 Permission.permission_id)\
2476 Permission.permission_id)\
2472 .join(
2477 .join(
2473 TargetUserGroup,
2478 TargetUserGroup,
2474 UserGroupUserGroupToPerm.target_user_group_id ==
2479 UserGroupUserGroupToPerm.target_user_group_id ==
2475 TargetUserGroup.users_group_id)\
2480 TargetUserGroup.users_group_id)\
2476 .join(
2481 .join(
2477 UserGroup,
2482 UserGroup,
2478 UserGroupUserGroupToPerm.user_group_id ==
2483 UserGroupUserGroupToPerm.user_group_id ==
2479 UserGroup.users_group_id)\
2484 UserGroup.users_group_id)\
2480 .join(
2485 .join(
2481 UserGroupMember,
2486 UserGroupMember,
2482 UserGroupUserGroupToPerm.user_group_id ==
2487 UserGroupUserGroupToPerm.user_group_id ==
2483 UserGroupMember.users_group_id)\
2488 UserGroupMember.users_group_id)\
2484 .filter(
2489 .filter(
2485 UserGroupMember.user_id == user_id,
2490 UserGroupMember.user_id == user_id,
2486 UserGroup.users_group_active == true())
2491 UserGroup.users_group_active == true())
2487 if user_group_id:
2492 if user_group_id:
2488 q = q.filter(
2493 q = q.filter(
2489 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2494 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2490
2495
2491 return q.all()
2496 return q.all()
2492
2497
2493
2498
2494 class UserRepoToPerm(Base, BaseModel):
2499 class UserRepoToPerm(Base, BaseModel):
2495 __tablename__ = 'repo_to_perm'
2500 __tablename__ = 'repo_to_perm'
2496 __table_args__ = (
2501 __table_args__ = (
2497 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2502 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2500 )
2505 )
2501 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2506 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2502 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2503 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2504 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2509 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2505
2510
2506 user = relationship('User')
2511 user = relationship('User')
2507 repository = relationship('Repository')
2512 repository = relationship('Repository')
2508 permission = relationship('Permission')
2513 permission = relationship('Permission')
2509
2514
2510 @classmethod
2515 @classmethod
2511 def create(cls, user, repository, permission):
2516 def create(cls, user, repository, permission):
2512 n = cls()
2517 n = cls()
2513 n.user = user
2518 n.user = user
2514 n.repository = repository
2519 n.repository = repository
2515 n.permission = permission
2520 n.permission = permission
2516 Session().add(n)
2521 Session().add(n)
2517 return n
2522 return n
2518
2523
2519 def __unicode__(self):
2524 def __unicode__(self):
2520 return u'<%s => %s >' % (self.user, self.repository)
2525 return u'<%s => %s >' % (self.user, self.repository)
2521
2526
2522
2527
2523 class UserUserGroupToPerm(Base, BaseModel):
2528 class UserUserGroupToPerm(Base, BaseModel):
2524 __tablename__ = 'user_user_group_to_perm'
2529 __tablename__ = 'user_user_group_to_perm'
2525 __table_args__ = (
2530 __table_args__ = (
2526 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2531 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2528 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2529 )
2534 )
2530 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
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 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2532 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2533 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2534
2539
2535 user = relationship('User')
2540 user = relationship('User')
2536 user_group = relationship('UserGroup')
2541 user_group = relationship('UserGroup')
2537 permission = relationship('Permission')
2542 permission = relationship('Permission')
2538
2543
2539 @classmethod
2544 @classmethod
2540 def create(cls, user, user_group, permission):
2545 def create(cls, user, user_group, permission):
2541 n = cls()
2546 n = cls()
2542 n.user = user
2547 n.user = user
2543 n.user_group = user_group
2548 n.user_group = user_group
2544 n.permission = permission
2549 n.permission = permission
2545 Session().add(n)
2550 Session().add(n)
2546 return n
2551 return n
2547
2552
2548 def __unicode__(self):
2553 def __unicode__(self):
2549 return u'<%s => %s >' % (self.user, self.user_group)
2554 return u'<%s => %s >' % (self.user, self.user_group)
2550
2555
2551
2556
2552 class UserToPerm(Base, BaseModel):
2557 class UserToPerm(Base, BaseModel):
2553 __tablename__ = 'user_to_perm'
2558 __tablename__ = 'user_to_perm'
2554 __table_args__ = (
2559 __table_args__ = (
2555 UniqueConstraint('user_id', 'permission_id'),
2560 UniqueConstraint('user_id', 'permission_id'),
2556 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2557 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2562 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2558 )
2563 )
2559 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2564 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2560 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2561 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2562
2567
2563 user = relationship('User')
2568 user = relationship('User')
2564 permission = relationship('Permission', lazy='joined')
2569 permission = relationship('Permission', lazy='joined')
2565
2570
2566 def __unicode__(self):
2571 def __unicode__(self):
2567 return u'<%s => %s >' % (self.user, self.permission)
2572 return u'<%s => %s >' % (self.user, self.permission)
2568
2573
2569
2574
2570 class UserGroupRepoToPerm(Base, BaseModel):
2575 class UserGroupRepoToPerm(Base, BaseModel):
2571 __tablename__ = 'users_group_repo_to_perm'
2576 __tablename__ = 'users_group_repo_to_perm'
2572 __table_args__ = (
2577 __table_args__ = (
2573 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2578 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2574 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2579 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2575 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2580 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2576 )
2581 )
2577 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2582 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2578 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2579 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2580 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2585 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2581
2586
2582 users_group = relationship('UserGroup')
2587 users_group = relationship('UserGroup')
2583 permission = relationship('Permission')
2588 permission = relationship('Permission')
2584 repository = relationship('Repository')
2589 repository = relationship('Repository')
2585
2590
2586 @classmethod
2591 @classmethod
2587 def create(cls, users_group, repository, permission):
2592 def create(cls, users_group, repository, permission):
2588 n = cls()
2593 n = cls()
2589 n.users_group = users_group
2594 n.users_group = users_group
2590 n.repository = repository
2595 n.repository = repository
2591 n.permission = permission
2596 n.permission = permission
2592 Session().add(n)
2597 Session().add(n)
2593 return n
2598 return n
2594
2599
2595 def __unicode__(self):
2600 def __unicode__(self):
2596 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2601 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2597
2602
2598
2603
2599 class UserGroupUserGroupToPerm(Base, BaseModel):
2604 class UserGroupUserGroupToPerm(Base, BaseModel):
2600 __tablename__ = 'user_group_user_group_to_perm'
2605 __tablename__ = 'user_group_user_group_to_perm'
2601 __table_args__ = (
2606 __table_args__ = (
2602 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2607 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2603 CheckConstraint('target_user_group_id != user_group_id'),
2608 CheckConstraint('target_user_group_id != user_group_id'),
2604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2605 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2610 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2606 )
2611 )
2607 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)
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 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2613 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2609 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2614 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2610 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2615 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2611
2616
2612 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2617 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2613 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2618 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2614 permission = relationship('Permission')
2619 permission = relationship('Permission')
2615
2620
2616 @classmethod
2621 @classmethod
2617 def create(cls, target_user_group, user_group, permission):
2622 def create(cls, target_user_group, user_group, permission):
2618 n = cls()
2623 n = cls()
2619 n.target_user_group = target_user_group
2624 n.target_user_group = target_user_group
2620 n.user_group = user_group
2625 n.user_group = user_group
2621 n.permission = permission
2626 n.permission = permission
2622 Session().add(n)
2627 Session().add(n)
2623 return n
2628 return n
2624
2629
2625 def __unicode__(self):
2630 def __unicode__(self):
2626 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2631 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2627
2632
2628
2633
2629 class UserGroupToPerm(Base, BaseModel):
2634 class UserGroupToPerm(Base, BaseModel):
2630 __tablename__ = 'users_group_to_perm'
2635 __tablename__ = 'users_group_to_perm'
2631 __table_args__ = (
2636 __table_args__ = (
2632 UniqueConstraint('users_group_id', 'permission_id',),
2637 UniqueConstraint('users_group_id', 'permission_id',),
2633 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2638 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2634 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2639 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2635 )
2640 )
2636 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2641 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2637 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2642 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2638 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2643 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2639
2644
2640 users_group = relationship('UserGroup')
2645 users_group = relationship('UserGroup')
2641 permission = relationship('Permission')
2646 permission = relationship('Permission')
2642
2647
2643
2648
2644 class UserRepoGroupToPerm(Base, BaseModel):
2649 class UserRepoGroupToPerm(Base, BaseModel):
2645 __tablename__ = 'user_repo_group_to_perm'
2650 __tablename__ = 'user_repo_group_to_perm'
2646 __table_args__ = (
2651 __table_args__ = (
2647 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2652 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2654 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2650 )
2655 )
2651
2656
2652 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2657 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2653 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2658 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2654 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2659 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2655 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2656
2661
2657 user = relationship('User')
2662 user = relationship('User')
2658 group = relationship('RepoGroup')
2663 group = relationship('RepoGroup')
2659 permission = relationship('Permission')
2664 permission = relationship('Permission')
2660
2665
2661 @classmethod
2666 @classmethod
2662 def create(cls, user, repository_group, permission):
2667 def create(cls, user, repository_group, permission):
2663 n = cls()
2668 n = cls()
2664 n.user = user
2669 n.user = user
2665 n.group = repository_group
2670 n.group = repository_group
2666 n.permission = permission
2671 n.permission = permission
2667 Session().add(n)
2672 Session().add(n)
2668 return n
2673 return n
2669
2674
2670
2675
2671 class UserGroupRepoGroupToPerm(Base, BaseModel):
2676 class UserGroupRepoGroupToPerm(Base, BaseModel):
2672 __tablename__ = 'users_group_repo_group_to_perm'
2677 __tablename__ = 'users_group_repo_group_to_perm'
2673 __table_args__ = (
2678 __table_args__ = (
2674 UniqueConstraint('users_group_id', 'group_id'),
2679 UniqueConstraint('users_group_id', 'group_id'),
2675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2676 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2681 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2677 )
2682 )
2678
2683
2679 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)
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 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2681 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2683
2688
2684 users_group = relationship('UserGroup')
2689 users_group = relationship('UserGroup')
2685 permission = relationship('Permission')
2690 permission = relationship('Permission')
2686 group = relationship('RepoGroup')
2691 group = relationship('RepoGroup')
2687
2692
2688 @classmethod
2693 @classmethod
2689 def create(cls, user_group, repository_group, permission):
2694 def create(cls, user_group, repository_group, permission):
2690 n = cls()
2695 n = cls()
2691 n.users_group = user_group
2696 n.users_group = user_group
2692 n.group = repository_group
2697 n.group = repository_group
2693 n.permission = permission
2698 n.permission = permission
2694 Session().add(n)
2699 Session().add(n)
2695 return n
2700 return n
2696
2701
2697 def __unicode__(self):
2702 def __unicode__(self):
2698 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2703 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2699
2704
2700
2705
2701 class Statistics(Base, BaseModel):
2706 class Statistics(Base, BaseModel):
2702 __tablename__ = 'statistics'
2707 __tablename__ = 'statistics'
2703 __table_args__ = (
2708 __table_args__ = (
2704 UniqueConstraint('repository_id'),
2709 UniqueConstraint('repository_id'),
2705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2706 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2707 )
2712 )
2708 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2709 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2710 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2711 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2712 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2713 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2714
2719
2715 repository = relationship('Repository', single_parent=True)
2720 repository = relationship('Repository', single_parent=True)
2716
2721
2717
2722
2718 class UserFollowing(Base, BaseModel):
2723 class UserFollowing(Base, BaseModel):
2719 __tablename__ = 'user_followings'
2724 __tablename__ = 'user_followings'
2720 __table_args__ = (
2725 __table_args__ = (
2721 UniqueConstraint('user_id', 'follows_repository_id'),
2726 UniqueConstraint('user_id', 'follows_repository_id'),
2722 UniqueConstraint('user_id', 'follows_user_id'),
2727 UniqueConstraint('user_id', 'follows_user_id'),
2723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2725 )
2730 )
2726
2731
2727 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2728 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2729 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2730 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2731 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2732
2737
2733 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2734
2739
2735 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2736 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2737
2742
2738 @classmethod
2743 @classmethod
2739 def get_repo_followers(cls, repo_id):
2744 def get_repo_followers(cls, repo_id):
2740 return cls.query().filter(cls.follows_repo_id == repo_id)
2745 return cls.query().filter(cls.follows_repo_id == repo_id)
2741
2746
2742
2747
2743 class CacheKey(Base, BaseModel):
2748 class CacheKey(Base, BaseModel):
2744 __tablename__ = 'cache_invalidation'
2749 __tablename__ = 'cache_invalidation'
2745 __table_args__ = (
2750 __table_args__ = (
2746 UniqueConstraint('cache_key'),
2751 UniqueConstraint('cache_key'),
2747 Index('key_idx', 'cache_key'),
2752 Index('key_idx', 'cache_key'),
2748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2750 )
2755 )
2751 CACHE_TYPE_ATOM = 'ATOM'
2756 CACHE_TYPE_ATOM = 'ATOM'
2752 CACHE_TYPE_RSS = 'RSS'
2757 CACHE_TYPE_RSS = 'RSS'
2753 CACHE_TYPE_README = 'README'
2758 CACHE_TYPE_README = 'README'
2754
2759
2755 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2756 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2761 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2757 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2762 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2758 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2763 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2759
2764
2760 def __init__(self, cache_key, cache_args=''):
2765 def __init__(self, cache_key, cache_args=''):
2761 self.cache_key = cache_key
2766 self.cache_key = cache_key
2762 self.cache_args = cache_args
2767 self.cache_args = cache_args
2763 self.cache_active = False
2768 self.cache_active = False
2764
2769
2765 def __unicode__(self):
2770 def __unicode__(self):
2766 return u"<%s('%s:%s[%s]')>" % (
2771 return u"<%s('%s:%s[%s]')>" % (
2767 self.__class__.__name__,
2772 self.__class__.__name__,
2768 self.cache_id, self.cache_key, self.cache_active)
2773 self.cache_id, self.cache_key, self.cache_active)
2769
2774
2770 def _cache_key_partition(self):
2775 def _cache_key_partition(self):
2771 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2772 return prefix, repo_name, suffix
2777 return prefix, repo_name, suffix
2773
2778
2774 def get_prefix(self):
2779 def get_prefix(self):
2775 """
2780 """
2776 Try to extract prefix from existing cache key. The key could consist
2781 Try to extract prefix from existing cache key. The key could consist
2777 of prefix, repo_name, suffix
2782 of prefix, repo_name, suffix
2778 """
2783 """
2779 # this returns prefix, repo_name, suffix
2784 # this returns prefix, repo_name, suffix
2780 return self._cache_key_partition()[0]
2785 return self._cache_key_partition()[0]
2781
2786
2782 def get_suffix(self):
2787 def get_suffix(self):
2783 """
2788 """
2784 get suffix that might have been used in _get_cache_key to
2789 get suffix that might have been used in _get_cache_key to
2785 generate self.cache_key. Only used for informational purposes
2790 generate self.cache_key. Only used for informational purposes
2786 in repo_edit.html.
2791 in repo_edit.html.
2787 """
2792 """
2788 # prefix, repo_name, suffix
2793 # prefix, repo_name, suffix
2789 return self._cache_key_partition()[2]
2794 return self._cache_key_partition()[2]
2790
2795
2791 @classmethod
2796 @classmethod
2792 def delete_all_cache(cls):
2797 def delete_all_cache(cls):
2793 """
2798 """
2794 Delete all cache keys from database.
2799 Delete all cache keys from database.
2795 Should only be run when all instances are down and all entries
2800 Should only be run when all instances are down and all entries
2796 thus stale.
2801 thus stale.
2797 """
2802 """
2798 cls.query().delete()
2803 cls.query().delete()
2799 Session().commit()
2804 Session().commit()
2800
2805
2801 @classmethod
2806 @classmethod
2802 def get_cache_key(cls, repo_name, cache_type):
2807 def get_cache_key(cls, repo_name, cache_type):
2803 """
2808 """
2804
2809
2805 Generate a cache key for this process of RhodeCode instance.
2810 Generate a cache key for this process of RhodeCode instance.
2806 Prefix most likely will be process id or maybe explicitly set
2811 Prefix most likely will be process id or maybe explicitly set
2807 instance_id from .ini file.
2812 instance_id from .ini file.
2808 """
2813 """
2809 import rhodecode
2814 import rhodecode
2810 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2815 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2811
2816
2812 repo_as_unicode = safe_unicode(repo_name)
2817 repo_as_unicode = safe_unicode(repo_name)
2813 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2818 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2814 if cache_type else repo_as_unicode
2819 if cache_type else repo_as_unicode
2815
2820
2816 return u'{}{}'.format(prefix, key)
2821 return u'{}{}'.format(prefix, key)
2817
2822
2818 @classmethod
2823 @classmethod
2819 def set_invalidate(cls, repo_name, delete=False):
2824 def set_invalidate(cls, repo_name, delete=False):
2820 """
2825 """
2821 Mark all caches of a repo as invalid in the database.
2826 Mark all caches of a repo as invalid in the database.
2822 """
2827 """
2823
2828
2824 try:
2829 try:
2825 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2830 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2826 if delete:
2831 if delete:
2827 log.debug('cache objects deleted for repo %s',
2832 log.debug('cache objects deleted for repo %s',
2828 safe_str(repo_name))
2833 safe_str(repo_name))
2829 qry.delete()
2834 qry.delete()
2830 else:
2835 else:
2831 log.debug('cache objects marked as invalid for repo %s',
2836 log.debug('cache objects marked as invalid for repo %s',
2832 safe_str(repo_name))
2837 safe_str(repo_name))
2833 qry.update({"cache_active": False})
2838 qry.update({"cache_active": False})
2834
2839
2835 Session().commit()
2840 Session().commit()
2836 except Exception:
2841 except Exception:
2837 log.exception(
2842 log.exception(
2838 'Cache key invalidation failed for repository %s',
2843 'Cache key invalidation failed for repository %s',
2839 safe_str(repo_name))
2844 safe_str(repo_name))
2840 Session().rollback()
2845 Session().rollback()
2841
2846
2842 @classmethod
2847 @classmethod
2843 def get_active_cache(cls, cache_key):
2848 def get_active_cache(cls, cache_key):
2844 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2845 if inv_obj:
2850 if inv_obj:
2846 return inv_obj
2851 return inv_obj
2847 return None
2852 return None
2848
2853
2849 @classmethod
2854 @classmethod
2850 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2855 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2851 thread_scoped=False):
2856 thread_scoped=False):
2852 """
2857 """
2853 @cache_region('long_term')
2858 @cache_region('long_term')
2854 def _heavy_calculation(cache_key):
2859 def _heavy_calculation(cache_key):
2855 return 'result'
2860 return 'result'
2856
2861
2857 cache_context = CacheKey.repo_context_cache(
2862 cache_context = CacheKey.repo_context_cache(
2858 _heavy_calculation, repo_name, cache_type)
2863 _heavy_calculation, repo_name, cache_type)
2859
2864
2860 with cache_context as context:
2865 with cache_context as context:
2861 context.invalidate()
2866 context.invalidate()
2862 computed = context.compute()
2867 computed = context.compute()
2863
2868
2864 assert computed == 'result'
2869 assert computed == 'result'
2865 """
2870 """
2866 from rhodecode.lib import caches
2871 from rhodecode.lib import caches
2867 return caches.InvalidationContext(
2872 return caches.InvalidationContext(
2868 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2873 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2869
2874
2870
2875
2871 class ChangesetComment(Base, BaseModel):
2876 class ChangesetComment(Base, BaseModel):
2872 __tablename__ = 'changeset_comments'
2877 __tablename__ = 'changeset_comments'
2873 __table_args__ = (
2878 __table_args__ = (
2874 Index('cc_revision_idx', 'revision'),
2879 Index('cc_revision_idx', 'revision'),
2875 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2876 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2877 )
2882 )
2878
2883
2879 COMMENT_OUTDATED = u'comment_outdated'
2884 COMMENT_OUTDATED = u'comment_outdated'
2880
2885
2881 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2882 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2883 revision = Column('revision', String(40), nullable=True)
2888 revision = Column('revision', String(40), nullable=True)
2884 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2885 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2886 line_no = Column('line_no', Unicode(10), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2887 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2888 f_path = Column('f_path', Unicode(1000), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2889 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2890 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2891 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2892 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2893 renderer = Column('renderer', Unicode(64), nullable=True)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2894 display_state = Column('display_state', Unicode(128), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2895
2900
2896 author = relationship('User', lazy='joined')
2901 author = relationship('User', lazy='joined')
2897 repo = relationship('Repository')
2902 repo = relationship('Repository')
2898 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2899 pull_request = relationship('PullRequest', lazy='joined')
2904 pull_request = relationship('PullRequest', lazy='joined')
2900 pull_request_version = relationship('PullRequestVersion')
2905 pull_request_version = relationship('PullRequestVersion')
2901
2906
2902 @classmethod
2907 @classmethod
2903 def get_users(cls, revision=None, pull_request_id=None):
2908 def get_users(cls, revision=None, pull_request_id=None):
2904 """
2909 """
2905 Returns user associated with this ChangesetComment. ie those
2910 Returns user associated with this ChangesetComment. ie those
2906 who actually commented
2911 who actually commented
2907
2912
2908 :param cls:
2913 :param cls:
2909 :param revision:
2914 :param revision:
2910 """
2915 """
2911 q = Session().query(User)\
2916 q = Session().query(User)\
2912 .join(ChangesetComment.author)
2917 .join(ChangesetComment.author)
2913 if revision:
2918 if revision:
2914 q = q.filter(cls.revision == revision)
2919 q = q.filter(cls.revision == revision)
2915 elif pull_request_id:
2920 elif pull_request_id:
2916 q = q.filter(cls.pull_request_id == pull_request_id)
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2917 return q.all()
2922 return q.all()
2918
2923
2919 def render(self, mentions=False):
2924 def render(self, mentions=False):
2920 from rhodecode.lib import helpers as h
2925 from rhodecode.lib import helpers as h
2921 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2922
2927
2923 def __repr__(self):
2928 def __repr__(self):
2924 if self.comment_id:
2929 if self.comment_id:
2925 return '<DB:ChangesetComment #%s>' % self.comment_id
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2926 else:
2931 else:
2927 return '<DB:ChangesetComment at %#x>' % id(self)
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2928
2933
2929
2934
2930 class ChangesetStatus(Base, BaseModel):
2935 class ChangesetStatus(Base, BaseModel):
2931 __tablename__ = 'changeset_statuses'
2936 __tablename__ = 'changeset_statuses'
2932 __table_args__ = (
2937 __table_args__ = (
2933 Index('cs_revision_idx', 'revision'),
2938 Index('cs_revision_idx', 'revision'),
2934 Index('cs_version_idx', 'version'),
2939 Index('cs_version_idx', 'version'),
2935 UniqueConstraint('repo_id', 'revision', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2937 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2938 )
2943 )
2939 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2940 STATUS_APPROVED = 'approved'
2945 STATUS_APPROVED = 'approved'
2941 STATUS_REJECTED = 'rejected'
2946 STATUS_REJECTED = 'rejected'
2942 STATUS_UNDER_REVIEW = 'under_review'
2947 STATUS_UNDER_REVIEW = 'under_review'
2943
2948
2944 STATUSES = [
2949 STATUSES = [
2945 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2946 (STATUS_APPROVED, _("Approved")),
2951 (STATUS_APPROVED, _("Approved")),
2947 (STATUS_REJECTED, _("Rejected")),
2952 (STATUS_REJECTED, _("Rejected")),
2948 (STATUS_UNDER_REVIEW, _("Under Review")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2949 ]
2954 ]
2950
2955
2951 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2952 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2953 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2954 revision = Column('revision', String(40), nullable=False)
2959 revision = Column('revision', String(40), nullable=False)
2955 status = Column('status', String(128), nullable=False, default=DEFAULT)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2956 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2957 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2958 version = Column('version', Integer(), nullable=False, default=0)
2963 version = Column('version', Integer(), nullable=False, default=0)
2959 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2960
2965
2961 author = relationship('User', lazy='joined')
2966 author = relationship('User', lazy='joined')
2962 repo = relationship('Repository')
2967 repo = relationship('Repository')
2963 comment = relationship('ChangesetComment', lazy='joined')
2968 comment = relationship('ChangesetComment', lazy='joined')
2964 pull_request = relationship('PullRequest', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2965
2970
2966 def __unicode__(self):
2971 def __unicode__(self):
2967 return u"<%s('%s[%s]:%s')>" % (
2972 return u"<%s('%s[%s]:%s')>" % (
2968 self.__class__.__name__,
2973 self.__class__.__name__,
2969 self.status, self.version, self.author
2974 self.status, self.version, self.author
2970 )
2975 )
2971
2976
2972 @classmethod
2977 @classmethod
2973 def get_status_lbl(cls, value):
2978 def get_status_lbl(cls, value):
2974 return dict(cls.STATUSES).get(value)
2979 return dict(cls.STATUSES).get(value)
2975
2980
2976 @property
2981 @property
2977 def status_lbl(self):
2982 def status_lbl(self):
2978 return ChangesetStatus.get_status_lbl(self.status)
2983 return ChangesetStatus.get_status_lbl(self.status)
2979
2984
2980
2985
2981 class _PullRequestBase(BaseModel):
2986 class _PullRequestBase(BaseModel):
2982 """
2987 """
2983 Common attributes of pull request and version entries.
2988 Common attributes of pull request and version entries.
2984 """
2989 """
2985
2990
2986 # .status values
2991 # .status values
2987 STATUS_NEW = u'new'
2992 STATUS_NEW = u'new'
2988 STATUS_OPEN = u'open'
2993 STATUS_OPEN = u'open'
2989 STATUS_CLOSED = u'closed'
2994 STATUS_CLOSED = u'closed'
2990
2995
2991 title = Column('title', Unicode(255), nullable=True)
2996 title = Column('title', Unicode(255), nullable=True)
2992 description = Column(
2997 description = Column(
2993 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2994 nullable=True)
2999 nullable=True)
2995 # new/open/closed status of pull request (not approve/reject/etc)
3000 # new/open/closed status of pull request (not approve/reject/etc)
2996 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2997 created_on = Column(
3002 created_on = Column(
2998 'created_on', DateTime(timezone=False), nullable=False,
3003 'created_on', DateTime(timezone=False), nullable=False,
2999 default=datetime.datetime.now)
3004 default=datetime.datetime.now)
3000 updated_on = Column(
3005 updated_on = Column(
3001 'updated_on', DateTime(timezone=False), nullable=False,
3006 'updated_on', DateTime(timezone=False), nullable=False,
3002 default=datetime.datetime.now)
3007 default=datetime.datetime.now)
3003
3008
3004 @declared_attr
3009 @declared_attr
3005 def user_id(cls):
3010 def user_id(cls):
3006 return Column(
3011 return Column(
3007 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3008 unique=None)
3013 unique=None)
3009
3014
3010 # 500 revisions max
3015 # 500 revisions max
3011 _revisions = Column(
3016 _revisions = Column(
3012 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3013
3018
3014 @declared_attr
3019 @declared_attr
3015 def source_repo_id(cls):
3020 def source_repo_id(cls):
3016 # TODO: dan: rename column to source_repo_id
3021 # TODO: dan: rename column to source_repo_id
3017 return Column(
3022 return Column(
3018 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3019 nullable=False)
3024 nullable=False)
3020
3025
3021 source_ref = Column('org_ref', Unicode(255), nullable=False)
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3022
3027
3023 @declared_attr
3028 @declared_attr
3024 def target_repo_id(cls):
3029 def target_repo_id(cls):
3025 # TODO: dan: rename column to target_repo_id
3030 # TODO: dan: rename column to target_repo_id
3026 return Column(
3031 return Column(
3027 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3028 nullable=False)
3033 nullable=False)
3029
3034
3030 target_ref = Column('other_ref', Unicode(255), nullable=False)
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3031
3036
3032 # TODO: dan: rename column to last_merge_source_rev
3037 # TODO: dan: rename column to last_merge_source_rev
3033 _last_merge_source_rev = Column(
3038 _last_merge_source_rev = Column(
3034 'last_merge_org_rev', String(40), nullable=True)
3039 'last_merge_org_rev', String(40), nullable=True)
3035 # TODO: dan: rename column to last_merge_target_rev
3040 # TODO: dan: rename column to last_merge_target_rev
3036 _last_merge_target_rev = Column(
3041 _last_merge_target_rev = Column(
3037 'last_merge_other_rev', String(40), nullable=True)
3042 'last_merge_other_rev', String(40), nullable=True)
3038 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3039 merge_rev = Column('merge_rev', String(40), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3040
3045
3041 @hybrid_property
3046 @hybrid_property
3042 def revisions(self):
3047 def revisions(self):
3043 return self._revisions.split(':') if self._revisions else []
3048 return self._revisions.split(':') if self._revisions else []
3044
3049
3045 @revisions.setter
3050 @revisions.setter
3046 def revisions(self, val):
3051 def revisions(self, val):
3047 self._revisions = ':'.join(val)
3052 self._revisions = ':'.join(val)
3048
3053
3049 @declared_attr
3054 @declared_attr
3050 def author(cls):
3055 def author(cls):
3051 return relationship('User', lazy='joined')
3056 return relationship('User', lazy='joined')
3052
3057
3053 @declared_attr
3058 @declared_attr
3054 def source_repo(cls):
3059 def source_repo(cls):
3055 return relationship(
3060 return relationship(
3056 'Repository',
3061 'Repository',
3057 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3058
3063
3059 @property
3064 @property
3060 def source_ref_parts(self):
3065 def source_ref_parts(self):
3061 refs = self.source_ref.split(':')
3066 refs = self.source_ref.split(':')
3062 return Reference(refs[0], refs[1], refs[2])
3067 return Reference(refs[0], refs[1], refs[2])
3063
3068
3064 @declared_attr
3069 @declared_attr
3065 def target_repo(cls):
3070 def target_repo(cls):
3066 return relationship(
3071 return relationship(
3067 'Repository',
3072 'Repository',
3068 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3069
3074
3070 @property
3075 @property
3071 def target_ref_parts(self):
3076 def target_ref_parts(self):
3072 refs = self.target_ref.split(':')
3077 refs = self.target_ref.split(':')
3073 return Reference(refs[0], refs[1], refs[2])
3078 return Reference(refs[0], refs[1], refs[2])
3074
3079
3075
3080
3076 class PullRequest(Base, _PullRequestBase):
3081 class PullRequest(Base, _PullRequestBase):
3077 __tablename__ = 'pull_requests'
3082 __tablename__ = 'pull_requests'
3078 __table_args__ = (
3083 __table_args__ = (
3079 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3080 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3081 )
3086 )
3082
3087
3083 pull_request_id = Column(
3088 pull_request_id = Column(
3084 'pull_request_id', Integer(), nullable=False, primary_key=True)
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3085
3090
3086 def __repr__(self):
3091 def __repr__(self):
3087 if self.pull_request_id:
3092 if self.pull_request_id:
3088 return '<DB:PullRequest #%s>' % self.pull_request_id
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3089 else:
3094 else:
3090 return '<DB:PullRequest at %#x>' % id(self)
3095 return '<DB:PullRequest at %#x>' % id(self)
3091
3096
3092 reviewers = relationship('PullRequestReviewers',
3097 reviewers = relationship('PullRequestReviewers',
3093 cascade="all, delete, delete-orphan")
3098 cascade="all, delete, delete-orphan")
3094 statuses = relationship('ChangesetStatus')
3099 statuses = relationship('ChangesetStatus')
3095 comments = relationship('ChangesetComment',
3100 comments = relationship('ChangesetComment',
3096 cascade="all, delete, delete-orphan")
3101 cascade="all, delete, delete-orphan")
3097 versions = relationship('PullRequestVersion',
3102 versions = relationship('PullRequestVersion',
3098 cascade="all, delete, delete-orphan")
3103 cascade="all, delete, delete-orphan")
3099
3104
3100 def is_closed(self):
3105 def is_closed(self):
3101 return self.status == self.STATUS_CLOSED
3106 return self.status == self.STATUS_CLOSED
3102
3107
3103 def get_api_data(self):
3108 def get_api_data(self):
3104 from rhodecode.model.pull_request import PullRequestModel
3109 from rhodecode.model.pull_request import PullRequestModel
3105 pull_request = self
3110 pull_request = self
3106 merge_status = PullRequestModel().merge_status(pull_request)
3111 merge_status = PullRequestModel().merge_status(pull_request)
3107 pull_request_url = url(
3112 pull_request_url = url(
3108 'pullrequest_show', repo_name=self.target_repo.repo_name,
3113 'pullrequest_show', repo_name=self.target_repo.repo_name,
3109 pull_request_id=self.pull_request_id, qualified=True)
3114 pull_request_id=self.pull_request_id, qualified=True)
3110 data = {
3115 data = {
3111 'pull_request_id': pull_request.pull_request_id,
3116 'pull_request_id': pull_request.pull_request_id,
3112 'url': pull_request_url,
3117 'url': pull_request_url,
3113 'title': pull_request.title,
3118 'title': pull_request.title,
3114 'description': pull_request.description,
3119 'description': pull_request.description,
3115 'status': pull_request.status,
3120 'status': pull_request.status,
3116 'created_on': pull_request.created_on,
3121 'created_on': pull_request.created_on,
3117 'updated_on': pull_request.updated_on,
3122 'updated_on': pull_request.updated_on,
3118 'commit_ids': pull_request.revisions,
3123 'commit_ids': pull_request.revisions,
3119 'review_status': pull_request.calculated_review_status(),
3124 'review_status': pull_request.calculated_review_status(),
3120 'mergeable': {
3125 'mergeable': {
3121 'status': merge_status[0],
3126 'status': merge_status[0],
3122 'message': unicode(merge_status[1]),
3127 'message': unicode(merge_status[1]),
3123 },
3128 },
3124 'source': {
3129 'source': {
3125 'clone_url': pull_request.source_repo.clone_url(),
3130 'clone_url': pull_request.source_repo.clone_url(),
3126 'repository': pull_request.source_repo.repo_name,
3131 'repository': pull_request.source_repo.repo_name,
3127 'reference': {
3132 'reference': {
3128 'name': pull_request.source_ref_parts.name,
3133 'name': pull_request.source_ref_parts.name,
3129 'type': pull_request.source_ref_parts.type,
3134 'type': pull_request.source_ref_parts.type,
3130 'commit_id': pull_request.source_ref_parts.commit_id,
3135 'commit_id': pull_request.source_ref_parts.commit_id,
3131 },
3136 },
3132 },
3137 },
3133 'target': {
3138 'target': {
3134 'clone_url': pull_request.target_repo.clone_url(),
3139 'clone_url': pull_request.target_repo.clone_url(),
3135 'repository': pull_request.target_repo.repo_name,
3140 'repository': pull_request.target_repo.repo_name,
3136 'reference': {
3141 'reference': {
3137 'name': pull_request.target_ref_parts.name,
3142 'name': pull_request.target_ref_parts.name,
3138 'type': pull_request.target_ref_parts.type,
3143 'type': pull_request.target_ref_parts.type,
3139 'commit_id': pull_request.target_ref_parts.commit_id,
3144 'commit_id': pull_request.target_ref_parts.commit_id,
3140 },
3145 },
3141 },
3146 },
3142 'shadow': {
3147 'shadow': {
3143 'clone_url': PullRequestModel().get_shadow_clone_url(
3148 'clone_url': PullRequestModel().get_shadow_clone_url(
3144 pull_request),
3149 pull_request),
3145 },
3150 },
3146 'author': pull_request.author.get_api_data(include_secrets=False,
3151 'author': pull_request.author.get_api_data(include_secrets=False,
3147 details='basic'),
3152 details='basic'),
3148 'reviewers': [
3153 'reviewers': [
3149 {
3154 {
3150 'user': reviewer.get_api_data(include_secrets=False,
3155 'user': reviewer.get_api_data(include_secrets=False,
3151 details='basic'),
3156 details='basic'),
3152 'reasons': reasons,
3157 'reasons': reasons,
3153 'review_status': st[0][1].status if st else 'not_reviewed',
3158 'review_status': st[0][1].status if st else 'not_reviewed',
3154 }
3159 }
3155 for reviewer, reasons, st in pull_request.reviewers_statuses()
3160 for reviewer, reasons, st in pull_request.reviewers_statuses()
3156 ]
3161 ]
3157 }
3162 }
3158
3163
3159 return data
3164 return data
3160
3165
3161 def __json__(self):
3166 def __json__(self):
3162 return {
3167 return {
3163 'revisions': self.revisions,
3168 'revisions': self.revisions,
3164 }
3169 }
3165
3170
3166 def calculated_review_status(self):
3171 def calculated_review_status(self):
3167 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3172 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3168 # because it's tricky on how to use ChangesetStatusModel from there
3173 # because it's tricky on how to use ChangesetStatusModel from there
3169 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3174 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3170 from rhodecode.model.changeset_status import ChangesetStatusModel
3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3171 return ChangesetStatusModel().calculated_review_status(self)
3176 return ChangesetStatusModel().calculated_review_status(self)
3172
3177
3173 def reviewers_statuses(self):
3178 def reviewers_statuses(self):
3174 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3179 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3180 from rhodecode.model.changeset_status import ChangesetStatusModel
3176 return ChangesetStatusModel().reviewers_statuses(self)
3181 return ChangesetStatusModel().reviewers_statuses(self)
3177
3182
3178
3183
3179 class PullRequestVersion(Base, _PullRequestBase):
3184 class PullRequestVersion(Base, _PullRequestBase):
3180 __tablename__ = 'pull_request_versions'
3185 __tablename__ = 'pull_request_versions'
3181 __table_args__ = (
3186 __table_args__ = (
3182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3184 )
3189 )
3185
3190
3186 pull_request_version_id = Column(
3191 pull_request_version_id = Column(
3187 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3192 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3188 pull_request_id = Column(
3193 pull_request_id = Column(
3189 'pull_request_id', Integer(),
3194 'pull_request_id', Integer(),
3190 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3195 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3191 pull_request = relationship('PullRequest')
3196 pull_request = relationship('PullRequest')
3192
3197
3193 def __repr__(self):
3198 def __repr__(self):
3194 if self.pull_request_version_id:
3199 if self.pull_request_version_id:
3195 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3200 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3196 else:
3201 else:
3197 return '<DB:PullRequestVersion at %#x>' % id(self)
3202 return '<DB:PullRequestVersion at %#x>' % id(self)
3198
3203
3199
3204
3200 class PullRequestReviewers(Base, BaseModel):
3205 class PullRequestReviewers(Base, BaseModel):
3201 __tablename__ = 'pull_request_reviewers'
3206 __tablename__ = 'pull_request_reviewers'
3202 __table_args__ = (
3207 __table_args__ = (
3203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3208 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3209 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3205 )
3210 )
3206
3211
3207 def __init__(self, user=None, pull_request=None, reasons=None):
3212 def __init__(self, user=None, pull_request=None, reasons=None):
3208 self.user = user
3213 self.user = user
3209 self.pull_request = pull_request
3214 self.pull_request = pull_request
3210 self.reasons = reasons or []
3215 self.reasons = reasons or []
3211
3216
3212 @hybrid_property
3217 @hybrid_property
3213 def reasons(self):
3218 def reasons(self):
3214 if not self._reasons:
3219 if not self._reasons:
3215 return []
3220 return []
3216 return self._reasons
3221 return self._reasons
3217
3222
3218 @reasons.setter
3223 @reasons.setter
3219 def reasons(self, val):
3224 def reasons(self, val):
3220 val = val or []
3225 val = val or []
3221 if any(not isinstance(x, basestring) for x in val):
3226 if any(not isinstance(x, basestring) for x in val):
3222 raise Exception('invalid reasons type, must be list of strings')
3227 raise Exception('invalid reasons type, must be list of strings')
3223 self._reasons = val
3228 self._reasons = val
3224
3229
3225 pull_requests_reviewers_id = Column(
3230 pull_requests_reviewers_id = Column(
3226 'pull_requests_reviewers_id', Integer(), nullable=False,
3231 'pull_requests_reviewers_id', Integer(), nullable=False,
3227 primary_key=True)
3232 primary_key=True)
3228 pull_request_id = Column(
3233 pull_request_id = Column(
3229 "pull_request_id", Integer(),
3234 "pull_request_id", Integer(),
3230 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3235 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3231 user_id = Column(
3236 user_id = Column(
3232 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3237 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3233 _reasons = Column(
3238 _reasons = Column(
3234 'reason', MutationList.as_mutable(
3239 'reason', MutationList.as_mutable(
3235 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3240 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3236
3241
3237 user = relationship('User')
3242 user = relationship('User')
3238 pull_request = relationship('PullRequest')
3243 pull_request = relationship('PullRequest')
3239
3244
3240
3245
3241 class Notification(Base, BaseModel):
3246 class Notification(Base, BaseModel):
3242 __tablename__ = 'notifications'
3247 __tablename__ = 'notifications'
3243 __table_args__ = (
3248 __table_args__ = (
3244 Index('notification_type_idx', 'type'),
3249 Index('notification_type_idx', 'type'),
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3251 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3247 )
3252 )
3248
3253
3249 TYPE_CHANGESET_COMMENT = u'cs_comment'
3254 TYPE_CHANGESET_COMMENT = u'cs_comment'
3250 TYPE_MESSAGE = u'message'
3255 TYPE_MESSAGE = u'message'
3251 TYPE_MENTION = u'mention'
3256 TYPE_MENTION = u'mention'
3252 TYPE_REGISTRATION = u'registration'
3257 TYPE_REGISTRATION = u'registration'
3253 TYPE_PULL_REQUEST = u'pull_request'
3258 TYPE_PULL_REQUEST = u'pull_request'
3254 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3259 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3255
3260
3256 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3261 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3257 subject = Column('subject', Unicode(512), nullable=True)
3262 subject = Column('subject', Unicode(512), nullable=True)
3258 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3263 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3259 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3264 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3261 type_ = Column('type', Unicode(255))
3266 type_ = Column('type', Unicode(255))
3262
3267
3263 created_by_user = relationship('User')
3268 created_by_user = relationship('User')
3264 notifications_to_users = relationship('UserNotification', lazy='joined',
3269 notifications_to_users = relationship('UserNotification', lazy='joined',
3265 cascade="all, delete, delete-orphan")
3270 cascade="all, delete, delete-orphan")
3266
3271
3267 @property
3272 @property
3268 def recipients(self):
3273 def recipients(self):
3269 return [x.user for x in UserNotification.query()\
3274 return [x.user for x in UserNotification.query()\
3270 .filter(UserNotification.notification == self)\
3275 .filter(UserNotification.notification == self)\
3271 .order_by(UserNotification.user_id.asc()).all()]
3276 .order_by(UserNotification.user_id.asc()).all()]
3272
3277
3273 @classmethod
3278 @classmethod
3274 def create(cls, created_by, subject, body, recipients, type_=None):
3279 def create(cls, created_by, subject, body, recipients, type_=None):
3275 if type_ is None:
3280 if type_ is None:
3276 type_ = Notification.TYPE_MESSAGE
3281 type_ = Notification.TYPE_MESSAGE
3277
3282
3278 notification = cls()
3283 notification = cls()
3279 notification.created_by_user = created_by
3284 notification.created_by_user = created_by
3280 notification.subject = subject
3285 notification.subject = subject
3281 notification.body = body
3286 notification.body = body
3282 notification.type_ = type_
3287 notification.type_ = type_
3283 notification.created_on = datetime.datetime.now()
3288 notification.created_on = datetime.datetime.now()
3284
3289
3285 for u in recipients:
3290 for u in recipients:
3286 assoc = UserNotification()
3291 assoc = UserNotification()
3287 assoc.notification = notification
3292 assoc.notification = notification
3288
3293
3289 # if created_by is inside recipients mark his notification
3294 # if created_by is inside recipients mark his notification
3290 # as read
3295 # as read
3291 if u.user_id == created_by.user_id:
3296 if u.user_id == created_by.user_id:
3292 assoc.read = True
3297 assoc.read = True
3293
3298
3294 u.notifications.append(assoc)
3299 u.notifications.append(assoc)
3295 Session().add(notification)
3300 Session().add(notification)
3296
3301
3297 return notification
3302 return notification
3298
3303
3299 @property
3304 @property
3300 def description(self):
3305 def description(self):
3301 from rhodecode.model.notification import NotificationModel
3306 from rhodecode.model.notification import NotificationModel
3302 return NotificationModel().make_description(self)
3307 return NotificationModel().make_description(self)
3303
3308
3304
3309
3305 class UserNotification(Base, BaseModel):
3310 class UserNotification(Base, BaseModel):
3306 __tablename__ = 'user_to_notification'
3311 __tablename__ = 'user_to_notification'
3307 __table_args__ = (
3312 __table_args__ = (
3308 UniqueConstraint('user_id', 'notification_id'),
3313 UniqueConstraint('user_id', 'notification_id'),
3309 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3310 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3311 )
3316 )
3312 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3317 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3313 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3318 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3314 read = Column('read', Boolean, default=False)
3319 read = Column('read', Boolean, default=False)
3315 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3320 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3316
3321
3317 user = relationship('User', lazy="joined")
3322 user = relationship('User', lazy="joined")
3318 notification = relationship('Notification', lazy="joined",
3323 notification = relationship('Notification', lazy="joined",
3319 order_by=lambda: Notification.created_on.desc(),)
3324 order_by=lambda: Notification.created_on.desc(),)
3320
3325
3321 def mark_as_read(self):
3326 def mark_as_read(self):
3322 self.read = True
3327 self.read = True
3323 Session().add(self)
3328 Session().add(self)
3324
3329
3325
3330
3326 class Gist(Base, BaseModel):
3331 class Gist(Base, BaseModel):
3327 __tablename__ = 'gists'
3332 __tablename__ = 'gists'
3328 __table_args__ = (
3333 __table_args__ = (
3329 Index('g_gist_access_id_idx', 'gist_access_id'),
3334 Index('g_gist_access_id_idx', 'gist_access_id'),
3330 Index('g_created_on_idx', 'created_on'),
3335 Index('g_created_on_idx', 'created_on'),
3331 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3336 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3332 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3337 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3333 )
3338 )
3334 GIST_PUBLIC = u'public'
3339 GIST_PUBLIC = u'public'
3335 GIST_PRIVATE = u'private'
3340 GIST_PRIVATE = u'private'
3336 DEFAULT_FILENAME = u'gistfile1.txt'
3341 DEFAULT_FILENAME = u'gistfile1.txt'
3337
3342
3338 ACL_LEVEL_PUBLIC = u'acl_public'
3343 ACL_LEVEL_PUBLIC = u'acl_public'
3339 ACL_LEVEL_PRIVATE = u'acl_private'
3344 ACL_LEVEL_PRIVATE = u'acl_private'
3340
3345
3341 gist_id = Column('gist_id', Integer(), primary_key=True)
3346 gist_id = Column('gist_id', Integer(), primary_key=True)
3342 gist_access_id = Column('gist_access_id', Unicode(250))
3347 gist_access_id = Column('gist_access_id', Unicode(250))
3343 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3348 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3344 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3349 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3345 gist_expires = Column('gist_expires', Float(53), nullable=False)
3350 gist_expires = Column('gist_expires', Float(53), nullable=False)
3346 gist_type = Column('gist_type', Unicode(128), nullable=False)
3351 gist_type = Column('gist_type', Unicode(128), nullable=False)
3347 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3352 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3348 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3353 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3349 acl_level = Column('acl_level', Unicode(128), nullable=True)
3354 acl_level = Column('acl_level', Unicode(128), nullable=True)
3350
3355
3351 owner = relationship('User')
3356 owner = relationship('User')
3352
3357
3353 def __repr__(self):
3358 def __repr__(self):
3354 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3359 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3355
3360
3356 @classmethod
3361 @classmethod
3357 def get_or_404(cls, id_):
3362 def get_or_404(cls, id_):
3358 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3363 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3359 if not res:
3364 if not res:
3360 raise HTTPNotFound
3365 raise HTTPNotFound
3361 return res
3366 return res
3362
3367
3363 @classmethod
3368 @classmethod
3364 def get_by_access_id(cls, gist_access_id):
3369 def get_by_access_id(cls, gist_access_id):
3365 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3370 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3366
3371
3367 def gist_url(self):
3372 def gist_url(self):
3368 import rhodecode
3373 import rhodecode
3369 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3374 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3370 if alias_url:
3375 if alias_url:
3371 return alias_url.replace('{gistid}', self.gist_access_id)
3376 return alias_url.replace('{gistid}', self.gist_access_id)
3372
3377
3373 return url('gist', gist_id=self.gist_access_id, qualified=True)
3378 return url('gist', gist_id=self.gist_access_id, qualified=True)
3374
3379
3375 @classmethod
3380 @classmethod
3376 def base_path(cls):
3381 def base_path(cls):
3377 """
3382 """
3378 Returns base path when all gists are stored
3383 Returns base path when all gists are stored
3379
3384
3380 :param cls:
3385 :param cls:
3381 """
3386 """
3382 from rhodecode.model.gist import GIST_STORE_LOC
3387 from rhodecode.model.gist import GIST_STORE_LOC
3383 q = Session().query(RhodeCodeUi)\
3388 q = Session().query(RhodeCodeUi)\
3384 .filter(RhodeCodeUi.ui_key == URL_SEP)
3389 .filter(RhodeCodeUi.ui_key == URL_SEP)
3385 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3390 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3386 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3391 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3387
3392
3388 def get_api_data(self):
3393 def get_api_data(self):
3389 """
3394 """
3390 Common function for generating gist related data for API
3395 Common function for generating gist related data for API
3391 """
3396 """
3392 gist = self
3397 gist = self
3393 data = {
3398 data = {
3394 'gist_id': gist.gist_id,
3399 'gist_id': gist.gist_id,
3395 'type': gist.gist_type,
3400 'type': gist.gist_type,
3396 'access_id': gist.gist_access_id,
3401 'access_id': gist.gist_access_id,
3397 'description': gist.gist_description,
3402 'description': gist.gist_description,
3398 'url': gist.gist_url(),
3403 'url': gist.gist_url(),
3399 'expires': gist.gist_expires,
3404 'expires': gist.gist_expires,
3400 'created_on': gist.created_on,
3405 'created_on': gist.created_on,
3401 'modified_at': gist.modified_at,
3406 'modified_at': gist.modified_at,
3402 'content': None,
3407 'content': None,
3403 'acl_level': gist.acl_level,
3408 'acl_level': gist.acl_level,
3404 }
3409 }
3405 return data
3410 return data
3406
3411
3407 def __json__(self):
3412 def __json__(self):
3408 data = dict(
3413 data = dict(
3409 )
3414 )
3410 data.update(self.get_api_data())
3415 data.update(self.get_api_data())
3411 return data
3416 return data
3412 # SCM functions
3417 # SCM functions
3413
3418
3414 def scm_instance(self, **kwargs):
3419 def scm_instance(self, **kwargs):
3415 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3420 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3416 return get_vcs_instance(
3421 return get_vcs_instance(
3417 repo_path=safe_str(full_repo_path), create=False)
3422 repo_path=safe_str(full_repo_path), create=False)
3418
3423
3419
3424
3420 class DbMigrateVersion(Base, BaseModel):
3425 class DbMigrateVersion(Base, BaseModel):
3421 __tablename__ = 'db_migrate_version'
3426 __tablename__ = 'db_migrate_version'
3422 __table_args__ = (
3427 __table_args__ = (
3423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3428 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3424 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3429 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3425 )
3430 )
3426 repository_id = Column('repository_id', String(250), primary_key=True)
3431 repository_id = Column('repository_id', String(250), primary_key=True)
3427 repository_path = Column('repository_path', Text)
3432 repository_path = Column('repository_path', Text)
3428 version = Column('version', Integer)
3433 version = Column('version', Integer)
3429
3434
3430
3435
3431 class ExternalIdentity(Base, BaseModel):
3436 class ExternalIdentity(Base, BaseModel):
3432 __tablename__ = 'external_identities'
3437 __tablename__ = 'external_identities'
3433 __table_args__ = (
3438 __table_args__ = (
3434 Index('local_user_id_idx', 'local_user_id'),
3439 Index('local_user_id_idx', 'local_user_id'),
3435 Index('external_id_idx', 'external_id'),
3440 Index('external_id_idx', 'external_id'),
3436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3441 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3437 'mysql_charset': 'utf8'})
3442 'mysql_charset': 'utf8'})
3438
3443
3439 external_id = Column('external_id', Unicode(255), default=u'',
3444 external_id = Column('external_id', Unicode(255), default=u'',
3440 primary_key=True)
3445 primary_key=True)
3441 external_username = Column('external_username', Unicode(1024), default=u'')
3446 external_username = Column('external_username', Unicode(1024), default=u'')
3442 local_user_id = Column('local_user_id', Integer(),
3447 local_user_id = Column('local_user_id', Integer(),
3443 ForeignKey('users.user_id'), primary_key=True)
3448 ForeignKey('users.user_id'), primary_key=True)
3444 provider_name = Column('provider_name', Unicode(255), default=u'',
3449 provider_name = Column('provider_name', Unicode(255), default=u'',
3445 primary_key=True)
3450 primary_key=True)
3446 access_token = Column('access_token', String(1024), default=u'')
3451 access_token = Column('access_token', String(1024), default=u'')
3447 alt_token = Column('alt_token', String(1024), default=u'')
3452 alt_token = Column('alt_token', String(1024), default=u'')
3448 token_secret = Column('token_secret', String(1024), default=u'')
3453 token_secret = Column('token_secret', String(1024), default=u'')
3449
3454
3450 @classmethod
3455 @classmethod
3451 def by_external_id_and_provider(cls, external_id, provider_name,
3456 def by_external_id_and_provider(cls, external_id, provider_name,
3452 local_user_id=None):
3457 local_user_id=None):
3453 """
3458 """
3454 Returns ExternalIdentity instance based on search params
3459 Returns ExternalIdentity instance based on search params
3455
3460
3456 :param external_id:
3461 :param external_id:
3457 :param provider_name:
3462 :param provider_name:
3458 :return: ExternalIdentity
3463 :return: ExternalIdentity
3459 """
3464 """
3460 query = cls.query()
3465 query = cls.query()
3461 query = query.filter(cls.external_id == external_id)
3466 query = query.filter(cls.external_id == external_id)
3462 query = query.filter(cls.provider_name == provider_name)
3467 query = query.filter(cls.provider_name == provider_name)
3463 if local_user_id:
3468 if local_user_id:
3464 query = query.filter(cls.local_user_id == local_user_id)
3469 query = query.filter(cls.local_user_id == local_user_id)
3465 return query.first()
3470 return query.first()
3466
3471
3467 @classmethod
3472 @classmethod
3468 def user_by_external_id_and_provider(cls, external_id, provider_name):
3473 def user_by_external_id_and_provider(cls, external_id, provider_name):
3469 """
3474 """
3470 Returns User instance based on search params
3475 Returns User instance based on search params
3471
3476
3472 :param external_id:
3477 :param external_id:
3473 :param provider_name:
3478 :param provider_name:
3474 :return: User
3479 :return: User
3475 """
3480 """
3476 query = User.query()
3481 query = User.query()
3477 query = query.filter(cls.external_id == external_id)
3482 query = query.filter(cls.external_id == external_id)
3478 query = query.filter(cls.provider_name == provider_name)
3483 query = query.filter(cls.provider_name == provider_name)
3479 query = query.filter(User.user_id == cls.local_user_id)
3484 query = query.filter(User.user_id == cls.local_user_id)
3480 return query.first()
3485 return query.first()
3481
3486
3482 @classmethod
3487 @classmethod
3483 def by_local_user_id(cls, local_user_id):
3488 def by_local_user_id(cls, local_user_id):
3484 """
3489 """
3485 Returns all tokens for user
3490 Returns all tokens for user
3486
3491
3487 :param local_user_id:
3492 :param local_user_id:
3488 :return: ExternalIdentity
3493 :return: ExternalIdentity
3489 """
3494 """
3490 query = cls.query()
3495 query = cls.query()
3491 query = query.filter(cls.local_user_id == local_user_id)
3496 query = query.filter(cls.local_user_id == local_user_id)
3492 return query
3497 return query
3493
3498
3494
3499
3495 class Integration(Base, BaseModel):
3500 class Integration(Base, BaseModel):
3496 __tablename__ = 'integrations'
3501 __tablename__ = 'integrations'
3497 __table_args__ = (
3502 __table_args__ = (
3498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3500 )
3505 )
3501
3506
3502 integration_id = Column('integration_id', Integer(), primary_key=True)
3507 integration_id = Column('integration_id', Integer(), primary_key=True)
3503 integration_type = Column('integration_type', String(255))
3508 integration_type = Column('integration_type', String(255))
3504 enabled = Column('enabled', Boolean(), nullable=False)
3509 enabled = Column('enabled', Boolean(), nullable=False)
3505 name = Column('name', String(255), nullable=False)
3510 name = Column('name', String(255), nullable=False)
3506 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3511 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3507 default=False)
3512 default=False)
3508
3513
3509 settings = Column(
3514 settings = Column(
3510 'settings_json', MutationObj.as_mutable(
3515 'settings_json', MutationObj.as_mutable(
3511 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3516 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3512 repo_id = Column(
3517 repo_id = Column(
3513 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3518 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3514 nullable=True, unique=None, default=None)
3519 nullable=True, unique=None, default=None)
3515 repo = relationship('Repository', lazy='joined')
3520 repo = relationship('Repository', lazy='joined')
3516
3521
3517 repo_group_id = Column(
3522 repo_group_id = Column(
3518 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3523 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3519 nullable=True, unique=None, default=None)
3524 nullable=True, unique=None, default=None)
3520 repo_group = relationship('RepoGroup', lazy='joined')
3525 repo_group = relationship('RepoGroup', lazy='joined')
3521
3526
3522 @property
3527 @property
3523 def scope(self):
3528 def scope(self):
3524 if self.repo:
3529 if self.repo:
3525 return repr(self.repo)
3530 return repr(self.repo)
3526 if self.repo_group:
3531 if self.repo_group:
3527 if self.child_repos_only:
3532 if self.child_repos_only:
3528 return repr(self.repo_group) + ' (child repos only)'
3533 return repr(self.repo_group) + ' (child repos only)'
3529 else:
3534 else:
3530 return repr(self.repo_group) + ' (recursive)'
3535 return repr(self.repo_group) + ' (recursive)'
3531 if self.child_repos_only:
3536 if self.child_repos_only:
3532 return 'root_repos'
3537 return 'root_repos'
3533 return 'global'
3538 return 'global'
3534
3539
3535 def __repr__(self):
3540 def __repr__(self):
3536 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3541 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3537
3542
3538
3543
3539 class RepoReviewRuleUser(Base, BaseModel):
3544 class RepoReviewRuleUser(Base, BaseModel):
3540 __tablename__ = 'repo_review_rules_users'
3545 __tablename__ = 'repo_review_rules_users'
3541 __table_args__ = (
3546 __table_args__ = (
3542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3543 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3548 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3544 )
3549 )
3545 repo_review_rule_user_id = Column(
3550 repo_review_rule_user_id = Column(
3546 'repo_review_rule_user_id', Integer(), primary_key=True)
3551 'repo_review_rule_user_id', Integer(), primary_key=True)
3547 repo_review_rule_id = Column("repo_review_rule_id",
3552 repo_review_rule_id = Column("repo_review_rule_id",
3548 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3553 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3549 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3554 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3550 nullable=False)
3555 nullable=False)
3551 user = relationship('User')
3556 user = relationship('User')
3552
3557
3553
3558
3554 class RepoReviewRuleUserGroup(Base, BaseModel):
3559 class RepoReviewRuleUserGroup(Base, BaseModel):
3555 __tablename__ = 'repo_review_rules_users_groups'
3560 __tablename__ = 'repo_review_rules_users_groups'
3556 __table_args__ = (
3561 __table_args__ = (
3557 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3558 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3563 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3559 )
3564 )
3560 repo_review_rule_users_group_id = Column(
3565 repo_review_rule_users_group_id = Column(
3561 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3566 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3562 repo_review_rule_id = Column("repo_review_rule_id",
3567 repo_review_rule_id = Column("repo_review_rule_id",
3563 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3568 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3564 users_group_id = Column("users_group_id", Integer(),
3569 users_group_id = Column("users_group_id", Integer(),
3565 ForeignKey('users_groups.users_group_id'), nullable=False)
3570 ForeignKey('users_groups.users_group_id'), nullable=False)
3566 users_group = relationship('UserGroup')
3571 users_group = relationship('UserGroup')
3567
3572
3568
3573
3569 class RepoReviewRule(Base, BaseModel):
3574 class RepoReviewRule(Base, BaseModel):
3570 __tablename__ = 'repo_review_rules'
3575 __tablename__ = 'repo_review_rules'
3571 __table_args__ = (
3576 __table_args__ = (
3572 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3573 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3578 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3574 )
3579 )
3575
3580
3576 repo_review_rule_id = Column(
3581 repo_review_rule_id = Column(
3577 'repo_review_rule_id', Integer(), primary_key=True)
3582 'repo_review_rule_id', Integer(), primary_key=True)
3578 repo_id = Column(
3583 repo_id = Column(
3579 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3584 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3580 repo = relationship('Repository', backref='review_rules')
3585 repo = relationship('Repository', backref='review_rules')
3581
3586
3582 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3587 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3583 default=u'*') # glob
3588 default=u'*') # glob
3584 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3589 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3585 default=u'*') # glob
3590 default=u'*') # glob
3586
3591
3587 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3592 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3588 nullable=False, default=False)
3593 nullable=False, default=False)
3589 rule_users = relationship('RepoReviewRuleUser')
3594 rule_users = relationship('RepoReviewRuleUser')
3590 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3595 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3591
3596
3592 @hybrid_property
3597 @hybrid_property
3593 def branch_pattern(self):
3598 def branch_pattern(self):
3594 return self._branch_pattern or '*'
3599 return self._branch_pattern or '*'
3595
3600
3596 def _validate_glob(self, value):
3601 def _validate_glob(self, value):
3597 re.compile('^' + glob2re(value) + '$')
3602 re.compile('^' + glob2re(value) + '$')
3598
3603
3599 @branch_pattern.setter
3604 @branch_pattern.setter
3600 def branch_pattern(self, value):
3605 def branch_pattern(self, value):
3601 self._validate_glob(value)
3606 self._validate_glob(value)
3602 self._branch_pattern = value or '*'
3607 self._branch_pattern = value or '*'
3603
3608
3604 @hybrid_property
3609 @hybrid_property
3605 def file_pattern(self):
3610 def file_pattern(self):
3606 return self._file_pattern or '*'
3611 return self._file_pattern or '*'
3607
3612
3608 @file_pattern.setter
3613 @file_pattern.setter
3609 def file_pattern(self, value):
3614 def file_pattern(self, value):
3610 self._validate_glob(value)
3615 self._validate_glob(value)
3611 self._file_pattern = value or '*'
3616 self._file_pattern = value or '*'
3612
3617
3613 def matches(self, branch, files_changed):
3618 def matches(self, branch, files_changed):
3614 """
3619 """
3615 Check if this review rule matches a branch/files in a pull request
3620 Check if this review rule matches a branch/files in a pull request
3616
3621
3617 :param branch: branch name for the commit
3622 :param branch: branch name for the commit
3618 :param files_changed: list of file paths changed in the pull request
3623 :param files_changed: list of file paths changed in the pull request
3619 """
3624 """
3620
3625
3621 branch = branch or ''
3626 branch = branch or ''
3622 files_changed = files_changed or []
3627 files_changed = files_changed or []
3623
3628
3624 branch_matches = True
3629 branch_matches = True
3625 if branch:
3630 if branch:
3626 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3631 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3627 branch_matches = bool(branch_regex.search(branch))
3632 branch_matches = bool(branch_regex.search(branch))
3628
3633
3629 files_matches = True
3634 files_matches = True
3630 if self.file_pattern != '*':
3635 if self.file_pattern != '*':
3631 files_matches = False
3636 files_matches = False
3632 file_regex = re.compile(glob2re(self.file_pattern))
3637 file_regex = re.compile(glob2re(self.file_pattern))
3633 for filename in files_changed:
3638 for filename in files_changed:
3634 if file_regex.search(filename):
3639 if file_regex.search(filename):
3635 files_matches = True
3640 files_matches = True
3636 break
3641 break
3637
3642
3638 return branch_matches and files_matches
3643 return branch_matches and files_matches
3639
3644
3640 @property
3645 @property
3641 def review_users(self):
3646 def review_users(self):
3642 """ Returns the users which this rule applies to """
3647 """ Returns the users which this rule applies to """
3643
3648
3644 users = set()
3649 users = set()
3645 users |= set([
3650 users |= set([
3646 rule_user.user for rule_user in self.rule_users
3651 rule_user.user for rule_user in self.rule_users
3647 if rule_user.user.active])
3652 if rule_user.user.active])
3648 users |= set(
3653 users |= set(
3649 member.user
3654 member.user
3650 for rule_user_group in self.rule_user_groups
3655 for rule_user_group in self.rule_user_groups
3651 for member in rule_user_group.users_group.members
3656 for member in rule_user_group.users_group.members
3652 if member.user.active
3657 if member.user.active
3653 )
3658 )
3654 return users
3659 return users
3655
3660
3656 def __repr__(self):
3661 def __repr__(self):
3657 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3662 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3658 self.repo_review_rule_id, self.repo)
3663 self.repo_review_rule_id, self.repo)
@@ -1,551 +1,553 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
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 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
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.
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 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
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 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pylons.i18n.translation import _
51 from pylons.i18n.translation import _
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 return self.load(template_name)(**kw)
69 return self.load(template_name)(**kw)
70
70
71
71
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 deform.Form.set_default_renderer(form_renderer)
73 deform.Form.set_default_renderer(form_renderer)
74
74
75
75
76 def LoginForm():
76 def LoginForm():
77 class _LoginForm(formencode.Schema):
77 class _LoginForm(formencode.Schema):
78 allow_extra_fields = True
78 allow_extra_fields = True
79 filter_extra_fields = True
79 filter_extra_fields = True
80 username = v.UnicodeString(
80 username = v.UnicodeString(
81 strip=True,
81 strip=True,
82 min=1,
82 min=1,
83 not_empty=True,
83 not_empty=True,
84 messages={
84 messages={
85 'empty': _(u'Please enter a login'),
85 'empty': _(u'Please enter a login'),
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 }
87 }
88 )
88 )
89
89
90 password = v.UnicodeString(
90 password = v.UnicodeString(
91 strip=False,
91 strip=False,
92 min=3,
92 min=3,
93 not_empty=True,
93 not_empty=True,
94 messages={
94 messages={
95 'empty': _(u'Please enter a password'),
95 'empty': _(u'Please enter a password'),
96 'tooShort': _(u'Enter %(min)i characters or more')}
96 'tooShort': _(u'Enter %(min)i characters or more')}
97 )
97 )
98
98
99 remember = v.StringBoolean(if_missing=False)
99 remember = v.StringBoolean(if_missing=False)
100
100
101 chained_validators = [v.ValidAuth()]
101 chained_validators = [v.ValidAuth()]
102 return _LoginForm
102 return _LoginForm
103
103
104
104
105 def UserForm(edit=False, available_languages=[], old_data={}):
105 def UserForm(edit=False, available_languages=[], old_data={}):
106 class _UserForm(formencode.Schema):
106 class _UserForm(formencode.Schema):
107 allow_extra_fields = True
107 allow_extra_fields = True
108 filter_extra_fields = True
108 filter_extra_fields = True
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 v.ValidUsername(edit, old_data))
110 v.ValidUsername(edit, old_data))
111 if edit:
111 if edit:
112 new_password = All(
112 new_password = All(
113 v.ValidPassword(),
113 v.ValidPassword(),
114 v.UnicodeString(strip=False, min=6, not_empty=False)
114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 )
115 )
116 password_confirmation = All(
116 password_confirmation = All(
117 v.ValidPassword(),
117 v.ValidPassword(),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 )
119 )
120 admin = v.StringBoolean(if_missing=False)
120 admin = v.StringBoolean(if_missing=False)
121 else:
121 else:
122 password = All(
122 password = All(
123 v.ValidPassword(),
123 v.ValidPassword(),
124 v.UnicodeString(strip=False, min=6, not_empty=True)
124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 )
125 )
126 password_confirmation = All(
126 password_confirmation = All(
127 v.ValidPassword(),
127 v.ValidPassword(),
128 v.UnicodeString(strip=False, min=6, not_empty=False)
128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 )
129 )
130
130
131 password_change = v.StringBoolean(if_missing=False)
131 password_change = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
133
133
134 active = v.StringBoolean(if_missing=False)
134 active = v.StringBoolean(if_missing=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 extern_name = v.UnicodeString(strip=True)
138 extern_name = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
140 language = v.OneOf(available_languages, hideList=False,
140 language = v.OneOf(available_languages, hideList=False,
141 testValueList=True, if_missing=None)
141 testValueList=True, if_missing=None)
142 chained_validators = [v.ValidPasswordsMatch()]
142 chained_validators = [v.ValidPasswordsMatch()]
143 return _UserForm
143 return _UserForm
144
144
145
145
146 def UserGroupForm(edit=False, old_data=None, available_members=None,
146 def UserGroupForm(edit=False, old_data=None, available_members=None,
147 allow_disabled=False):
147 allow_disabled=False):
148 old_data = old_data or {}
148 old_data = old_data or {}
149 available_members = available_members or []
149 available_members = available_members or []
150
150
151 class _UserGroupForm(formencode.Schema):
151 class _UserGroupForm(formencode.Schema):
152 allow_extra_fields = True
152 allow_extra_fields = True
153 filter_extra_fields = True
153 filter_extra_fields = True
154
154
155 users_group_name = All(
155 users_group_name = All(
156 v.UnicodeString(strip=True, min=1, not_empty=True),
156 v.UnicodeString(strip=True, min=1, not_empty=True),
157 v.ValidUserGroup(edit, old_data)
157 v.ValidUserGroup(edit, old_data)
158 )
158 )
159 user_group_description = v.UnicodeString(strip=True, min=1,
159 user_group_description = v.UnicodeString(strip=True, min=1,
160 not_empty=False)
160 not_empty=False)
161
161
162 users_group_active = v.StringBoolean(if_missing=False)
162 users_group_active = v.StringBoolean(if_missing=False)
163
163
164 if edit:
164 if edit:
165 users_group_members = v.OneOf(
165 users_group_members = v.OneOf(
166 available_members, hideList=False, testValueList=True,
166 available_members, hideList=False, testValueList=True,
167 if_missing=None, not_empty=False
167 if_missing=None, not_empty=False
168 )
168 )
169 # this is user group owner
169 # this is user group owner
170 user = All(
170 user = All(
171 v.UnicodeString(not_empty=True),
171 v.UnicodeString(not_empty=True),
172 v.ValidRepoUser(allow_disabled))
172 v.ValidRepoUser(allow_disabled))
173 return _UserGroupForm
173 return _UserGroupForm
174
174
175
175
176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
177 can_create_in_root=False, allow_disabled=False):
177 can_create_in_root=False, allow_disabled=False):
178 old_data = old_data or {}
178 old_data = old_data or {}
179 available_groups = available_groups or []
179 available_groups = available_groups or []
180
180
181 class _RepoGroupForm(formencode.Schema):
181 class _RepoGroupForm(formencode.Schema):
182 allow_extra_fields = True
182 allow_extra_fields = True
183 filter_extra_fields = False
183 filter_extra_fields = False
184
184
185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
186 v.SlugifyName(),)
186 v.SlugifyName(),)
187 group_description = v.UnicodeString(strip=True, min=1,
187 group_description = v.UnicodeString(strip=True, min=1,
188 not_empty=False)
188 not_empty=False)
189 group_copy_permissions = v.StringBoolean(if_missing=False)
189 group_copy_permissions = v.StringBoolean(if_missing=False)
190
190
191 group_parent_id = v.OneOf(available_groups, hideList=False,
191 group_parent_id = v.OneOf(available_groups, hideList=False,
192 testValueList=True, not_empty=True)
192 testValueList=True, not_empty=True)
193 enable_locking = v.StringBoolean(if_missing=False)
193 enable_locking = v.StringBoolean(if_missing=False)
194 chained_validators = [
194 chained_validators = [
195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
196
196
197 if edit:
197 if edit:
198 # this is repo group owner
198 # this is repo group owner
199 user = All(
199 user = All(
200 v.UnicodeString(not_empty=True),
200 v.UnicodeString(not_empty=True),
201 v.ValidRepoUser(allow_disabled))
201 v.ValidRepoUser(allow_disabled))
202
202
203 return _RepoGroupForm
203 return _RepoGroupForm
204
204
205
205
206 def RegisterForm(edit=False, old_data={}):
206 def RegisterForm(edit=False, old_data={}):
207 class _RegisterForm(formencode.Schema):
207 class _RegisterForm(formencode.Schema):
208 allow_extra_fields = True
208 allow_extra_fields = True
209 filter_extra_fields = True
209 filter_extra_fields = True
210 username = All(
210 username = All(
211 v.ValidUsername(edit, old_data),
211 v.ValidUsername(edit, old_data),
212 v.UnicodeString(strip=True, min=1, not_empty=True)
212 v.UnicodeString(strip=True, min=1, not_empty=True)
213 )
213 )
214 password = All(
214 password = All(
215 v.ValidPassword(),
215 v.ValidPassword(),
216 v.UnicodeString(strip=False, min=6, not_empty=True)
216 v.UnicodeString(strip=False, min=6, not_empty=True)
217 )
217 )
218 password_confirmation = All(
218 password_confirmation = All(
219 v.ValidPassword(),
219 v.ValidPassword(),
220 v.UnicodeString(strip=False, min=6, not_empty=True)
220 v.UnicodeString(strip=False, min=6, not_empty=True)
221 )
221 )
222 active = v.StringBoolean(if_missing=False)
222 active = v.StringBoolean(if_missing=False)
223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
226
226
227 chained_validators = [v.ValidPasswordsMatch()]
227 chained_validators = [v.ValidPasswordsMatch()]
228
228
229 return _RegisterForm
229 return _RegisterForm
230
230
231
231
232 def PasswordResetForm():
232 def PasswordResetForm():
233 class _PasswordResetForm(formencode.Schema):
233 class _PasswordResetForm(formencode.Schema):
234 allow_extra_fields = True
234 allow_extra_fields = True
235 filter_extra_fields = True
235 filter_extra_fields = True
236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
237 return _PasswordResetForm
237 return _PasswordResetForm
238
238
239
239
240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
241 allow_disabled=False):
241 allow_disabled=False):
242 old_data = old_data or {}
242 old_data = old_data or {}
243 repo_groups = repo_groups or []
243 repo_groups = repo_groups or []
244 landing_revs = landing_revs or []
244 landing_revs = landing_revs or []
245 supported_backends = BACKENDS.keys()
245 supported_backends = BACKENDS.keys()
246
246
247 class _RepoForm(formencode.Schema):
247 class _RepoForm(formencode.Schema):
248 allow_extra_fields = True
248 allow_extra_fields = True
249 filter_extra_fields = False
249 filter_extra_fields = False
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
251 v.SlugifyName())
251 v.SlugifyName())
252 repo_group = All(v.CanWriteGroup(old_data),
252 repo_group = All(v.CanWriteGroup(old_data),
253 v.OneOf(repo_groups, hideList=True))
253 v.OneOf(repo_groups, hideList=True))
254 repo_type = v.OneOf(supported_backends, required=False,
254 repo_type = v.OneOf(supported_backends, required=False,
255 if_missing=old_data.get('repo_type'))
255 if_missing=old_data.get('repo_type'))
256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
257 repo_private = v.StringBoolean(if_missing=False)
257 repo_private = v.StringBoolean(if_missing=False)
258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
259 repo_copy_permissions = v.StringBoolean(if_missing=False)
259 repo_copy_permissions = v.StringBoolean(if_missing=False)
260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
261
261
262 repo_enable_statistics = v.StringBoolean(if_missing=False)
262 repo_enable_statistics = v.StringBoolean(if_missing=False)
263 repo_enable_downloads = v.StringBoolean(if_missing=False)
263 repo_enable_downloads = v.StringBoolean(if_missing=False)
264 repo_enable_locking = v.StringBoolean(if_missing=False)
264 repo_enable_locking = v.StringBoolean(if_missing=False)
265
265
266 if edit:
266 if edit:
267 # this is repo owner
267 # this is repo owner
268 user = All(
268 user = All(
269 v.UnicodeString(not_empty=True),
269 v.UnicodeString(not_empty=True),
270 v.ValidRepoUser(allow_disabled))
270 v.ValidRepoUser(allow_disabled))
271 clone_uri_change = v.UnicodeString(
271 clone_uri_change = v.UnicodeString(
272 not_empty=False, if_missing=v.Missing)
272 not_empty=False, if_missing=v.Missing)
273
273
274 chained_validators = [v.ValidCloneUri(),
274 chained_validators = [v.ValidCloneUri(),
275 v.ValidRepoName(edit, old_data)]
275 v.ValidRepoName(edit, old_data)]
276 return _RepoForm
276 return _RepoForm
277
277
278
278
279 def RepoPermsForm():
279 def RepoPermsForm():
280 class _RepoPermsForm(formencode.Schema):
280 class _RepoPermsForm(formencode.Schema):
281 allow_extra_fields = True
281 allow_extra_fields = True
282 filter_extra_fields = False
282 filter_extra_fields = False
283 chained_validators = [v.ValidPerms(type_='repo')]
283 chained_validators = [v.ValidPerms(type_='repo')]
284 return _RepoPermsForm
284 return _RepoPermsForm
285
285
286
286
287 def RepoGroupPermsForm(valid_recursive_choices):
287 def RepoGroupPermsForm(valid_recursive_choices):
288 class _RepoGroupPermsForm(formencode.Schema):
288 class _RepoGroupPermsForm(formencode.Schema):
289 allow_extra_fields = True
289 allow_extra_fields = True
290 filter_extra_fields = False
290 filter_extra_fields = False
291 recursive = v.OneOf(valid_recursive_choices)
291 recursive = v.OneOf(valid_recursive_choices)
292 chained_validators = [v.ValidPerms(type_='repo_group')]
292 chained_validators = [v.ValidPerms(type_='repo_group')]
293 return _RepoGroupPermsForm
293 return _RepoGroupPermsForm
294
294
295
295
296 def UserGroupPermsForm():
296 def UserGroupPermsForm():
297 class _UserPermsForm(formencode.Schema):
297 class _UserPermsForm(formencode.Schema):
298 allow_extra_fields = True
298 allow_extra_fields = True
299 filter_extra_fields = False
299 filter_extra_fields = False
300 chained_validators = [v.ValidPerms(type_='user_group')]
300 chained_validators = [v.ValidPerms(type_='user_group')]
301 return _UserPermsForm
301 return _UserPermsForm
302
302
303
303
304 def RepoFieldForm():
304 def RepoFieldForm():
305 class _RepoFieldForm(formencode.Schema):
305 class _RepoFieldForm(formencode.Schema):
306 filter_extra_fields = True
306 filter_extra_fields = True
307 allow_extra_fields = True
307 allow_extra_fields = True
308
308
309 new_field_key = All(v.FieldKey(),
309 new_field_key = All(v.FieldKey(),
310 v.UnicodeString(strip=True, min=3, not_empty=True))
310 v.UnicodeString(strip=True, min=3, not_empty=True))
311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
313 if_missing='str')
313 if_missing='str')
314 new_field_label = v.UnicodeString(not_empty=False)
314 new_field_label = v.UnicodeString(not_empty=False)
315 new_field_desc = v.UnicodeString(not_empty=False)
315 new_field_desc = v.UnicodeString(not_empty=False)
316
316
317 return _RepoFieldForm
317 return _RepoFieldForm
318
318
319
319
320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
321 repo_groups=[], landing_revs=[]):
321 repo_groups=[], landing_revs=[]):
322 class _RepoForkForm(formencode.Schema):
322 class _RepoForkForm(formencode.Schema):
323 allow_extra_fields = True
323 allow_extra_fields = True
324 filter_extra_fields = False
324 filter_extra_fields = False
325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
326 v.SlugifyName())
326 v.SlugifyName())
327 repo_group = All(v.CanWriteGroup(),
327 repo_group = All(v.CanWriteGroup(),
328 v.OneOf(repo_groups, hideList=True))
328 v.OneOf(repo_groups, hideList=True))
329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
331 private = v.StringBoolean(if_missing=False)
331 private = v.StringBoolean(if_missing=False)
332 copy_permissions = v.StringBoolean(if_missing=False)
332 copy_permissions = v.StringBoolean(if_missing=False)
333 fork_parent_id = v.UnicodeString()
333 fork_parent_id = v.UnicodeString()
334 chained_validators = [v.ValidForkName(edit, old_data)]
334 chained_validators = [v.ValidForkName(edit, old_data)]
335 landing_rev = v.OneOf(landing_revs, hideList=True)
335 landing_rev = v.OneOf(landing_revs, hideList=True)
336
336
337 return _RepoForkForm
337 return _RepoForkForm
338
338
339
339
340 def ApplicationSettingsForm():
340 def ApplicationSettingsForm():
341 class _ApplicationSettingsForm(formencode.Schema):
341 class _ApplicationSettingsForm(formencode.Schema):
342 allow_extra_fields = True
342 allow_extra_fields = True
343 filter_extra_fields = False
343 filter_extra_fields = False
344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
350
350
351 return _ApplicationSettingsForm
351 return _ApplicationSettingsForm
352
352
353
353
354 def ApplicationVisualisationForm():
354 def ApplicationVisualisationForm():
355 class _ApplicationVisualisationForm(formencode.Schema):
355 class _ApplicationVisualisationForm(formencode.Schema):
356 allow_extra_fields = True
356 allow_extra_fields = True
357 filter_extra_fields = False
357 filter_extra_fields = False
358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
361
361
362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
366 rhodecode_show_version = v.StringBoolean(if_missing=False)
366 rhodecode_show_version = v.StringBoolean(if_missing=False)
367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
369 rhodecode_gravatar_url = v.UnicodeString(min=3)
369 rhodecode_gravatar_url = v.UnicodeString(min=3)
370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
371 rhodecode_support_url = v.UnicodeString()
371 rhodecode_support_url = v.UnicodeString()
372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
374
374
375 return _ApplicationVisualisationForm
375 return _ApplicationVisualisationForm
376
376
377
377
378 class _BaseVcsSettingsForm(formencode.Schema):
378 class _BaseVcsSettingsForm(formencode.Schema):
379 allow_extra_fields = True
379 allow_extra_fields = True
380 filter_extra_fields = False
380 filter_extra_fields = False
381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
384
384
385 extensions_largefiles = v.StringBoolean(if_missing=False)
385 extensions_largefiles = v.StringBoolean(if_missing=False)
386 phases_publish = v.StringBoolean(if_missing=False)
386 phases_publish = v.StringBoolean(if_missing=False)
387
387
388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
391
391
392 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
392 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
393 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
393 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
394
394
395
395
396 def ApplicationUiSettingsForm():
396 def ApplicationUiSettingsForm():
397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
398 web_push_ssl = v.StringBoolean(if_missing=False)
398 web_push_ssl = v.StringBoolean(if_missing=False)
399 paths_root_path = All(
399 paths_root_path = All(
400 v.ValidPath(),
400 v.ValidPath(),
401 v.UnicodeString(strip=True, min=1, not_empty=True)
401 v.UnicodeString(strip=True, min=1, not_empty=True)
402 )
402 )
403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
404 extensions_hggit = v.StringBoolean(if_missing=False)
404 extensions_hggit = v.StringBoolean(if_missing=False)
405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
407
407
408 return _ApplicationUiSettingsForm
408 return _ApplicationUiSettingsForm
409
409
410
410
411 def RepoVcsSettingsForm(repo_name):
411 def RepoVcsSettingsForm(repo_name):
412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
413 inherit_global_settings = v.StringBoolean(if_missing=False)
413 inherit_global_settings = v.StringBoolean(if_missing=False)
414 new_svn_branch = v.ValidSvnPattern(
414 new_svn_branch = v.ValidSvnPattern(
415 section='vcs_svn_branch', repo_name=repo_name)
415 section='vcs_svn_branch', repo_name=repo_name)
416 new_svn_tag = v.ValidSvnPattern(
416 new_svn_tag = v.ValidSvnPattern(
417 section='vcs_svn_tag', repo_name=repo_name)
417 section='vcs_svn_tag', repo_name=repo_name)
418
418
419 return _RepoVcsSettingsForm
419 return _RepoVcsSettingsForm
420
420
421
421
422 def LabsSettingsForm():
422 def LabsSettingsForm():
423 class _LabSettingsForm(formencode.Schema):
423 class _LabSettingsForm(formencode.Schema):
424 allow_extra_fields = True
424 allow_extra_fields = True
425 filter_extra_fields = False
425 filter_extra_fields = False
426
426
427 return _LabSettingsForm
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 class _DefaultPermissionsForm(formencode.Schema):
432 class _DefaultPermissionsForm(formencode.Schema):
432 allow_extra_fields = True
433 allow_extra_fields = True
433 filter_extra_fields = True
434 filter_extra_fields = True
434
435
435 anonymous = v.StringBoolean(if_missing=False)
436 anonymous = v.StringBoolean(if_missing=False)
436 default_register = v.OneOf(register_choices)
437 default_register = v.OneOf(register_choices)
437 default_register_message = v.UnicodeString()
438 default_register_message = v.UnicodeString()
439 default_password_reset = v.OneOf(password_reset_choices)
438 default_extern_activate = v.OneOf(extern_activate_choices)
440 default_extern_activate = v.OneOf(extern_activate_choices)
439
441
440 return _DefaultPermissionsForm
442 return _DefaultPermissionsForm
441
443
442
444
443 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
445 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
444 user_group_perms_choices):
446 user_group_perms_choices):
445 class _ObjectPermissionsForm(formencode.Schema):
447 class _ObjectPermissionsForm(formencode.Schema):
446 allow_extra_fields = True
448 allow_extra_fields = True
447 filter_extra_fields = True
449 filter_extra_fields = True
448 overwrite_default_repo = v.StringBoolean(if_missing=False)
450 overwrite_default_repo = v.StringBoolean(if_missing=False)
449 overwrite_default_group = v.StringBoolean(if_missing=False)
451 overwrite_default_group = v.StringBoolean(if_missing=False)
450 overwrite_default_user_group = v.StringBoolean(if_missing=False)
452 overwrite_default_user_group = v.StringBoolean(if_missing=False)
451 default_repo_perm = v.OneOf(repo_perms_choices)
453 default_repo_perm = v.OneOf(repo_perms_choices)
452 default_group_perm = v.OneOf(group_perms_choices)
454 default_group_perm = v.OneOf(group_perms_choices)
453 default_user_group_perm = v.OneOf(user_group_perms_choices)
455 default_user_group_perm = v.OneOf(user_group_perms_choices)
454
456
455 return _ObjectPermissionsForm
457 return _ObjectPermissionsForm
456
458
457
459
458 def UserPermissionsForm(create_choices, create_on_write_choices,
460 def UserPermissionsForm(create_choices, create_on_write_choices,
459 repo_group_create_choices, user_group_create_choices,
461 repo_group_create_choices, user_group_create_choices,
460 fork_choices, inherit_default_permissions_choices):
462 fork_choices, inherit_default_permissions_choices):
461 class _DefaultPermissionsForm(formencode.Schema):
463 class _DefaultPermissionsForm(formencode.Schema):
462 allow_extra_fields = True
464 allow_extra_fields = True
463 filter_extra_fields = True
465 filter_extra_fields = True
464
466
465 anonymous = v.StringBoolean(if_missing=False)
467 anonymous = v.StringBoolean(if_missing=False)
466
468
467 default_repo_create = v.OneOf(create_choices)
469 default_repo_create = v.OneOf(create_choices)
468 default_repo_create_on_write = v.OneOf(create_on_write_choices)
470 default_repo_create_on_write = v.OneOf(create_on_write_choices)
469 default_user_group_create = v.OneOf(user_group_create_choices)
471 default_user_group_create = v.OneOf(user_group_create_choices)
470 default_repo_group_create = v.OneOf(repo_group_create_choices)
472 default_repo_group_create = v.OneOf(repo_group_create_choices)
471 default_fork_create = v.OneOf(fork_choices)
473 default_fork_create = v.OneOf(fork_choices)
472 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
474 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
473
475
474 return _DefaultPermissionsForm
476 return _DefaultPermissionsForm
475
477
476
478
477 def UserIndividualPermissionsForm():
479 def UserIndividualPermissionsForm():
478 class _DefaultPermissionsForm(formencode.Schema):
480 class _DefaultPermissionsForm(formencode.Schema):
479 allow_extra_fields = True
481 allow_extra_fields = True
480 filter_extra_fields = True
482 filter_extra_fields = True
481
483
482 inherit_default_permissions = v.StringBoolean(if_missing=False)
484 inherit_default_permissions = v.StringBoolean(if_missing=False)
483
485
484 return _DefaultPermissionsForm
486 return _DefaultPermissionsForm
485
487
486
488
487 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
489 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
488 class _DefaultsForm(formencode.Schema):
490 class _DefaultsForm(formencode.Schema):
489 allow_extra_fields = True
491 allow_extra_fields = True
490 filter_extra_fields = True
492 filter_extra_fields = True
491 default_repo_type = v.OneOf(supported_backends)
493 default_repo_type = v.OneOf(supported_backends)
492 default_repo_private = v.StringBoolean(if_missing=False)
494 default_repo_private = v.StringBoolean(if_missing=False)
493 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
495 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
494 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
496 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
495 default_repo_enable_locking = v.StringBoolean(if_missing=False)
497 default_repo_enable_locking = v.StringBoolean(if_missing=False)
496
498
497 return _DefaultsForm
499 return _DefaultsForm
498
500
499
501
500 def AuthSettingsForm():
502 def AuthSettingsForm():
501 class _AuthSettingsForm(formencode.Schema):
503 class _AuthSettingsForm(formencode.Schema):
502 allow_extra_fields = True
504 allow_extra_fields = True
503 filter_extra_fields = True
505 filter_extra_fields = True
504 auth_plugins = All(v.ValidAuthPlugins(),
506 auth_plugins = All(v.ValidAuthPlugins(),
505 v.UniqueListFromString()(not_empty=True))
507 v.UniqueListFromString()(not_empty=True))
506
508
507 return _AuthSettingsForm
509 return _AuthSettingsForm
508
510
509
511
510 def UserExtraEmailForm():
512 def UserExtraEmailForm():
511 class _UserExtraEmailForm(formencode.Schema):
513 class _UserExtraEmailForm(formencode.Schema):
512 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
514 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
513 return _UserExtraEmailForm
515 return _UserExtraEmailForm
514
516
515
517
516 def UserExtraIpForm():
518 def UserExtraIpForm():
517 class _UserExtraIpForm(formencode.Schema):
519 class _UserExtraIpForm(formencode.Schema):
518 ip = v.ValidIp()(not_empty=True)
520 ip = v.ValidIp()(not_empty=True)
519 return _UserExtraIpForm
521 return _UserExtraIpForm
520
522
521
523
522
524
523 def PullRequestForm(repo_id):
525 def PullRequestForm(repo_id):
524 class ReviewerForm(formencode.Schema):
526 class ReviewerForm(formencode.Schema):
525 user_id = v.Int(not_empty=True)
527 user_id = v.Int(not_empty=True)
526 reasons = All()
528 reasons = All()
527
529
528 class _PullRequestForm(formencode.Schema):
530 class _PullRequestForm(formencode.Schema):
529 allow_extra_fields = True
531 allow_extra_fields = True
530 filter_extra_fields = True
532 filter_extra_fields = True
531
533
532 user = v.UnicodeString(strip=True, required=True)
534 user = v.UnicodeString(strip=True, required=True)
533 source_repo = v.UnicodeString(strip=True, required=True)
535 source_repo = v.UnicodeString(strip=True, required=True)
534 source_ref = v.UnicodeString(strip=True, required=True)
536 source_ref = v.UnicodeString(strip=True, required=True)
535 target_repo = v.UnicodeString(strip=True, required=True)
537 target_repo = v.UnicodeString(strip=True, required=True)
536 target_ref = v.UnicodeString(strip=True, required=True)
538 target_ref = v.UnicodeString(strip=True, required=True)
537 revisions = All(#v.NotReviewedRevisions(repo_id)(),
539 revisions = All(#v.NotReviewedRevisions(repo_id)(),
538 v.UniqueList()(not_empty=True))
540 v.UniqueList()(not_empty=True))
539 review_members = formencode.ForEach(ReviewerForm())
541 review_members = formencode.ForEach(ReviewerForm())
540 pullrequest_title = v.UnicodeString(strip=True, required=True)
542 pullrequest_title = v.UnicodeString(strip=True, required=True)
541 pullrequest_desc = v.UnicodeString(strip=True, required=False)
543 pullrequest_desc = v.UnicodeString(strip=True, required=False)
542
544
543 return _PullRequestForm
545 return _PullRequestForm
544
546
545
547
546 def IssueTrackerPatternsForm():
548 def IssueTrackerPatternsForm():
547 class _IssueTrackerPatternsForm(formencode.Schema):
549 class _IssueTrackerPatternsForm(formencode.Schema):
548 allow_extra_fields = True
550 allow_extra_fields = True
549 filter_extra_fields = False
551 filter_extra_fields = False
550 chained_validators = [v.ValidPattern()]
552 chained_validators = [v.ValidPattern()]
551 return _IssueTrackerPatternsForm
553 return _IssueTrackerPatternsForm
@@ -1,470 +1,482 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 permissions model for RhodeCode
22 permissions model for RhodeCode
23 """
23 """
24
24
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from sqlalchemy.exc import DatabaseError
29 from sqlalchemy.exc import DatabaseError
30
30
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import (
32 from rhodecode.model.db import (
33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 UserUserGroupToPerm, UserGroup, UserGroupToPerm)
34 UserUserGroupToPerm, UserGroup, UserGroupToPerm)
35 from rhodecode.lib.utils2 import str2bool, safe_int
35 from rhodecode.lib.utils2 import str2bool, safe_int
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class PermissionModel(BaseModel):
40 class PermissionModel(BaseModel):
41 """
41 """
42 Permissions model for RhodeCode
42 Permissions model for RhodeCode
43 """
43 """
44
44
45 cls = Permission
45 cls = Permission
46 global_perms = {
46 global_perms = {
47 'default_repo_create': None,
47 'default_repo_create': None,
48 # special case for create repos on write access to group
48 # special case for create repos on write access to group
49 'default_repo_create_on_write': None,
49 'default_repo_create_on_write': None,
50 'default_repo_group_create': None,
50 'default_repo_group_create': None,
51 'default_user_group_create': None,
51 'default_user_group_create': None,
52 'default_fork_create': None,
52 'default_fork_create': None,
53 'default_inherit_default_permissions': None,
53 'default_inherit_default_permissions': None,
54
55 'default_register': None,
54 'default_register': None,
55 'default_password_reset': None,
56 'default_extern_activate': None,
56 'default_extern_activate': None,
57
57
58 # object permissions below
58 # object permissions below
59 'default_repo_perm': None,
59 'default_repo_perm': None,
60 'default_group_perm': None,
60 'default_group_perm': None,
61 'default_user_group_perm': None,
61 'default_user_group_perm': None,
62 }
62 }
63
63
64 def set_global_permission_choices(self, c_obj, translator):
64 def set_global_permission_choices(self, c_obj, translator):
65 c_obj.repo_perms_choices = [
65 c_obj.repo_perms_choices = [
66 ('repository.none', translator('None'),),
66 ('repository.none', translator('None'),),
67 ('repository.read', translator('Read'),),
67 ('repository.read', translator('Read'),),
68 ('repository.write', translator('Write'),),
68 ('repository.write', translator('Write'),),
69 ('repository.admin', translator('Admin'),)]
69 ('repository.admin', translator('Admin'),)]
70
70
71 c_obj.group_perms_choices = [
71 c_obj.group_perms_choices = [
72 ('group.none', translator('None'),),
72 ('group.none', translator('None'),),
73 ('group.read', translator('Read'),),
73 ('group.read', translator('Read'),),
74 ('group.write', translator('Write'),),
74 ('group.write', translator('Write'),),
75 ('group.admin', translator('Admin'),)]
75 ('group.admin', translator('Admin'),)]
76
76
77 c_obj.user_group_perms_choices = [
77 c_obj.user_group_perms_choices = [
78 ('usergroup.none', translator('None'),),
78 ('usergroup.none', translator('None'),),
79 ('usergroup.read', translator('Read'),),
79 ('usergroup.read', translator('Read'),),
80 ('usergroup.write', translator('Write'),),
80 ('usergroup.write', translator('Write'),),
81 ('usergroup.admin', translator('Admin'),)]
81 ('usergroup.admin', translator('Admin'),)]
82
82
83 c_obj.register_choices = [
83 c_obj.register_choices = [
84 ('hg.register.none', translator('Disabled')),
84 ('hg.register.none', translator('Disabled')),
85 ('hg.register.manual_activate', translator('Allowed with manual account activation')),
85 ('hg.register.manual_activate', translator('Allowed with manual account activation')),
86 ('hg.register.auto_activate', translator('Allowed with automatic account activation')),]
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 c_obj.extern_activate_choices = [
93 c_obj.extern_activate_choices = [
89 ('hg.extern_activate.manual', translator('Manual activation of external account')),
94 ('hg.extern_activate.manual', translator('Manual activation of external account')),
90 ('hg.extern_activate.auto', translator('Automatic activation of external account')),]
95 ('hg.extern_activate.auto', translator('Automatic activation of external account')),]
91
96
92 c_obj.repo_create_choices = [
97 c_obj.repo_create_choices = [
93 ('hg.create.none', translator('Disabled')),
98 ('hg.create.none', translator('Disabled')),
94 ('hg.create.repository', translator('Enabled'))]
99 ('hg.create.repository', translator('Enabled'))]
95
100
96 c_obj.repo_create_on_write_choices = [
101 c_obj.repo_create_on_write_choices = [
97 ('hg.create.write_on_repogroup.false', translator('Disabled')),
102 ('hg.create.write_on_repogroup.false', translator('Disabled')),
98 ('hg.create.write_on_repogroup.true', translator('Enabled'))]
103 ('hg.create.write_on_repogroup.true', translator('Enabled'))]
99
104
100 c_obj.user_group_create_choices = [
105 c_obj.user_group_create_choices = [
101 ('hg.usergroup.create.false', translator('Disabled')),
106 ('hg.usergroup.create.false', translator('Disabled')),
102 ('hg.usergroup.create.true', translator('Enabled'))]
107 ('hg.usergroup.create.true', translator('Enabled'))]
103
108
104 c_obj.repo_group_create_choices = [
109 c_obj.repo_group_create_choices = [
105 ('hg.repogroup.create.false', translator('Disabled')),
110 ('hg.repogroup.create.false', translator('Disabled')),
106 ('hg.repogroup.create.true', translator('Enabled'))]
111 ('hg.repogroup.create.true', translator('Enabled'))]
107
112
108 c_obj.fork_choices = [
113 c_obj.fork_choices = [
109 ('hg.fork.none', translator('Disabled')),
114 ('hg.fork.none', translator('Disabled')),
110 ('hg.fork.repository', translator('Enabled'))]
115 ('hg.fork.repository', translator('Enabled'))]
111
116
112 c_obj.inherit_default_permission_choices = [
117 c_obj.inherit_default_permission_choices = [
113 ('hg.inherit_default_perms.false', translator('Disabled')),
118 ('hg.inherit_default_perms.false', translator('Disabled')),
114 ('hg.inherit_default_perms.true', translator('Enabled'))]
119 ('hg.inherit_default_perms.true', translator('Enabled'))]
115
120
116 def get_default_perms(self, object_perms, suffix):
121 def get_default_perms(self, object_perms, suffix):
117 defaults = {}
122 defaults = {}
118 for perm in object_perms:
123 for perm in object_perms:
119 # perms
124 # perms
120 if perm.permission.permission_name.startswith('repository.'):
125 if perm.permission.permission_name.startswith('repository.'):
121 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
126 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
122
127
123 if perm.permission.permission_name.startswith('group.'):
128 if perm.permission.permission_name.startswith('group.'):
124 defaults['default_group_perm' + suffix] = perm.permission.permission_name
129 defaults['default_group_perm' + suffix] = perm.permission.permission_name
125
130
126 if perm.permission.permission_name.startswith('usergroup.'):
131 if perm.permission.permission_name.startswith('usergroup.'):
127 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
132 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
128
133
129 # creation of objects
134 # creation of objects
130 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
135 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
131 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
136 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
132
137
133 elif perm.permission.permission_name.startswith('hg.create.'):
138 elif perm.permission.permission_name.startswith('hg.create.'):
134 defaults['default_repo_create' + suffix] = perm.permission.permission_name
139 defaults['default_repo_create' + suffix] = perm.permission.permission_name
135
140
136 if perm.permission.permission_name.startswith('hg.fork.'):
141 if perm.permission.permission_name.startswith('hg.fork.'):
137 defaults['default_fork_create' + suffix] = perm.permission.permission_name
142 defaults['default_fork_create' + suffix] = perm.permission.permission_name
138
143
139 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
144 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
140 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
145 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
141
146
142 if perm.permission.permission_name.startswith('hg.repogroup.'):
147 if perm.permission.permission_name.startswith('hg.repogroup.'):
143 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
148 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
144
149
145 if perm.permission.permission_name.startswith('hg.usergroup.'):
150 if perm.permission.permission_name.startswith('hg.usergroup.'):
146 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
151 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
147
152
148 # registration and external account activation
153 # registration and external account activation
149 if perm.permission.permission_name.startswith('hg.register.'):
154 if perm.permission.permission_name.startswith('hg.register.'):
150 defaults['default_register' + suffix] = perm.permission.permission_name
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 if perm.permission.permission_name.startswith('hg.extern_activate.'):
160 if perm.permission.permission_name.startswith('hg.extern_activate.'):
153 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
161 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
154
162
155 return defaults
163 return defaults
156
164
157 def _make_new_user_perm(self, user, perm_name):
165 def _make_new_user_perm(self, user, perm_name):
158 log.debug('Creating new user permission:%s', perm_name)
166 log.debug('Creating new user permission:%s', perm_name)
159 new = UserToPerm()
167 new = UserToPerm()
160 new.user = user
168 new.user = user
161 new.permission = Permission.get_by_key(perm_name)
169 new.permission = Permission.get_by_key(perm_name)
162 return new
170 return new
163
171
164 def _make_new_user_group_perm(self, user_group, perm_name):
172 def _make_new_user_group_perm(self, user_group, perm_name):
165 log.debug('Creating new user group permission:%s', perm_name)
173 log.debug('Creating new user group permission:%s', perm_name)
166 new = UserGroupToPerm()
174 new = UserGroupToPerm()
167 new.users_group = user_group
175 new.users_group = user_group
168 new.permission = Permission.get_by_key(perm_name)
176 new.permission = Permission.get_by_key(perm_name)
169 return new
177 return new
170
178
171 def _keep_perm(self, perm_name, keep_fields):
179 def _keep_perm(self, perm_name, keep_fields):
172 def get_pat(field_name):
180 def get_pat(field_name):
173 return {
181 return {
174 # global perms
182 # global perms
175 'default_repo_create': 'hg.create.',
183 'default_repo_create': 'hg.create.',
176 # special case for create repos on write access to group
184 # special case for create repos on write access to group
177 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
185 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
178 'default_repo_group_create': 'hg.repogroup.create.',
186 'default_repo_group_create': 'hg.repogroup.create.',
179 'default_user_group_create': 'hg.usergroup.create.',
187 'default_user_group_create': 'hg.usergroup.create.',
180 'default_fork_create': 'hg.fork.',
188 'default_fork_create': 'hg.fork.',
181 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
189 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
182
190
183 # application perms
191 # application perms
184 'default_register': 'hg.register.',
192 'default_register': 'hg.register.',
193 'default_password_reset': 'hg.password_reset.',
185 'default_extern_activate': 'hg.extern_activate.',
194 'default_extern_activate': 'hg.extern_activate.',
186
195
187 # object permissions below
196 # object permissions below
188 'default_repo_perm': 'repository.',
197 'default_repo_perm': 'repository.',
189 'default_group_perm': 'group.',
198 'default_group_perm': 'group.',
190 'default_user_group_perm': 'usergroup.',
199 'default_user_group_perm': 'usergroup.',
191 }[field_name]
200 }[field_name]
192 for field in keep_fields:
201 for field in keep_fields:
193 pat = get_pat(field)
202 pat = get_pat(field)
194 if perm_name.startswith(pat):
203 if perm_name.startswith(pat):
195 return True
204 return True
196 return False
205 return False
197
206
198 def _clear_object_perm(self, object_perms, preserve=None):
207 def _clear_object_perm(self, object_perms, preserve=None):
199 preserve = preserve or []
208 preserve = preserve or []
200 _deleted = []
209 _deleted = []
201 for perm in object_perms:
210 for perm in object_perms:
202 perm_name = perm.permission.permission_name
211 perm_name = perm.permission.permission_name
203 if not self._keep_perm(perm_name, keep_fields=preserve):
212 if not self._keep_perm(perm_name, keep_fields=preserve):
204 _deleted.append(perm_name)
213 _deleted.append(perm_name)
205 self.sa.delete(perm)
214 self.sa.delete(perm)
206 return _deleted
215 return _deleted
207
216
208 def _clear_user_perms(self, user_id, preserve=None):
217 def _clear_user_perms(self, user_id, preserve=None):
209 perms = self.sa.query(UserToPerm)\
218 perms = self.sa.query(UserToPerm)\
210 .filter(UserToPerm.user_id == user_id)\
219 .filter(UserToPerm.user_id == user_id)\
211 .all()
220 .all()
212 return self._clear_object_perm(perms, preserve=preserve)
221 return self._clear_object_perm(perms, preserve=preserve)
213
222
214 def _clear_user_group_perms(self, user_group_id, preserve=None):
223 def _clear_user_group_perms(self, user_group_id, preserve=None):
215 perms = self.sa.query(UserGroupToPerm)\
224 perms = self.sa.query(UserGroupToPerm)\
216 .filter(UserGroupToPerm.users_group_id == user_group_id)\
225 .filter(UserGroupToPerm.users_group_id == user_group_id)\
217 .all()
226 .all()
218 return self._clear_object_perm(perms, preserve=preserve)
227 return self._clear_object_perm(perms, preserve=preserve)
219
228
220 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
229 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
221 # clear current entries, to make this function idempotent
230 # clear current entries, to make this function idempotent
222 # it will fix even if we define more permissions or permissions
231 # it will fix even if we define more permissions or permissions
223 # are somehow missing
232 # are somehow missing
224 preserve = preserve or []
233 preserve = preserve or []
225 _global_perms = self.global_perms.copy()
234 _global_perms = self.global_perms.copy()
226 if obj_type not in ['user', 'user_group']:
235 if obj_type not in ['user', 'user_group']:
227 raise ValueError("obj_type must be on of 'user' or 'user_group'")
236 raise ValueError("obj_type must be on of 'user' or 'user_group'")
228 if len(_global_perms) != len(Permission.DEFAULT_USER_PERMISSIONS):
237 if len(_global_perms) != len(Permission.DEFAULT_USER_PERMISSIONS):
229 raise Exception('Inconsistent permissions definition')
238 raise Exception('Inconsistent permissions definition')
230
239
231 if obj_type == 'user':
240 if obj_type == 'user':
232 self._clear_user_perms(object.user_id, preserve)
241 self._clear_user_perms(object.user_id, preserve)
233 if obj_type == 'user_group':
242 if obj_type == 'user_group':
234 self._clear_user_group_perms(object.users_group_id, preserve)
243 self._clear_user_group_perms(object.users_group_id, preserve)
235
244
236 # now kill the keys that we want to preserve from the form.
245 # now kill the keys that we want to preserve from the form.
237 for key in preserve:
246 for key in preserve:
238 del _global_perms[key]
247 del _global_perms[key]
239
248
240 for k in _global_perms.copy():
249 for k in _global_perms.copy():
241 _global_perms[k] = form_result[k]
250 _global_perms[k] = form_result[k]
242
251
243 # at that stage we validate all are passed inside form_result
252 # at that stage we validate all are passed inside form_result
244 for _perm_key, perm_value in _global_perms.items():
253 for _perm_key, perm_value in _global_perms.items():
245 if perm_value is None:
254 if perm_value is None:
246 raise ValueError('Missing permission for %s' % (_perm_key,))
255 raise ValueError('Missing permission for %s' % (_perm_key,))
247
256
248 if obj_type == 'user':
257 if obj_type == 'user':
249 p = self._make_new_user_perm(object, perm_value)
258 p = self._make_new_user_perm(object, perm_value)
250 self.sa.add(p)
259 self.sa.add(p)
251 if obj_type == 'user_group':
260 if obj_type == 'user_group':
252 p = self._make_new_user_group_perm(object, perm_value)
261 p = self._make_new_user_group_perm(object, perm_value)
253 self.sa.add(p)
262 self.sa.add(p)
254
263
255 def _set_new_user_perms(self, user, form_result, preserve=None):
264 def _set_new_user_perms(self, user, form_result, preserve=None):
256 return self._set_new_object_perms(
265 return self._set_new_object_perms(
257 'user', user, form_result, preserve)
266 'user', user, form_result, preserve)
258
267
259 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
268 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
260 return self._set_new_object_perms(
269 return self._set_new_object_perms(
261 'user_group', user_group, form_result, preserve)
270 'user_group', user_group, form_result, preserve)
262
271
263 def set_new_user_perms(self, user, form_result):
272 def set_new_user_perms(self, user, form_result):
264 # calculate what to preserve from what is given in form_result
273 # calculate what to preserve from what is given in form_result
265 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
274 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
266 return self._set_new_user_perms(user, form_result, preserve)
275 return self._set_new_user_perms(user, form_result, preserve)
267
276
268 def set_new_user_group_perms(self, user_group, form_result):
277 def set_new_user_group_perms(self, user_group, form_result):
269 # calculate what to preserve from what is given in form_result
278 # calculate what to preserve from what is given in form_result
270 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
279 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
271 return self._set_new_user_group_perms(user_group, form_result, preserve)
280 return self._set_new_user_group_perms(user_group, form_result, preserve)
272
281
273 def create_permissions(self):
282 def create_permissions(self):
274 """
283 """
275 Create permissions for whole system
284 Create permissions for whole system
276 """
285 """
277 for p in Permission.PERMS:
286 for p in Permission.PERMS:
278 if not Permission.get_by_key(p[0]):
287 if not Permission.get_by_key(p[0]):
279 new_perm = Permission()
288 new_perm = Permission()
280 new_perm.permission_name = p[0]
289 new_perm.permission_name = p[0]
281 new_perm.permission_longname = p[0] # translation err with p[1]
290 new_perm.permission_longname = p[0] # translation err with p[1]
282 self.sa.add(new_perm)
291 self.sa.add(new_perm)
283
292
284 def _create_default_object_permission(self, obj_type, obj, obj_perms,
293 def _create_default_object_permission(self, obj_type, obj, obj_perms,
285 force=False):
294 force=False):
286 if obj_type not in ['user', 'user_group']:
295 if obj_type not in ['user', 'user_group']:
287 raise ValueError("obj_type must be on of 'user' or 'user_group'")
296 raise ValueError("obj_type must be on of 'user' or 'user_group'")
288
297
289 def _get_group(perm_name):
298 def _get_group(perm_name):
290 return '.'.join(perm_name.split('.')[:1])
299 return '.'.join(perm_name.split('.')[:1])
291
300
292 defined_perms_groups = map(
301 defined_perms_groups = map(
293 _get_group, (x.permission.permission_name for x in obj_perms))
302 _get_group, (x.permission.permission_name for x in obj_perms))
294 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
303 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
295
304
296 if force:
305 if force:
297 self._clear_object_perm(obj_perms)
306 self._clear_object_perm(obj_perms)
298 self.sa.commit()
307 self.sa.commit()
299 defined_perms_groups = []
308 defined_perms_groups = []
300 # for every default permission that needs to be created, we check if
309 # for every default permission that needs to be created, we check if
301 # it's group is already defined, if it's not we create default perm
310 # it's group is already defined, if it's not we create default perm
302 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
311 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
303 gr = _get_group(perm_name)
312 gr = _get_group(perm_name)
304 if gr not in defined_perms_groups:
313 if gr not in defined_perms_groups:
305 log.debug('GR:%s not found, creating permission %s',
314 log.debug('GR:%s not found, creating permission %s',
306 gr, perm_name)
315 gr, perm_name)
307 if obj_type == 'user':
316 if obj_type == 'user':
308 new_perm = self._make_new_user_perm(obj, perm_name)
317 new_perm = self._make_new_user_perm(obj, perm_name)
309 self.sa.add(new_perm)
318 self.sa.add(new_perm)
310 if obj_type == 'user_group':
319 if obj_type == 'user_group':
311 new_perm = self._make_new_user_group_perm(obj, perm_name)
320 new_perm = self._make_new_user_group_perm(obj, perm_name)
312 self.sa.add(new_perm)
321 self.sa.add(new_perm)
313
322
314 def create_default_user_permissions(self, user, force=False):
323 def create_default_user_permissions(self, user, force=False):
315 """
324 """
316 Creates only missing default permissions for user, if force is set it
325 Creates only missing default permissions for user, if force is set it
317 resets the default permissions for that user
326 resets the default permissions for that user
318
327
319 :param user:
328 :param user:
320 :param force:
329 :param force:
321 """
330 """
322 user = self._get_user(user)
331 user = self._get_user(user)
323 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
332 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
324 return self._create_default_object_permission(
333 return self._create_default_object_permission(
325 'user', user, obj_perms, force)
334 'user', user, obj_perms, force)
326
335
327 def create_default_user_group_permissions(self, user_group, force=False):
336 def create_default_user_group_permissions(self, user_group, force=False):
328 """
337 """
329 Creates only missing default permissions for user group, if force is set it
338 Creates only missing default permissions for user group, if force is set it
330 resets the default permissions for that user group
339 resets the default permissions for that user group
331
340
332 :param user_group:
341 :param user_group:
333 :param force:
342 :param force:
334 """
343 """
335 user_group = self._get_user_group(user_group)
344 user_group = self._get_user_group(user_group)
336 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
345 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
337 return self._create_default_object_permission(
346 return self._create_default_object_permission(
338 'user_group', user_group, obj_perms, force)
347 'user_group', user_group, obj_perms, force)
339
348
340 def update_application_permissions(self, form_result):
349 def update_application_permissions(self, form_result):
341 if 'perm_user_id' in form_result:
350 if 'perm_user_id' in form_result:
342 perm_user = User.get(safe_int(form_result['perm_user_id']))
351 perm_user = User.get(safe_int(form_result['perm_user_id']))
343 else:
352 else:
344 # used mostly to do lookup for default user
353 # used mostly to do lookup for default user
345 perm_user = User.get_by_username(form_result['perm_user_name'])
354 perm_user = User.get_by_username(form_result['perm_user_name'])
346
355
347 try:
356 try:
348 # stage 1 set anonymous access
357 # stage 1 set anonymous access
349 if perm_user.username == User.DEFAULT_USER:
358 if perm_user.username == User.DEFAULT_USER:
350 perm_user.active = str2bool(form_result['anonymous'])
359 perm_user.active = str2bool(form_result['anonymous'])
351 self.sa.add(perm_user)
360 self.sa.add(perm_user)
352
361
353 # stage 2 reset defaults and set them from form data
362 # stage 2 reset defaults and set them from form data
354 self._set_new_user_perms(perm_user, form_result, preserve=[
363 self._set_new_user_perms(perm_user, form_result, preserve=[
355 'default_repo_perm',
364 'default_repo_perm',
356 'default_group_perm',
365 'default_group_perm',
357 'default_user_group_perm',
366 'default_user_group_perm',
358
367
359 'default_repo_group_create',
368 'default_repo_group_create',
360 'default_user_group_create',
369 'default_user_group_create',
361 'default_repo_create_on_write',
370 'default_repo_create_on_write',
362 'default_repo_create',
371 'default_repo_create',
363 'default_fork_create',
372 'default_fork_create',
364 'default_inherit_default_permissions',])
373 'default_inherit_default_permissions',])
365
374
366 self.sa.commit()
375 self.sa.commit()
367 except (DatabaseError,):
376 except (DatabaseError,):
368 log.error(traceback.format_exc())
377 log.error(traceback.format_exc())
369 self.sa.rollback()
378 self.sa.rollback()
370 raise
379 raise
371
380
372 def update_user_permissions(self, form_result):
381 def update_user_permissions(self, form_result):
373 if 'perm_user_id' in form_result:
382 if 'perm_user_id' in form_result:
374 perm_user = User.get(safe_int(form_result['perm_user_id']))
383 perm_user = User.get(safe_int(form_result['perm_user_id']))
375 else:
384 else:
376 # used mostly to do lookup for default user
385 # used mostly to do lookup for default user
377 perm_user = User.get_by_username(form_result['perm_user_name'])
386 perm_user = User.get_by_username(form_result['perm_user_name'])
378 try:
387 try:
379 # stage 2 reset defaults and set them from form data
388 # stage 2 reset defaults and set them from form data
380 self._set_new_user_perms(perm_user, form_result, preserve=[
389 self._set_new_user_perms(perm_user, form_result, preserve=[
381 'default_repo_perm',
390 'default_repo_perm',
382 'default_group_perm',
391 'default_group_perm',
383 'default_user_group_perm',
392 'default_user_group_perm',
384
393
385 'default_register',
394 'default_register',
395 'default_password_reset',
386 'default_extern_activate'])
396 'default_extern_activate'])
387 self.sa.commit()
397 self.sa.commit()
388 except (DatabaseError,):
398 except (DatabaseError,):
389 log.error(traceback.format_exc())
399 log.error(traceback.format_exc())
390 self.sa.rollback()
400 self.sa.rollback()
391 raise
401 raise
392
402
393 def update_user_group_permissions(self, form_result):
403 def update_user_group_permissions(self, form_result):
394 if 'perm_user_group_id' in form_result:
404 if 'perm_user_group_id' in form_result:
395 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
405 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
396 else:
406 else:
397 # used mostly to do lookup for default user
407 # used mostly to do lookup for default user
398 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
408 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
399 try:
409 try:
400 # stage 2 reset defaults and set them from form data
410 # stage 2 reset defaults and set them from form data
401 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
411 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
402 'default_repo_perm',
412 'default_repo_perm',
403 'default_group_perm',
413 'default_group_perm',
404 'default_user_group_perm',
414 'default_user_group_perm',
405
415
406 'default_register',
416 'default_register',
417 'default_password_reset',
407 'default_extern_activate'])
418 'default_extern_activate'])
408 self.sa.commit()
419 self.sa.commit()
409 except (DatabaseError,):
420 except (DatabaseError,):
410 log.error(traceback.format_exc())
421 log.error(traceback.format_exc())
411 self.sa.rollback()
422 self.sa.rollback()
412 raise
423 raise
413
424
414 def update_object_permissions(self, form_result):
425 def update_object_permissions(self, form_result):
415 if 'perm_user_id' in form_result:
426 if 'perm_user_id' in form_result:
416 perm_user = User.get(safe_int(form_result['perm_user_id']))
427 perm_user = User.get(safe_int(form_result['perm_user_id']))
417 else:
428 else:
418 # used mostly to do lookup for default user
429 # used mostly to do lookup for default user
419 perm_user = User.get_by_username(form_result['perm_user_name'])
430 perm_user = User.get_by_username(form_result['perm_user_name'])
420 try:
431 try:
421
432
422 # stage 2 reset defaults and set them from form data
433 # stage 2 reset defaults and set them from form data
423 self._set_new_user_perms(perm_user, form_result, preserve=[
434 self._set_new_user_perms(perm_user, form_result, preserve=[
424 'default_repo_group_create',
435 'default_repo_group_create',
425 'default_user_group_create',
436 'default_user_group_create',
426 'default_repo_create_on_write',
437 'default_repo_create_on_write',
427 'default_repo_create',
438 'default_repo_create',
428 'default_fork_create',
439 'default_fork_create',
429 'default_inherit_default_permissions',
440 'default_inherit_default_permissions',
430
441
431 'default_register',
442 'default_register',
443 'default_password_reset',
432 'default_extern_activate'])
444 'default_extern_activate'])
433
445
434 # overwrite default repo permissions
446 # overwrite default repo permissions
435 if form_result['overwrite_default_repo']:
447 if form_result['overwrite_default_repo']:
436 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
448 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
437 _def = Permission.get_by_key('repository.' + _def_name)
449 _def = Permission.get_by_key('repository.' + _def_name)
438 for r2p in self.sa.query(UserRepoToPerm)\
450 for r2p in self.sa.query(UserRepoToPerm)\
439 .filter(UserRepoToPerm.user == perm_user)\
451 .filter(UserRepoToPerm.user == perm_user)\
440 .all():
452 .all():
441 # don't reset PRIVATE repositories
453 # don't reset PRIVATE repositories
442 if not r2p.repository.private:
454 if not r2p.repository.private:
443 r2p.permission = _def
455 r2p.permission = _def
444 self.sa.add(r2p)
456 self.sa.add(r2p)
445
457
446 # overwrite default repo group permissions
458 # overwrite default repo group permissions
447 if form_result['overwrite_default_group']:
459 if form_result['overwrite_default_group']:
448 _def_name = form_result['default_group_perm'].split('group.')[-1]
460 _def_name = form_result['default_group_perm'].split('group.')[-1]
449 _def = Permission.get_by_key('group.' + _def_name)
461 _def = Permission.get_by_key('group.' + _def_name)
450 for g2p in self.sa.query(UserRepoGroupToPerm)\
462 for g2p in self.sa.query(UserRepoGroupToPerm)\
451 .filter(UserRepoGroupToPerm.user == perm_user)\
463 .filter(UserRepoGroupToPerm.user == perm_user)\
452 .all():
464 .all():
453 g2p.permission = _def
465 g2p.permission = _def
454 self.sa.add(g2p)
466 self.sa.add(g2p)
455
467
456 # overwrite default user group permissions
468 # overwrite default user group permissions
457 if form_result['overwrite_default_user_group']:
469 if form_result['overwrite_default_user_group']:
458 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
470 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
459 # user groups
471 # user groups
460 _def = Permission.get_by_key('usergroup.' + _def_name)
472 _def = Permission.get_by_key('usergroup.' + _def_name)
461 for g2p in self.sa.query(UserUserGroupToPerm)\
473 for g2p in self.sa.query(UserUserGroupToPerm)\
462 .filter(UserUserGroupToPerm.user == perm_user)\
474 .filter(UserUserGroupToPerm.user == perm_user)\
463 .all():
475 .all():
464 g2p.permission = _def
476 g2p.permission = _def
465 self.sa.add(g2p)
477 self.sa.add(g2p)
466 self.sa.commit()
478 self.sa.commit()
467 except (DatabaseError,):
479 except (DatabaseError,):
468 log.exception('Failed to set default object permissions')
480 log.exception('Failed to set default object permissions')
469 self.sa.rollback()
481 self.sa.rollback()
470 raise
482 raise
@@ -1,297 +1,301 b''
1 //LOGIN
1 //LOGIN
2
2
3
3
4 .loginbox {
4 .loginbox {
5 max-width: 65%;
5 max-width: 65%;
6 margin: @pagepadding auto;
6 margin: @pagepadding auto;
7 font-family: @text-light;
7 font-family: @text-light;
8 border: @border-thickness solid @grey5;
8 border: @border-thickness solid @grey5;
9 box-sizing: border-box;
9 box-sizing: border-box;
10
10
11 @media (max-width:1200px) {
11 @media (max-width:1200px) {
12 max-width: 85%;
12 max-width: 85%;
13 }
13 }
14
14
15 @media (max-width:768px) {
15 @media (max-width:768px) {
16 max-width: 100%;
16 max-width: 100%;
17 width: 100%;
17 width: 100%;
18 margin: 0;
18 margin: 0;
19 }
19 }
20
20
21 .title {
21 .title {
22 float: none;
22 float: none;
23 }
23 }
24
24
25 .header {
25 .header {
26 width: 100%;
26 width: 100%;
27 padding: 0 35px;
27 padding: 0 35px;
28 box-sizing: border-box;
28 box-sizing: border-box;
29
29
30 .title {
30 .title {
31 padding: 0;
31 padding: 0;
32 }
32 }
33 }
33 }
34
34
35 .loginwrapper {
35 .loginwrapper {
36 float: left;
36 float: left;
37 height: 100%;
37 height: 100%;
38 width: 100%;
38 width: 100%;
39 padding: 35px 55px 35px 0;
39 padding: 35px 55px 35px 0;
40 background-color: white;
40 background-color: white;
41 box-sizing: border-box;
41 box-sizing: border-box;
42
42
43 @media (max-width:414px) {
43 @media (max-width:414px) {
44 padding: 35px;
44 padding: 35px;
45 }
45 }
46 }
46 }
47
47
48 .left-column {
48 .left-column {
49 float: left;
49 float: left;
50 position: relative;
50 position: relative;
51 width: 50%;
51 width: 50%;
52 height: 100%;
52 height: 100%;
53
53
54 @media (max-width:414px) {
54 @media (max-width:414px) {
55 display:none;
55 display:none;
56 }
56 }
57 }
57 }
58
58
59 .right-column {
59 .right-column {
60 float: right;
60 float: right;
61 position: relative;
61 position: relative;
62 width: 50%;
62 width: 50%;
63
63
64 @media (max-width:414px) {
64 @media (max-width:414px) {
65 width: 100%;
65 width: 100%;
66 }
66 }
67 }
67 }
68
68
69 .sign-in-image {
69 .sign-in-image {
70 display: block;
70 display: block;
71 width: 65%;
71 width: 65%;
72 margin: 5% auto;
72 margin: 5% auto;
73 }
73 }
74
74
75 .sign-in-title {
75 .sign-in-title {
76 h1 {
76 h1 {
77 margin: 0;
77 margin: 0;
78 }
78 }
79
79
80 h4 {
80 h4 {
81 margin: @padding*2 0;
81 margin: @padding*2 0;
82 }
82 }
83 }
83 }
84
84
85 .form {
85 .form {
86 label {
86 label {
87 display: block;
87 display: block;
88 }
88 }
89
89
90 input {
90 input {
91 width: 100%;
91 width: 100%;
92 margin: 0 10% @padding 0;
92 margin: 0 10% @padding 0;
93 .box-sizing(border-box) ;
93 .box-sizing(border-box) ;
94
94
95 &[type="checkbox"] {
95 &[type="checkbox"] {
96 clear: both;
96 clear: both;
97 width: auto;
97 width: auto;
98 margin: 0 1em @padding 0;
98 margin: 0 1em @padding 0;
99 }
99 }
100 }
100 }
101
101
102 .checkbox {
102 .checkbox {
103 display: inline;
103 display: inline;
104 width: auto;
104 width: auto;
105 }
105 }
106
106
107 .sign-in {
107 .sign-in {
108 clear: both;
108 clear: both;
109 width: 100%;
109 width: 100%;
110 margin: @padding 0;
110 margin: @padding 0;
111 }
111 }
112 }
112 }
113 .register_message,
113 .register_message,
114 .activation_msg {
114 .activation_msg {
115 padding: 0 0 @padding;
115 padding: 0 0 @padding;
116 }
116 }
117
117
118 .buttons,
118 .buttons,
119 .links {
119 .links {
120 padding: 0;
120 padding: 0;
121 }
121 }
122
122
123 .buttons {
123 .buttons {
124 input {
124 input {
125 margin-right: 0;
125 margin-right: 0;
126 .box-sizing(border-box);
126 .box-sizing(border-box);
127 }
127 }
128
128
129 #sign_up, #send {
129 #sign_up, #send {
130 width: 100%;
130 width: 100%;
131 }
131 }
132 }
132 }
133
133
134 .fields {
134 .fields {
135 .field.field-compact {
135 .field.field-compact {
136 margin-bottom: 0px;
136 margin-bottom: 0px;
137 }
137 }
138
138
139 .buttons {
139 .buttons {
140 margin: 0;
140 margin: 0;
141 }
141 }
142
142
143 .field {
143 .field {
144 margin-bottom: 15px;
144 margin-bottom: 15px;
145
145
146 input {
146 input {
147 width: 100%;
147 width: 100%;
148 .box-sizing(border-box);
148 .box-sizing(border-box);
149 }
149 }
150
150
151 .input {
151 .input {
152 margin-left: 0;
152 margin-left: 0;
153 }
153 }
154
154
155 .label {
155 .label {
156 padding-top: 0;
156 padding-top: 0;
157 }
157 }
158 }
158 }
159 }
159 }
160
160
161 .checkbox {
161 .checkbox {
162 margin: 0 0 @textmargin 0;
162 margin: 0 0 @textmargin 0;
163
163
164 input[type="checkbox"] {
164 input[type="checkbox"] {
165 width: auto;
165 width: auto;
166 }
166 }
167
167
168 label {
168 label {
169 padding: 0;
169 padding: 0;
170 min-height: 0;
170 min-height: 0;
171 }
171 }
172 }
172 }
173
173
174 .activation_msg {
174 .activation_msg {
175 padding: @padding 0 0;
175 padding: @padding 0 0;
176 color: @grey4;
176 color: @grey4;
177 }
177 }
178
178
179 .links {
179 .links {
180 float: right;
180 float: right;
181 margin: 0;
181 margin: 0;
182 padding: 0;
182 padding: 0;
183 line-height: 1;
183 line-height: 1;
184
184
185 p {
185 p {
186 float: right;
186 float: right;
187 margin: 0;
187 margin: 0;
188 line-height: 1.5em;
188 line-height: 1.5em;
189 }
189 }
190 }
190 }
191
192 p.help-block {
193 margin-left: 0;
194 }
191 }
195 }
192
196
193 .user-menu.submenu {
197 .user-menu.submenu {
194 right: 0;
198 right: 0;
195 left: auto;
199 left: auto;
196 }
200 }
197 #quick_login {
201 #quick_login {
198 left: auto;
202 left: auto;
199 right: 0;
203 right: 0;
200 padding: @menupadding;
204 padding: @menupadding;
201 z-index: 999;
205 z-index: 999;
202 overflow: hidden;
206 overflow: hidden;
203 background-color: @grey6;
207 background-color: @grey6;
204 color: @grey2;
208 color: @grey2;
205
209
206 h4 {
210 h4 {
207 margin-bottom: 12px;
211 margin-bottom: 12px;
208 }
212 }
209
213
210 .form {
214 .form {
211 width: auto;
215 width: auto;
212 }
216 }
213
217
214 label, .field {
218 label, .field {
215 margin-bottom: 0;
219 margin-bottom: 0;
216 }
220 }
217
221
218 .label {
222 .label {
219 padding-top: 0;
223 padding-top: 0;
220 }
224 }
221
225
222 input {
226 input {
223 min-width: 215px;
227 min-width: 215px;
224 margin: 8px 0 @padding;
228 margin: 8px 0 @padding;
225 }
229 }
226
230
227 input[type="submit"] {
231 input[type="submit"] {
228 &:extend(.btn-primary);
232 &:extend(.btn-primary);
229 width:100%;
233 width:100%;
230 min-width: 0;
234 min-width: 0;
231 }
235 }
232
236
233 .forgot_password,
237 .forgot_password,
234 .buttons .register {
238 .buttons .register {
235 a {
239 a {
236 color: @rcblue;
240 color: @rcblue;
237
241
238 &:hover {
242 &:hover {
239 color: @rcdarkblue;
243 color: @rcdarkblue;
240 }
244 }
241 }
245 }
242 }
246 }
243
247
244 .buttons {
248 .buttons {
245 margin: 0;
249 margin: 0;
246 }
250 }
247
251
248 .buttons a {
252 .buttons a {
249 padding: 8px 0;
253 padding: 8px 0;
250 line-height: 1.4em;
254 line-height: 1.4em;
251 color: @grey4;
255 color: @grey4;
252
256
253 &:hover {
257 &:hover {
254 color: @grey2;
258 color: @grey2;
255 }
259 }
256 }
260 }
257
261
258 #sign_in {
262 #sign_in {
259 margin-bottom: 0
263 margin-bottom: 0
260 }
264 }
261
265
262 .big_gravatar {
266 .big_gravatar {
263 float: left;
267 float: left;
264 display: block;
268 display: block;
265 margin-top: .5em;
269 margin-top: .5em;
266 }
270 }
267
271
268 .full_name,
272 .full_name,
269 .email {
273 .email {
270 margin: 0 0 0 65px;
274 margin: 0 0 0 65px;
271 }
275 }
272
276
273 .email {
277 .email {
274 font-family: @text-light;
278 font-family: @text-light;
275 }
279 }
276
280
277 ol.links {
281 ol.links {
278 clear:both;
282 clear:both;
279 margin: 0;
283 margin: 0;
280 padding: @padding 0 0 0;
284 padding: @padding 0 0 0;
281
285
282 li {
286 li {
283 border-top: @border-thickness solid @grey5;
287 border-top: @border-thickness solid @grey5;
284
288
285 input {
289 input {
286 margin: @padding 0 0 0;
290 margin: @padding 0 0 0;
287 }
291 }
288 }
292 }
289 }
293 }
290 }
294 }
291 .submenu #quick_login li:hover {
295 .submenu #quick_login li:hover {
292 background-color: transparent;
296 background-color: transparent;
293 }
297 }
294
298
295 #quick_login_link:hover + #quick_login {
299 #quick_login_link:hover + #quick_login {
296 display: block;
300 display: block;
297 }
301 }
@@ -1,71 +1,81 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('System Wide Application Permissions')}</h3>
3 <h3 class="panel-title">${_('System Wide Application Permissions')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 ${h.secure_form(url('admin_permissions_application'), method='post')}
6 ${h.secure_form(url('admin_permissions_application'), method='post')}
7 <div class="form">
7 <div class="form">
8 <!-- fields -->
8 <!-- fields -->
9 <div class="fields">
9 <div class="fields">
10 <div class="field">
10 <div class="field">
11 <div class="label label-checkbox">
11 <div class="label label-checkbox">
12 <label for="anonymous">${_('Anonymous Access')}:</label>
12 <label for="anonymous">${_('Anonymous Access')}:</label>
13 </div>
13 </div>
14 <div class="checkboxes">
14 <div class="checkboxes">
15 <div class="checkbox">
15 <div class="checkbox">
16 ${h.checkbox('anonymous',True)} Allow Anonymous Access
16 ${h.checkbox('anonymous',True)} Allow Anonymous Access
17 </div>
17 </div>
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>
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 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field">
22 <div class="field">
23 <div class="label label-select">
23 <div class="label label-select">
24 <label for="default_register">${_('Registration')}:</label>
24 <label for="default_register">${_('Registration')}:</label>
25 </div>
25 </div>
26 <div class="select">
26 <div class="select">
27 ${h.select('default_register','',c.register_choices)}
27 ${h.select('default_register','',c.register_choices)}
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div class="field">
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 <div class="label label-textarea">
41 <div class="label label-textarea">
33 <label for="default_register_message">${_('Registration Page Message')}:</label>
42 <label for="default_register_message">${_('Registration Page Message')}:</label>
34 </div>
43 </div>
35 <div class="textarea text-area editor" >
44 <div class="textarea text-area editor" >
36 ${h.textarea('default_register_message', class_="medium", )}
45 ${h.textarea('default_register_message', class_="medium", )}
37 <span class="help-block">${_('Custom message to be displayed on the registration page. HTML syntax is supported.')}</span>
46 <span class="help-block">${_('Custom message to be displayed on the registration page. HTML syntax is supported.')}</span>
38 </div>
47 </div>
39 </div>
48 </div>
40
49
41 <div class="field">
50 <div class="field">
42 <div class="label">
51 <div class="label">
43 <label for="default_extern_activate">${_('External Authentication Account Activation')}:</label>
52 <label for="default_extern_activate">${_('External Authentication Account Activation')}:</label>
44 </div>
53 </div>
45 <div class="select">
54 <div class="select">
46 ${h.select('default_extern_activate','',c.extern_activate_choices)}
55 ${h.select('default_extern_activate','',c.extern_activate_choices)}
47 </div>
56 </div>
48 </div>
57 </div>
49 <div class="buttons">
58 <div class="buttons">
50 ${h.submit('save',_('Save'),class_="btn")}
59 ${h.submit('save',_('Save'),class_="btn")}
51 ${h.reset('reset',_('Reset'),class_="btn")}
60 ${h.reset('reset',_('Reset'),class_="btn")}
52 </div>
61 </div>
53 </div>
62 </div>
54 </div>
63 </div>
55 ${h.end_form()}
64 ${h.end_form()}
56 </div>
65 </div>
57 </div>
66 </div>
58
67
59 <script>
68 <script>
60 $(document).ready(function(){
69 $(document).ready(function(){
61 var select2Options = {
70 var select2Options = {
62 containerCssClass: 'drop-menu',
71 containerCssClass: 'drop-menu',
63 dropdownCssClass: 'drop-menu-dropdown',
72 dropdownCssClass: 'drop-menu-dropdown',
64 dropdownAutoWidth: true,
73 dropdownAutoWidth: true,
65 minimumResultsForSearch: -1
74 minimumResultsForSearch: -1
66 };
75 };
67
76
68 $("#default_register").select2(select2Options);
77 $("#default_register").select2(select2Options);
78 $("#default_password_reset").select2(select2Options);
69 $("#default_extern_activate").select2(select2Options);
79 $("#default_extern_activate").select2(select2Options);
70 });
80 });
71 </script>
81 </script>
@@ -1,651 +1,653 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26 <div class="main">
26 <div class="main">
27 ${next.main()}
27 ${next.main()}
28 </div>
28 </div>
29 </div>
29 </div>
30 <!-- END CONTENT -->
30 <!-- END CONTENT -->
31
31
32 </div>
32 </div>
33 <!-- FOOTER -->
33 <!-- FOOTER -->
34 <div id="footer">
34 <div id="footer">
35 <div id="footer-inner" class="title wrapper">
35 <div id="footer-inner" class="title wrapper">
36 <div>
36 <div>
37 <p class="footer-link-right">
37 <p class="footer-link-right">
38 % if c.visual.show_version:
38 % if c.visual.show_version:
39 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
39 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
40 % endif
40 % endif
41 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
41 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
42 % if c.visual.rhodecode_support_url:
42 % if c.visual.rhodecode_support_url:
43 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
43 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
44 % endif
44 % endif
45 </p>
45 </p>
46 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
46 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
47 <p class="server-instance" style="display:${sid}">
47 <p class="server-instance" style="display:${sid}">
48 ## display hidden instance ID if specially defined
48 ## display hidden instance ID if specially defined
49 % if c.rhodecode_instanceid:
49 % if c.rhodecode_instanceid:
50 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
50 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
51 % endif
51 % endif
52 </p>
52 </p>
53 </div>
53 </div>
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <!-- END FOOTER -->
57 <!-- END FOOTER -->
58
58
59 ### MAKO DEFS ###
59 ### MAKO DEFS ###
60
60
61 <%def name="menu_bar_subnav()">
61 <%def name="menu_bar_subnav()">
62 </%def>
62 </%def>
63
63
64 <%def name="breadcrumbs(class_='breadcrumbs')">
64 <%def name="breadcrumbs(class_='breadcrumbs')">
65 <div class="${class_}">
65 <div class="${class_}">
66 ${self.breadcrumbs_links()}
66 ${self.breadcrumbs_links()}
67 </div>
67 </div>
68 </%def>
68 </%def>
69
69
70 <%def name="admin_menu()">
70 <%def name="admin_menu()">
71 <ul class="admin_menu submenu">
71 <ul class="admin_menu submenu">
72 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
72 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
73 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
73 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
74 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
74 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
75 <li><a href="${h.url('users')}">${_('Users')}</a></li>
75 <li><a href="${h.url('users')}">${_('Users')}</a></li>
76 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
76 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
77 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
77 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
78 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
78 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
79 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
79 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
80 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
80 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
81 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
81 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
82 </ul>
82 </ul>
83 </%def>
83 </%def>
84
84
85
85
86 <%def name="dt_info_panel(elements)">
86 <%def name="dt_info_panel(elements)">
87 <dl class="dl-horizontal">
87 <dl class="dl-horizontal">
88 %for dt, dd, title, show_items in elements:
88 %for dt, dd, title, show_items in elements:
89 <dt>${dt}:</dt>
89 <dt>${dt}:</dt>
90 <dd title="${title}">
90 <dd title="${title}">
91 %if callable(dd):
91 %if callable(dd):
92 ## allow lazy evaluation of elements
92 ## allow lazy evaluation of elements
93 ${dd()}
93 ${dd()}
94 %else:
94 %else:
95 ${dd}
95 ${dd}
96 %endif
96 %endif
97 %if show_items:
97 %if show_items:
98 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
98 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
99 %endif
99 %endif
100 </dd>
100 </dd>
101
101
102 %if show_items:
102 %if show_items:
103 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
103 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
104 %for item in show_items:
104 %for item in show_items:
105 <dt></dt>
105 <dt></dt>
106 <dd>${item}</dd>
106 <dd>${item}</dd>
107 %endfor
107 %endfor
108 </div>
108 </div>
109 %endif
109 %endif
110
110
111 %endfor
111 %endfor
112 </dl>
112 </dl>
113 </%def>
113 </%def>
114
114
115
115
116 <%def name="gravatar(email, size=16)">
116 <%def name="gravatar(email, size=16)">
117 <%
117 <%
118 if (size > 16):
118 if (size > 16):
119 gravatar_class = 'gravatar gravatar-large'
119 gravatar_class = 'gravatar gravatar-large'
120 else:
120 else:
121 gravatar_class = 'gravatar'
121 gravatar_class = 'gravatar'
122 %>
122 %>
123 <%doc>
123 <%doc>
124 TODO: johbo: For now we serve double size images to make it smooth
124 TODO: johbo: For now we serve double size images to make it smooth
125 for retina. This is how it worked until now. Should be replaced
125 for retina. This is how it worked until now. Should be replaced
126 with a better solution at some point.
126 with a better solution at some point.
127 </%doc>
127 </%doc>
128 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
128 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
129 </%def>
129 </%def>
130
130
131
131
132 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
132 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
133 <% email = h.email_or_none(contact) %>
133 <% email = h.email_or_none(contact) %>
134 <div class="rc-user tooltip" title="${h.author_string(email)}">
134 <div class="rc-user tooltip" title="${h.author_string(email)}">
135 ${self.gravatar(email, size)}
135 ${self.gravatar(email, size)}
136 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
136 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
137 </div>
137 </div>
138 </%def>
138 </%def>
139
139
140
140
141 ## admin menu used for people that have some admin resources
141 ## admin menu used for people that have some admin resources
142 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
142 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
143 <ul class="submenu">
143 <ul class="submenu">
144 %if repositories:
144 %if repositories:
145 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
145 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
146 %endif
146 %endif
147 %if repository_groups:
147 %if repository_groups:
148 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
148 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
149 %endif
149 %endif
150 %if user_groups:
150 %if user_groups:
151 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
151 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
152 %endif
152 %endif
153 </ul>
153 </ul>
154 </%def>
154 </%def>
155
155
156 <%def name="repo_page_title(repo_instance)">
156 <%def name="repo_page_title(repo_instance)">
157 <div class="title-content">
157 <div class="title-content">
158 <div class="title-main">
158 <div class="title-main">
159 ## SVN/HG/GIT icons
159 ## SVN/HG/GIT icons
160 %if h.is_hg(repo_instance):
160 %if h.is_hg(repo_instance):
161 <i class="icon-hg"></i>
161 <i class="icon-hg"></i>
162 %endif
162 %endif
163 %if h.is_git(repo_instance):
163 %if h.is_git(repo_instance):
164 <i class="icon-git"></i>
164 <i class="icon-git"></i>
165 %endif
165 %endif
166 %if h.is_svn(repo_instance):
166 %if h.is_svn(repo_instance):
167 <i class="icon-svn"></i>
167 <i class="icon-svn"></i>
168 %endif
168 %endif
169
169
170 ## public/private
170 ## public/private
171 %if repo_instance.private:
171 %if repo_instance.private:
172 <i class="icon-repo-private"></i>
172 <i class="icon-repo-private"></i>
173 %else:
173 %else:
174 <i class="icon-repo-public"></i>
174 <i class="icon-repo-public"></i>
175 %endif
175 %endif
176
176
177 ## repo name with group name
177 ## repo name with group name
178 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
178 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
179
179
180 </div>
180 </div>
181
181
182 ## FORKED
182 ## FORKED
183 %if repo_instance.fork:
183 %if repo_instance.fork:
184 <p>
184 <p>
185 <i class="icon-code-fork"></i> ${_('Fork of')}
185 <i class="icon-code-fork"></i> ${_('Fork of')}
186 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
186 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
187 </p>
187 </p>
188 %endif
188 %endif
189
189
190 ## IMPORTED FROM REMOTE
190 ## IMPORTED FROM REMOTE
191 %if repo_instance.clone_uri:
191 %if repo_instance.clone_uri:
192 <p>
192 <p>
193 <i class="icon-code-fork"></i> ${_('Clone from')}
193 <i class="icon-code-fork"></i> ${_('Clone from')}
194 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
194 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
195 </p>
195 </p>
196 %endif
196 %endif
197
197
198 ## LOCKING STATUS
198 ## LOCKING STATUS
199 %if repo_instance.locked[0]:
199 %if repo_instance.locked[0]:
200 <p class="locking_locked">
200 <p class="locking_locked">
201 <i class="icon-repo-lock"></i>
201 <i class="icon-repo-lock"></i>
202 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
202 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
203 </p>
203 </p>
204 %elif repo_instance.enable_locking:
204 %elif repo_instance.enable_locking:
205 <p class="locking_unlocked">
205 <p class="locking_unlocked">
206 <i class="icon-repo-unlock"></i>
206 <i class="icon-repo-unlock"></i>
207 ${_('Repository not locked. Pull repository to lock it.')}
207 ${_('Repository not locked. Pull repository to lock it.')}
208 </p>
208 </p>
209 %endif
209 %endif
210
210
211 </div>
211 </div>
212 </%def>
212 </%def>
213
213
214 <%def name="repo_menu(active=None)">
214 <%def name="repo_menu(active=None)">
215 <%
215 <%
216 def is_active(selected):
216 def is_active(selected):
217 if selected == active:
217 if selected == active:
218 return "active"
218 return "active"
219 %>
219 %>
220
220
221 <!--- CONTEXT BAR -->
221 <!--- CONTEXT BAR -->
222 <div id="context-bar">
222 <div id="context-bar">
223 <div class="wrapper">
223 <div class="wrapper">
224 <ul id="context-pages" class="horizontal-list navigation">
224 <ul id="context-pages" class="horizontal-list navigation">
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>
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 <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>
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 <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>
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 <li class="${is_active('compare')}">
228 <li class="${is_active('compare')}">
229 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
229 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
230 </li>
230 </li>
231 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
231 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
232 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 <li class="${is_active('showpullrequest')}">
233 <li class="${is_active('showpullrequest')}">
234 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
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 %if c.repository_pull_requests:
235 %if c.repository_pull_requests:
236 <span class="pr_notifications">${c.repository_pull_requests}</span>
236 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 %endif
237 %endif
238 <div class="menulabel">${_('Pull Requests')}</div>
238 <div class="menulabel">${_('Pull Requests')}</div>
239 </a>
239 </a>
240 </li>
240 </li>
241 %endif
241 %endif
242 <li class="${is_active('options')}">
242 <li class="${is_active('options')}">
243 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
243 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
244 <ul class="submenu">
244 <ul class="submenu">
245 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
245 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
246 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
246 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
247 %endif
247 %endif
248 %if c.rhodecode_db_repo.fork:
248 %if c.rhodecode_db_repo.fork:
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)}">
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 ${_('Compare fork')}</a></li>
250 ${_('Compare fork')}</a></li>
251 %endif
251 %endif
252
252
253 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
253 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
254
254
255 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
255 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
256 %if c.rhodecode_db_repo.locked[0]:
256 %if c.rhodecode_db_repo.locked[0]:
257 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
257 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
258 %else:
258 %else:
259 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
259 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
260 %endif
260 %endif
261 %endif
261 %endif
262 %if c.rhodecode_user.username != h.DEFAULT_USER:
262 %if c.rhodecode_user.username != h.DEFAULT_USER:
263 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
263 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
264 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
264 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
265 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
265 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
266 %endif
266 %endif
267 %endif
267 %endif
268 </ul>
268 </ul>
269 </li>
269 </li>
270 </ul>
270 </ul>
271 </div>
271 </div>
272 <div class="clear"></div>
272 <div class="clear"></div>
273 </div>
273 </div>
274 <!--- END CONTEXT BAR -->
274 <!--- END CONTEXT BAR -->
275
275
276 </%def>
276 </%def>
277
277
278 <%def name="usermenu()">
278 <%def name="usermenu()">
279 ## USER MENU
279 ## USER MENU
280 <li id="quick_login_li">
280 <li id="quick_login_li">
281 <a id="quick_login_link" class="menulink childs">
281 <a id="quick_login_link" class="menulink childs">
282 ${gravatar(c.rhodecode_user.email, 20)}
282 ${gravatar(c.rhodecode_user.email, 20)}
283 <span class="user">
283 <span class="user">
284 %if c.rhodecode_user.username != h.DEFAULT_USER:
284 %if c.rhodecode_user.username != h.DEFAULT_USER:
285 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
285 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
286 %else:
286 %else:
287 <span>${_('Sign in')}</span>
287 <span>${_('Sign in')}</span>
288 %endif
288 %endif
289 </span>
289 </span>
290 </a>
290 </a>
291
291
292 <div class="user-menu submenu">
292 <div class="user-menu submenu">
293 <div id="quick_login">
293 <div id="quick_login">
294 %if c.rhodecode_user.username == h.DEFAULT_USER:
294 %if c.rhodecode_user.username == h.DEFAULT_USER:
295 <h4>${_('Sign in to your account')}</h4>
295 <h4>${_('Sign in to your account')}</h4>
296 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
296 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
297 <div class="form form-vertical">
297 <div class="form form-vertical">
298 <div class="fields">
298 <div class="fields">
299 <div class="field">
299 <div class="field">
300 <div class="label">
300 <div class="label">
301 <label for="username">${_('Username')}:</label>
301 <label for="username">${_('Username')}:</label>
302 </div>
302 </div>
303 <div class="input">
303 <div class="input">
304 ${h.text('username',class_='focus',tabindex=1)}
304 ${h.text('username',class_='focus',tabindex=1)}
305 </div>
305 </div>
306
306
307 </div>
307 </div>
308 <div class="field">
308 <div class="field">
309 <div class="label">
309 <div class="label">
310 <label for="password">${_('Password')}:</label>
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 </div>
314 </div>
313 <div class="input">
315 <div class="input">
314 ${h.password('password',class_='focus',tabindex=2)}
316 ${h.password('password',class_='focus',tabindex=2)}
315 </div>
317 </div>
316 </div>
318 </div>
317 <div class="buttons">
319 <div class="buttons">
318 <div class="register">
320 <div class="register">
319 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
321 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
320 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
322 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
321 %endif
323 %endif
322 </div>
324 </div>
323 <div class="submit">
325 <div class="submit">
324 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
326 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
325 </div>
327 </div>
326 </div>
328 </div>
327 </div>
329 </div>
328 </div>
330 </div>
329 ${h.end_form()}
331 ${h.end_form()}
330 %else:
332 %else:
331 <div class="">
333 <div class="">
332 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
334 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
333 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
335 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
334 <div class="email">${c.rhodecode_user.email}</div>
336 <div class="email">${c.rhodecode_user.email}</div>
335 </div>
337 </div>
336 <div class="">
338 <div class="">
337 <ol class="links">
339 <ol class="links">
338 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
340 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
339 <li class="logout">
341 <li class="logout">
340 ${h.secure_form(h.route_path('logout'))}
342 ${h.secure_form(h.route_path('logout'))}
341 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
343 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
342 ${h.end_form()}
344 ${h.end_form()}
343 </li>
345 </li>
344 </ol>
346 </ol>
345 </div>
347 </div>
346 %endif
348 %endif
347 </div>
349 </div>
348 </div>
350 </div>
349 %if c.rhodecode_user.username != h.DEFAULT_USER:
351 %if c.rhodecode_user.username != h.DEFAULT_USER:
350 <div class="pill_container">
352 <div class="pill_container">
351 % if c.unread_notifications == 0:
353 % if c.unread_notifications == 0:
352 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
354 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
353 % else:
355 % else:
354 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
356 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
355 % endif
357 % endif
356 </div>
358 </div>
357 % endif
359 % endif
358 </li>
360 </li>
359 </%def>
361 </%def>
360
362
361 <%def name="menu_items(active=None)">
363 <%def name="menu_items(active=None)">
362 <%
364 <%
363 def is_active(selected):
365 def is_active(selected):
364 if selected == active:
366 if selected == active:
365 return "active"
367 return "active"
366 return ""
368 return ""
367 %>
369 %>
368 <ul id="quick" class="main_nav navigation horizontal-list">
370 <ul id="quick" class="main_nav navigation horizontal-list">
369 <!-- repo switcher -->
371 <!-- repo switcher -->
370 <li class="${is_active('repositories')} repo_switcher_li has_select2">
372 <li class="${is_active('repositories')} repo_switcher_li has_select2">
371 <input id="repo_switcher" name="repo_switcher" type="hidden">
373 <input id="repo_switcher" name="repo_switcher" type="hidden">
372 </li>
374 </li>
373
375
374 ## ROOT MENU
376 ## ROOT MENU
375 %if c.rhodecode_user.username != h.DEFAULT_USER:
377 %if c.rhodecode_user.username != h.DEFAULT_USER:
376 <li class="${is_active('journal')}">
378 <li class="${is_active('journal')}">
377 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
379 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
378 <div class="menulabel">${_('Journal')}</div>
380 <div class="menulabel">${_('Journal')}</div>
379 </a>
381 </a>
380 </li>
382 </li>
381 %else:
383 %else:
382 <li class="${is_active('journal')}">
384 <li class="${is_active('journal')}">
383 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
385 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
384 <div class="menulabel">${_('Public journal')}</div>
386 <div class="menulabel">${_('Public journal')}</div>
385 </a>
387 </a>
386 </li>
388 </li>
387 %endif
389 %endif
388 <li class="${is_active('gists')}">
390 <li class="${is_active('gists')}">
389 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
391 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
390 <div class="menulabel">${_('Gists')}</div>
392 <div class="menulabel">${_('Gists')}</div>
391 </a>
393 </a>
392 </li>
394 </li>
393 <li class="${is_active('search')}">
395 <li class="${is_active('search')}">
394 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
396 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
395 <div class="menulabel">${_('Search')}</div>
397 <div class="menulabel">${_('Search')}</div>
396 </a>
398 </a>
397 </li>
399 </li>
398 % if h.HasPermissionAll('hg.admin')('access admin main page'):
400 % if h.HasPermissionAll('hg.admin')('access admin main page'):
399 <li class="${is_active('admin')}">
401 <li class="${is_active('admin')}">
400 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
402 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
401 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
403 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
402 </a>
404 </a>
403 ${admin_menu()}
405 ${admin_menu()}
404 </li>
406 </li>
405 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
407 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
406 <li class="${is_active('admin')}">
408 <li class="${is_active('admin')}">
407 <a class="menulink childs" title="${_('Delegated Admin settings')}">
409 <a class="menulink childs" title="${_('Delegated Admin settings')}">
408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 </a>
411 </a>
410 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
412 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
411 c.rhodecode_user.repository_groups_admin,
413 c.rhodecode_user.repository_groups_admin,
412 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
414 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
413 </li>
415 </li>
414 % endif
416 % endif
415 % if c.debug_style:
417 % if c.debug_style:
416 <li class="${is_active('debug_style')}">
418 <li class="${is_active('debug_style')}">
417 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
419 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
418 <div class="menulabel">${_('Style')}</div>
420 <div class="menulabel">${_('Style')}</div>
419 </a>
421 </a>
420 </li>
422 </li>
421 % endif
423 % endif
422 ## render extra user menu
424 ## render extra user menu
423 ${usermenu()}
425 ${usermenu()}
424 </ul>
426 </ul>
425
427
426 <script type="text/javascript">
428 <script type="text/javascript">
427 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
429 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
428
430
429 /*format the look of items in the list*/
431 /*format the look of items in the list*/
430 var format = function(state, escapeMarkup){
432 var format = function(state, escapeMarkup){
431 if (!state.id){
433 if (!state.id){
432 return state.text; // optgroup
434 return state.text; // optgroup
433 }
435 }
434 var obj_dict = state.obj;
436 var obj_dict = state.obj;
435 var tmpl = '';
437 var tmpl = '';
436
438
437 if(obj_dict && state.type == 'repo'){
439 if(obj_dict && state.type == 'repo'){
438 if(obj_dict['repo_type'] === 'hg'){
440 if(obj_dict['repo_type'] === 'hg'){
439 tmpl += '<i class="icon-hg"></i> ';
441 tmpl += '<i class="icon-hg"></i> ';
440 }
442 }
441 else if(obj_dict['repo_type'] === 'git'){
443 else if(obj_dict['repo_type'] === 'git'){
442 tmpl += '<i class="icon-git"></i> ';
444 tmpl += '<i class="icon-git"></i> ';
443 }
445 }
444 else if(obj_dict['repo_type'] === 'svn'){
446 else if(obj_dict['repo_type'] === 'svn'){
445 tmpl += '<i class="icon-svn"></i> ';
447 tmpl += '<i class="icon-svn"></i> ';
446 }
448 }
447 if(obj_dict['private']){
449 if(obj_dict['private']){
448 tmpl += '<i class="icon-lock" ></i> ';
450 tmpl += '<i class="icon-lock" ></i> ';
449 }
451 }
450 else if(visual_show_public_icon){
452 else if(visual_show_public_icon){
451 tmpl += '<i class="icon-unlock-alt"></i> ';
453 tmpl += '<i class="icon-unlock-alt"></i> ';
452 }
454 }
453 }
455 }
454 if(obj_dict && state.type == 'commit') {
456 if(obj_dict && state.type == 'commit') {
455 tmpl += '<i class="icon-tag"></i>';
457 tmpl += '<i class="icon-tag"></i>';
456 }
458 }
457 if(obj_dict && state.type == 'group'){
459 if(obj_dict && state.type == 'group'){
458 tmpl += '<i class="icon-folder-close"></i> ';
460 tmpl += '<i class="icon-folder-close"></i> ';
459 }
461 }
460 tmpl += escapeMarkup(state.text);
462 tmpl += escapeMarkup(state.text);
461 return tmpl;
463 return tmpl;
462 };
464 };
463
465
464 var formatResult = function(result, container, query, escapeMarkup) {
466 var formatResult = function(result, container, query, escapeMarkup) {
465 return format(result, escapeMarkup);
467 return format(result, escapeMarkup);
466 };
468 };
467
469
468 var formatSelection = function(data, container, escapeMarkup) {
470 var formatSelection = function(data, container, escapeMarkup) {
469 return format(data, escapeMarkup);
471 return format(data, escapeMarkup);
470 };
472 };
471
473
472 $("#repo_switcher").select2({
474 $("#repo_switcher").select2({
473 cachedDataSource: {},
475 cachedDataSource: {},
474 minimumInputLength: 2,
476 minimumInputLength: 2,
475 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
477 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
476 dropdownAutoWidth: true,
478 dropdownAutoWidth: true,
477 formatResult: formatResult,
479 formatResult: formatResult,
478 formatSelection: formatSelection,
480 formatSelection: formatSelection,
479 containerCssClass: "repo-switcher",
481 containerCssClass: "repo-switcher",
480 dropdownCssClass: "repo-switcher-dropdown",
482 dropdownCssClass: "repo-switcher-dropdown",
481 escapeMarkup: function(m){
483 escapeMarkup: function(m){
482 // don't escape our custom placeholder
484 // don't escape our custom placeholder
483 if(m.substr(0,23) == '<div class="menulabel">'){
485 if(m.substr(0,23) == '<div class="menulabel">'){
484 return m;
486 return m;
485 }
487 }
486
488
487 return Select2.util.escapeMarkup(m);
489 return Select2.util.escapeMarkup(m);
488 },
490 },
489 query: $.debounce(250, function(query){
491 query: $.debounce(250, function(query){
490 self = this;
492 self = this;
491 var cacheKey = query.term;
493 var cacheKey = query.term;
492 var cachedData = self.cachedDataSource[cacheKey];
494 var cachedData = self.cachedDataSource[cacheKey];
493
495
494 if (cachedData) {
496 if (cachedData) {
495 query.callback({results: cachedData.results});
497 query.callback({results: cachedData.results});
496 } else {
498 } else {
497 $.ajax({
499 $.ajax({
498 url: "${h.url('goto_switcher_data')}",
500 url: "${h.url('goto_switcher_data')}",
499 data: {'query': query.term},
501 data: {'query': query.term},
500 dataType: 'json',
502 dataType: 'json',
501 type: 'GET',
503 type: 'GET',
502 success: function(data) {
504 success: function(data) {
503 self.cachedDataSource[cacheKey] = data;
505 self.cachedDataSource[cacheKey] = data;
504 query.callback({results: data.results});
506 query.callback({results: data.results});
505 },
507 },
506 error: function(data, textStatus, errorThrown) {
508 error: function(data, textStatus, errorThrown) {
507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
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 $("#repo_switcher").on('select2-selecting', function(e){
516 $("#repo_switcher").on('select2-selecting', function(e){
515 e.preventDefault();
517 e.preventDefault();
516 window.location = e.choice.url;
518 window.location = e.choice.url;
517 });
519 });
518
520
519 ## Global mouse bindings ##
521 ## Global mouse bindings ##
520
522
521 // general help "?"
523 // general help "?"
522 Mousetrap.bind(['?'], function(e) {
524 Mousetrap.bind(['?'], function(e) {
523 $('#help_kb').modal({})
525 $('#help_kb').modal({})
524 });
526 });
525
527
526 // / open the quick filter
528 // / open the quick filter
527 Mousetrap.bind(['/'], function(e) {
529 Mousetrap.bind(['/'], function(e) {
528 $("#repo_switcher").select2("open");
530 $("#repo_switcher").select2("open");
529
531
530 // return false to prevent default browser behavior
532 // return false to prevent default browser behavior
531 // and stop event from bubbling
533 // and stop event from bubbling
532 return false;
534 return false;
533 });
535 });
534
536
535 // general nav g + action
537 // general nav g + action
536 Mousetrap.bind(['g h'], function(e) {
538 Mousetrap.bind(['g h'], function(e) {
537 window.location = pyroutes.url('home');
539 window.location = pyroutes.url('home');
538 });
540 });
539 Mousetrap.bind(['g g'], function(e) {
541 Mousetrap.bind(['g g'], function(e) {
540 window.location = pyroutes.url('gists', {'private':1});
542 window.location = pyroutes.url('gists', {'private':1});
541 });
543 });
542 Mousetrap.bind(['g G'], function(e) {
544 Mousetrap.bind(['g G'], function(e) {
543 window.location = pyroutes.url('gists', {'public':1});
545 window.location = pyroutes.url('gists', {'public':1});
544 });
546 });
545 Mousetrap.bind(['n g'], function(e) {
547 Mousetrap.bind(['n g'], function(e) {
546 window.location = pyroutes.url('new_gist');
548 window.location = pyroutes.url('new_gist');
547 });
549 });
548 Mousetrap.bind(['n r'], function(e) {
550 Mousetrap.bind(['n r'], function(e) {
549 window.location = pyroutes.url('new_repo');
551 window.location = pyroutes.url('new_repo');
550 });
552 });
551
553
552 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
554 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
553 // nav in repo context
555 // nav in repo context
554 Mousetrap.bind(['g s'], function(e) {
556 Mousetrap.bind(['g s'], function(e) {
555 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
557 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
556 });
558 });
557 Mousetrap.bind(['g c'], function(e) {
559 Mousetrap.bind(['g c'], function(e) {
558 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
560 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
559 });
561 });
560 Mousetrap.bind(['g F'], function(e) {
562 Mousetrap.bind(['g F'], function(e) {
561 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
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 Mousetrap.bind(['g f'], function(e) {
565 Mousetrap.bind(['g f'], function(e) {
564 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
566 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
565 });
567 });
566 Mousetrap.bind(['g p'], function(e) {
568 Mousetrap.bind(['g p'], function(e) {
567 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
569 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
568 });
570 });
569 Mousetrap.bind(['g o'], function(e) {
571 Mousetrap.bind(['g o'], function(e) {
570 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
572 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
571 });
573 });
572 Mousetrap.bind(['g O'], function(e) {
574 Mousetrap.bind(['g O'], function(e) {
573 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
575 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
574 });
576 });
575 % endif
577 % endif
576
578
577 </script>
579 </script>
578 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
580 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
579 </%def>
581 </%def>
580
582
581 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
583 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
582 <div class="modal-dialog">
584 <div class="modal-dialog">
583 <div class="modal-content">
585 <div class="modal-content">
584 <div class="modal-header">
586 <div class="modal-header">
585 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
587 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
586 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
588 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
587 </div>
589 </div>
588 <div class="modal-body">
590 <div class="modal-body">
589 <div class="block-left">
591 <div class="block-left">
590 <table class="keyboard-mappings">
592 <table class="keyboard-mappings">
591 <tbody>
593 <tbody>
592 <tr>
594 <tr>
593 <th></th>
595 <th></th>
594 <th>${_('Site-wide shortcuts')}</th>
596 <th>${_('Site-wide shortcuts')}</th>
595 </tr>
597 </tr>
596 <%
598 <%
597 elems = [
599 elems = [
598 ('/', 'Open quick search box'),
600 ('/', 'Open quick search box'),
599 ('g h', 'Goto home page'),
601 ('g h', 'Goto home page'),
600 ('g g', 'Goto my private gists page'),
602 ('g g', 'Goto my private gists page'),
601 ('g G', 'Goto my public gists page'),
603 ('g G', 'Goto my public gists page'),
602 ('n r', 'New repository page'),
604 ('n r', 'New repository page'),
603 ('n g', 'New gist page'),
605 ('n g', 'New gist page'),
604 ]
606 ]
605 %>
607 %>
606 %for key, desc in elems:
608 %for key, desc in elems:
607 <tr>
609 <tr>
608 <td class="keys">
610 <td class="keys">
609 <span class="key tag">${key}</span>
611 <span class="key tag">${key}</span>
610 </td>
612 </td>
611 <td>${desc}</td>
613 <td>${desc}</td>
612 </tr>
614 </tr>
613 %endfor
615 %endfor
614 </tbody>
616 </tbody>
615 </table>
617 </table>
616 </div>
618 </div>
617 <div class="block-left">
619 <div class="block-left">
618 <table class="keyboard-mappings">
620 <table class="keyboard-mappings">
619 <tbody>
621 <tbody>
620 <tr>
622 <tr>
621 <th></th>
623 <th></th>
622 <th>${_('Repositories')}</th>
624 <th>${_('Repositories')}</th>
623 </tr>
625 </tr>
624 <%
626 <%
625 elems = [
627 elems = [
626 ('g s', 'Goto summary page'),
628 ('g s', 'Goto summary page'),
627 ('g c', 'Goto changelog page'),
629 ('g c', 'Goto changelog page'),
628 ('g f', 'Goto files page'),
630 ('g f', 'Goto files page'),
629 ('g F', 'Goto files page with file search activated'),
631 ('g F', 'Goto files page with file search activated'),
630 ('g p', 'Goto pull requests page'),
632 ('g p', 'Goto pull requests page'),
631 ('g o', 'Goto repository settings'),
633 ('g o', 'Goto repository settings'),
632 ('g O', 'Goto repository permissions settings'),
634 ('g O', 'Goto repository permissions settings'),
633 ]
635 ]
634 %>
636 %>
635 %for key, desc in elems:
637 %for key, desc in elems:
636 <tr>
638 <tr>
637 <td class="keys">
639 <td class="keys">
638 <span class="key tag">${key}</span>
640 <span class="key tag">${key}</span>
639 </td>
641 </td>
640 <td>${desc}</td>
642 <td>${desc}</td>
641 </tr>
643 </tr>
642 %endfor
644 %endfor
643 </tbody>
645 </tbody>
644 </table>
646 </table>
645 </div>
647 </div>
646 </div>
648 </div>
647 <div class="modal-footer">
649 <div class="modal-footer">
648 </div>
650 </div>
649 </div><!-- /.modal-content -->
651 </div><!-- /.modal-content -->
650 </div><!-- /.modal-dialog -->
652 </div><!-- /.modal-dialog -->
651 </div><!-- /.modal -->
653 </div><!-- /.modal -->
@@ -1,76 +1,84 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/root.html"/>
2 <%inherit file="base/root.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Sign In')}
5 ${_('Sign In')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <style>body{background-color:#eeeeee;}</style>
11 <style>body{background-color:#eeeeee;}</style>
12 <div class="loginbox">
12 <div class="loginbox">
13 <div class="header">
13 <div class="header">
14 <div id="header-inner" class="title">
14 <div id="header-inner" class="title">
15 <div id="logo">
15 <div id="logo">
16 <div class="logo-wrapper">
16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 </div>
18 </div>
19 %if c.rhodecode_name:
19 %if c.rhodecode_name:
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
21 %endif
21 %endif
22 </div>
22 </div>
23 </div>
23 </div>
24 </div>
24 </div>
25
25
26 <div class="loginwrapper">
26 <div class="loginwrapper">
27 <div class="left-column">
27 <div class="left-column">
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 </div>
29 </div>
30 <%block name="above_login_button" />
30 <%block name="above_login_button" />
31 <div id="login" class="right-column">
31 <div id="login" class="right-column">
32 <!-- login -->
32 <!-- login -->
33 <div class="sign-in-title">
33 <div class="sign-in-title">
34 <h1>${_('Sign In')}</h1>
34 <h1>${_('Sign In')}</h1>
35 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
35 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
36 <h4>${h.link_to(_("Go to the registration page to create a new account."), request.route_path('register'))}</h4>
36 <h4>${h.link_to(_("Go to the registration page to create a new account."), request.route_path('register'))}</h4>
37 %endif
37 %endif
38 </div>
38 </div>
39 <div class="inner form">
39 <div class="inner form">
40 ${h.form(request.route_path('login', _query={'came_from': came_from}), needs_csrf_token=False)}
40 ${h.form(request.route_path('login', _query={'came_from': came_from}), needs_csrf_token=False)}
41
41
42 <label for="username">${_('Username')}:</label>
42 <label for="username">${_('Username')}:</label>
43 ${h.text('username', class_='focus', value=defaults.get('username'))}
43 ${h.text('username', class_='focus', value=defaults.get('username'))}
44 %if 'username' in errors:
44 %if 'username' in errors:
45 <span class="error-message">${errors.get('username')}</span>
45 <span class="error-message">${errors.get('username')}</span>
46 <br />
46 <br />
47 %endif
47 %endif
48
48
49 <label for="password">${_('Password')}:</label>
49 <label for="password">${_('Password')}:</label>
50 ${h.password('password', class_='focus')}
50 ${h.password('password', class_='focus')}
51 %if 'password' in errors:
51 %if 'password' in errors:
52 <span class="error-message">${errors.get('password')}</span>
52 <span class="error-message">${errors.get('password')}</span>
53 <br />
53 <br />
54 %endif
54 %endif
55
55
56 ${h.checkbox('remember', value=True, checked=defaults.get('remember'))}
56 ${h.checkbox('remember', value=True, checked=defaults.get('remember'))}
57 <label class="checkbox" for="remember">${_('Remember me')}</label>
57 <label class="checkbox" for="remember">${_('Remember me')}</label>
58
58
59 <p class="links">
59
60 ${h.link_to(_('Forgot your password?'), h.route_path('reset_password'))}
60 %if h.HasPermissionAny('hg.password_reset.enable')():
61 </p>
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 ${h.submit('sign_in', _('Sign In'), class_="btn sign-in")}
71 ${h.submit('sign_in', _('Sign In'), class_="btn sign-in")}
64
72
65 ${h.end_form()}
73 ${h.end_form()}
66 <script type="text/javascript">
74 <script type="text/javascript">
67 $(document).ready(function(){
75 $(document).ready(function(){
68 $('#username').focus();
76 $('#username').focus();
69 })
77 })
70 </script>
78 </script>
71 </div>
79 </div>
72 <!-- end login -->
80 <!-- end login -->
73 <%block name="below_login_button" />
81 <%block name="below_login_button" />
74 </div>
82 </div>
75 </div>
83 </div>
76 </div>
84 </div>
@@ -1,77 +1,83 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/root.html"/>
2 <%inherit file="base/root.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Create an Account')}
5 ${_('Create an Account')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10 <style>body{background-color:#eeeeee;}</style>
10 <style>body{background-color:#eeeeee;}</style>
11
11
12 <div class="loginbox">
12 <div class="loginbox">
13 <div class="header">
13 <div class="header">
14 <div id="header-inner" class="title">
14 <div id="header-inner" class="title">
15 <div id="logo">
15 <div id="logo">
16 <div class="logo-wrapper">
16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 </div>
18 </div>
19 %if c.rhodecode_name:
19 %if c.rhodecode_name:
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
21 %endif
21 %endif
22 </div>
22 </div>
23 </div>
23 </div>
24 </div>
24 </div>
25
25
26 <div class="loginwrapper">
26 <div class="loginwrapper">
27 <div class="left-column">
27 <div class="left-column">
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 </div>
29 </div>
30
30
31 <div id="register" class="right-column">
31 %if h.HasPermissionAny('hg.password_reset.disabled')():
32 <!-- login -->
32 <div class="right-column">
33 <div class="sign-in-title">
33 <p>${_('Password reset has been disabled.')}</p>
34 <h1>${_('Reset your Password')}</h1>
35 <h4>${h.link_to(_("Go to the login page to sign in."), request.route_path('login'))}</h4>
36 </div>
34 </div>
37 <div class="inner form">
35 %else:
38 ${h.form(request.route_path('reset_password'), needs_csrf_token=False)}
36 <div id="register" class="right-column">
39 <label for="email">${_('Email Address')}:</label>
37 <!-- login -->
40 ${h.text('email', defaults.get('email'))}
38 <div class="sign-in-title">
41 %if 'email' in errors:
39 <h1>${_('Reset your Password')}</h1>
42 <span class="error-message">${errors.get('email')}</span>
40 <h4>${h.link_to(_("Go to the login page to sign in."), request.route_path('login'))}</h4>
43 <br />
41 </div>
44 %endif
42 <div class="inner form">
45
43 ${h.form(request.route_path('reset_password'), needs_csrf_token=False)}
46 %if captcha_active:
44 <label for="email">${_('Email Address')}:</label>
47 <div class="login-captcha"
45 ${h.text('email', defaults.get('email'))}
48 <label for="email">${_('Captcha')}:</label>
46 %if 'email' in errors:
49 ${h.hidden('recaptcha_field')}
47 <span class="error-message">${errors.get('email')}</span>
50 <div id="recaptcha"></div>
51 %if 'recaptcha_field' in errors:
52 <span class="error-message">${errors.get('recaptcha_field')}</span>
53 <br />
48 <br />
54 %endif
49 %endif
55 </div>
50
56 %endif
51 %if captcha_active:
57
52 <div class="login-captcha"
58 ${h.submit('send', _('Send password reset email'), class_="btn sign-in")}
53 <label for="email">${_('Captcha')}:</label>
59 <div class="activation_msg">${_('Password reset link will be sent to matching email address')}</div>
54 ${h.hidden('recaptcha_field')}
60
55 <div id="recaptcha"></div>
61 ${h.end_form()}
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 </div>
68 </div>
63 </div>
69 %endif
64 </div>
70 </div>
65 </div>
71 </div>
66
72
67 %if captcha_active:
73 %if captcha_active:
68 <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
74 <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
69 %endif
75 %endif
70 <script type="text/javascript">
76 <script type="text/javascript">
71 $(document).ready(function(){
77 $(document).ready(function(){
72 $('#email').focus();
78 $('#email').focus();
73 %if captcha_active:
79 %if captcha_active:
74 Recaptcha.create("${captcha_public_key}", "recaptcha", {theme: "white"});
80 Recaptcha.create("${captcha_public_key}", "recaptcha", {theme: "white"});
75 %endif
81 %endif
76 });
82 });
77 </script>
83 </script>
General Comments 0
You need to be logged in to leave comments. Login now