Show More
@@ -1,1029 +1,1029 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | rhodecode.lib.auth |
|
4 | 4 | ~~~~~~~~~~~~~~~~~~ |
|
5 | 5 | |
|
6 | 6 | authentication and permission libraries |
|
7 | 7 | |
|
8 | 8 | :created_on: Apr 4, 2010 |
|
9 | 9 | :author: marcink |
|
10 | 10 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> |
|
11 | 11 | :license: GPLv3, see COPYING for more details. |
|
12 | 12 | """ |
|
13 | 13 | # This program is free software: you can redistribute it and/or modify |
|
14 | 14 | # it under the terms of the GNU General Public License as published by |
|
15 | 15 | # the Free Software Foundation, either version 3 of the License, or |
|
16 | 16 | # (at your option) any later version. |
|
17 | 17 | # |
|
18 | 18 | # This program is distributed in the hope that it will be useful, |
|
19 | 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
20 | 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
21 | 21 | # GNU General Public License for more details. |
|
22 | 22 | # |
|
23 | 23 | # You should have received a copy of the GNU General Public License |
|
24 | 24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 | 25 | |
|
26 | 26 | import random |
|
27 | 27 | import logging |
|
28 | 28 | import traceback |
|
29 | 29 | import hashlib |
|
30 | 30 | |
|
31 | 31 | from tempfile import _RandomNameSequence |
|
32 | 32 | from decorator import decorator |
|
33 | 33 | |
|
34 | 34 | from pylons import config, url, request |
|
35 | 35 | from pylons.controllers.util import abort, redirect |
|
36 | 36 | from pylons.i18n.translation import _ |
|
37 | 37 | from sqlalchemy.orm.exc import ObjectDeletedError |
|
38 | 38 | |
|
39 | 39 | from rhodecode import __platform__, is_windows, is_unix |
|
40 | 40 | from rhodecode.model.meta import Session |
|
41 | 41 | |
|
42 | 42 | from rhodecode.lib.utils2 import str2bool, safe_unicode |
|
43 | 43 | from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError |
|
44 | 44 | from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug |
|
45 | 45 | from rhodecode.lib.auth_ldap import AuthLdap |
|
46 | 46 | |
|
47 | 47 | from rhodecode.model import meta |
|
48 | 48 | from rhodecode.model.user import UserModel |
|
49 | 49 | from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap |
|
50 | 50 | from rhodecode.lib.caching_query import FromCache |
|
51 | 51 | |
|
52 | 52 | log = logging.getLogger(__name__) |
|
53 | 53 | |
|
54 | 54 | |
|
55 | 55 | class PasswordGenerator(object): |
|
56 | 56 | """ |
|
57 | 57 | This is a simple class for generating password from different sets of |
|
58 | 58 | characters |
|
59 | 59 | usage:: |
|
60 | 60 | |
|
61 | 61 | passwd_gen = PasswordGenerator() |
|
62 | 62 | #print 8-letter password containing only big and small letters |
|
63 | 63 | of alphabet |
|
64 | 64 | passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL) |
|
65 | 65 | """ |
|
66 | 66 | ALPHABETS_NUM = r'''1234567890''' |
|
67 | 67 | ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm''' |
|
68 | 68 | ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM''' |
|
69 | 69 | ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' |
|
70 | 70 | ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \ |
|
71 | 71 | + ALPHABETS_NUM + ALPHABETS_SPECIAL |
|
72 | 72 | ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM |
|
73 | 73 | ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL |
|
74 | 74 | ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM |
|
75 | 75 | ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM |
|
76 | 76 | |
|
77 | 77 | def __init__(self, passwd=''): |
|
78 | 78 | self.passwd = passwd |
|
79 | 79 | |
|
80 | 80 | def gen_password(self, length, type_=None): |
|
81 | 81 | if type_ is None: |
|
82 | 82 | type_ = self.ALPHABETS_FULL |
|
83 | 83 | self.passwd = ''.join([random.choice(type_) for _ in xrange(length)]) |
|
84 | 84 | return self.passwd |
|
85 | 85 | |
|
86 | 86 | |
|
87 | 87 | class RhodeCodeCrypto(object): |
|
88 | 88 | |
|
89 | 89 | @classmethod |
|
90 | 90 | def hash_string(cls, str_): |
|
91 | 91 | """ |
|
92 | 92 | Cryptographic function used for password hashing based on pybcrypt |
|
93 | 93 | or pycrypto in windows |
|
94 | 94 | |
|
95 | 95 | :param password: password to hash |
|
96 | 96 | """ |
|
97 | 97 | if is_windows: |
|
98 | 98 | from hashlib import sha256 |
|
99 | 99 | return sha256(str_).hexdigest() |
|
100 | 100 | elif is_unix: |
|
101 | 101 | import bcrypt |
|
102 | 102 | return bcrypt.hashpw(str_, bcrypt.gensalt(10)) |
|
103 | 103 | else: |
|
104 | 104 | raise Exception('Unknown or unsupported platform %s' \ |
|
105 | 105 | % __platform__) |
|
106 | 106 | |
|
107 | 107 | @classmethod |
|
108 | 108 | def hash_check(cls, password, hashed): |
|
109 | 109 | """ |
|
110 | 110 | Checks matching password with it's hashed value, runs different |
|
111 | 111 | implementation based on platform it runs on |
|
112 | 112 | |
|
113 | 113 | :param password: password |
|
114 | 114 | :param hashed: password in hashed form |
|
115 | 115 | """ |
|
116 | 116 | |
|
117 | 117 | if is_windows: |
|
118 | 118 | from hashlib import sha256 |
|
119 | 119 | return sha256(password).hexdigest() == hashed |
|
120 | 120 | elif is_unix: |
|
121 | 121 | import bcrypt |
|
122 | 122 | return bcrypt.hashpw(password, hashed) == hashed |
|
123 | 123 | else: |
|
124 | 124 | raise Exception('Unknown or unsupported platform %s' \ |
|
125 | 125 | % __platform__) |
|
126 | 126 | |
|
127 | 127 | |
|
128 | 128 | def get_crypt_password(password): |
|
129 | 129 | return RhodeCodeCrypto.hash_string(password) |
|
130 | 130 | |
|
131 | 131 | |
|
132 | 132 | def check_password(password, hashed): |
|
133 | 133 | return RhodeCodeCrypto.hash_check(password, hashed) |
|
134 | 134 | |
|
135 | 135 | |
|
136 | 136 | def generate_api_key(str_, salt=None): |
|
137 | 137 | """ |
|
138 | 138 | Generates API KEY from given string |
|
139 | 139 | |
|
140 | 140 | :param str_: |
|
141 | 141 | :param salt: |
|
142 | 142 | """ |
|
143 | 143 | |
|
144 | 144 | if salt is None: |
|
145 | 145 | salt = _RandomNameSequence().next() |
|
146 | 146 | |
|
147 | 147 | return hashlib.sha1(str_ + salt).hexdigest() |
|
148 | 148 | |
|
149 | 149 | |
|
150 | 150 | def authfunc(environ, username, password): |
|
151 | 151 | """ |
|
152 | 152 | Dummy authentication wrapper function used in Mercurial and Git for |
|
153 | 153 | access control. |
|
154 | 154 | |
|
155 | 155 | :param environ: needed only for using in Basic auth |
|
156 | 156 | """ |
|
157 | 157 | return authenticate(username, password) |
|
158 | 158 | |
|
159 | 159 | |
|
160 | 160 | def authenticate(username, password): |
|
161 | 161 | """ |
|
162 | 162 | Authentication function used for access control, |
|
163 | 163 | firstly checks for db authentication then if ldap is enabled for ldap |
|
164 | 164 | authentication, also creates ldap user if not in database |
|
165 | 165 | |
|
166 | 166 | :param username: username |
|
167 | 167 | :param password: password |
|
168 | 168 | """ |
|
169 | 169 | |
|
170 | 170 | user_model = UserModel() |
|
171 | 171 | user = User.get_by_username(username) |
|
172 | 172 | |
|
173 | 173 | log.debug('Authenticating user using RhodeCode account') |
|
174 | 174 | if user is not None and not user.ldap_dn: |
|
175 | 175 | if user.active: |
|
176 | 176 | if user.username == 'default' and user.active: |
|
177 | 177 | log.info('user %s authenticated correctly as anonymous user' % |
|
178 | 178 | username) |
|
179 | 179 | return True |
|
180 | 180 | |
|
181 | 181 | elif user.username == username and check_password(password, |
|
182 | 182 | user.password): |
|
183 | 183 | log.info('user %s authenticated correctly' % username) |
|
184 | 184 | return True |
|
185 | 185 | else: |
|
186 | 186 | log.warning('user %s tried auth but is disabled' % username) |
|
187 | 187 | |
|
188 | 188 | else: |
|
189 | 189 | log.debug('Regular authentication failed') |
|
190 | 190 | user_obj = User.get_by_username(username, case_insensitive=True) |
|
191 | 191 | |
|
192 | 192 | if user_obj is not None and not user_obj.ldap_dn: |
|
193 | 193 | log.debug('this user already exists as non ldap') |
|
194 | 194 | return False |
|
195 | 195 | |
|
196 | 196 | ldap_settings = RhodeCodeSetting.get_ldap_settings() |
|
197 | 197 | #====================================================================== |
|
198 | 198 | # FALLBACK TO LDAP AUTH IF ENABLE |
|
199 | 199 | #====================================================================== |
|
200 | 200 | if str2bool(ldap_settings.get('ldap_active')): |
|
201 | 201 | log.debug("Authenticating user using ldap") |
|
202 | 202 | kwargs = { |
|
203 | 203 | 'server': ldap_settings.get('ldap_host', ''), |
|
204 | 204 | 'base_dn': ldap_settings.get('ldap_base_dn', ''), |
|
205 | 205 | 'port': ldap_settings.get('ldap_port'), |
|
206 | 206 | 'bind_dn': ldap_settings.get('ldap_dn_user'), |
|
207 | 207 | 'bind_pass': ldap_settings.get('ldap_dn_pass'), |
|
208 | 208 | 'tls_kind': ldap_settings.get('ldap_tls_kind'), |
|
209 | 209 | 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'), |
|
210 | 210 | 'ldap_filter': ldap_settings.get('ldap_filter'), |
|
211 | 211 | 'search_scope': ldap_settings.get('ldap_search_scope'), |
|
212 | 212 | 'attr_login': ldap_settings.get('ldap_attr_login'), |
|
213 | 213 | 'ldap_version': 3, |
|
214 | 214 | } |
|
215 | 215 | log.debug('Checking for ldap authentication') |
|
216 | 216 | try: |
|
217 | 217 | aldap = AuthLdap(**kwargs) |
|
218 | 218 | (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, |
|
219 | 219 | password) |
|
220 | 220 | log.debug('Got ldap DN response %s' % user_dn) |
|
221 | 221 | |
|
222 | 222 | get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\ |
|
223 | 223 | .get(k), [''])[0] |
|
224 | 224 | |
|
225 | 225 | user_attrs = { |
|
226 | 226 | 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')), |
|
227 | 227 | 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')), |
|
228 | 228 | 'email': get_ldap_attr('ldap_attr_email'), |
|
229 | 229 | 'active': 'hg.register.auto_activate' in User\ |
|
230 | 230 | .get_by_username('default').AuthUser.permissions['global'] |
|
231 | 231 | } |
|
232 | 232 | |
|
233 | 233 | # don't store LDAP password since we don't need it. Override |
|
234 | 234 | # with some random generated password |
|
235 | 235 | _password = PasswordGenerator().gen_password(length=8) |
|
236 | 236 | # create this user on the fly if it doesn't exist in rhodecode |
|
237 | 237 | # database |
|
238 | 238 | if user_model.create_ldap(username, _password, user_dn, |
|
239 | 239 | user_attrs): |
|
240 | 240 | log.info('created new ldap user %s' % username) |
|
241 | 241 | |
|
242 | 242 | Session().commit() |
|
243 | 243 | return True |
|
244 | 244 | except (LdapUsernameError, LdapPasswordError,): |
|
245 | 245 | pass |
|
246 | 246 | except (Exception,): |
|
247 | 247 | log.error(traceback.format_exc()) |
|
248 | 248 | pass |
|
249 | 249 | return False |
|
250 | 250 | |
|
251 | 251 | |
|
252 | 252 | def login_container_auth(username): |
|
253 | 253 | user = User.get_by_username(username) |
|
254 | 254 | if user is None: |
|
255 | 255 | user_attrs = { |
|
256 | 256 | 'name': username, |
|
257 | 257 | 'lastname': None, |
|
258 | 258 | 'email': None, |
|
259 | 259 | 'active': 'hg.register.auto_activate' in User\ |
|
260 | 260 | .get_by_username('default').AuthUser.permissions['global'] |
|
261 | 261 | } |
|
262 | 262 | user = UserModel().create_for_container_auth(username, user_attrs) |
|
263 | 263 | if not user: |
|
264 | 264 | return None |
|
265 | 265 | log.info('User %s was created by container authentication' % username) |
|
266 | 266 | |
|
267 | 267 | if not user.active: |
|
268 | 268 | return None |
|
269 | 269 | |
|
270 | 270 | user.update_lastlogin() |
|
271 | 271 | Session().commit() |
|
272 | 272 | |
|
273 | 273 | log.debug('User %s is now logged in by container authentication', |
|
274 | 274 | user.username) |
|
275 | 275 | return user |
|
276 | 276 | |
|
277 | 277 | |
|
278 | 278 | def get_container_username(environ, config, clean_username=False): |
|
279 | 279 | """ |
|
280 | 280 | Get's the container_auth username (or email). It tries to get username |
|
281 | 281 | from REMOTE_USER if container_auth_enabled is enabled, if that fails |
|
282 | 282 | it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled |
|
283 | 283 | is enabled. clean_username extracts the username from this data if it's |
|
284 | 284 | having @ in it. |
|
285 | 285 | |
|
286 | 286 | :param environ: |
|
287 | 287 | :param config: |
|
288 | 288 | :param clean_username: |
|
289 | 289 | """ |
|
290 | 290 | username = None |
|
291 | 291 | |
|
292 | 292 | if str2bool(config.get('container_auth_enabled', False)): |
|
293 | 293 | from paste.httpheaders import REMOTE_USER |
|
294 | 294 | username = REMOTE_USER(environ) |
|
295 | 295 | log.debug('extracted REMOTE_USER:%s' % (username)) |
|
296 | 296 | |
|
297 | 297 | if not username and str2bool(config.get('proxypass_auth_enabled', False)): |
|
298 | 298 | username = environ.get('HTTP_X_FORWARDED_USER') |
|
299 | 299 | log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username)) |
|
300 | 300 | |
|
301 | 301 | if username and clean_username: |
|
302 | 302 | # Removing realm and domain from username |
|
303 | 303 | username = username.partition('@')[0] |
|
304 | 304 | username = username.rpartition('\\')[2] |
|
305 | 305 | log.debug('Received username %s from container' % username) |
|
306 | 306 | |
|
307 | 307 | return username |
|
308 | 308 | |
|
309 | 309 | |
|
310 | 310 | class CookieStoreWrapper(object): |
|
311 | 311 | |
|
312 | 312 | def __init__(self, cookie_store): |
|
313 | 313 | self.cookie_store = cookie_store |
|
314 | 314 | |
|
315 | 315 | def __repr__(self): |
|
316 | 316 | return 'CookieStore<%s>' % (self.cookie_store) |
|
317 | 317 | |
|
318 | 318 | def get(self, key, other=None): |
|
319 | 319 | if isinstance(self.cookie_store, dict): |
|
320 | 320 | return self.cookie_store.get(key, other) |
|
321 | 321 | elif isinstance(self.cookie_store, AuthUser): |
|
322 | 322 | return self.cookie_store.__dict__.get(key, other) |
|
323 | 323 | |
|
324 | 324 | |
|
325 | 325 | class AuthUser(object): |
|
326 | 326 | """ |
|
327 | 327 | A simple object that handles all attributes of user in RhodeCode |
|
328 | 328 | |
|
329 | 329 | It does lookup based on API key,given user, or user present in session |
|
330 | 330 | Then it fills all required information for such user. It also checks if |
|
331 | 331 | anonymous access is enabled and if so, it returns default user as logged |
|
332 | 332 | in |
|
333 | 333 | """ |
|
334 | 334 | |
|
335 | 335 | def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None): |
|
336 | 336 | |
|
337 | 337 | self.user_id = user_id |
|
338 | 338 | self.api_key = None |
|
339 | 339 | self.username = username |
|
340 | 340 | self.ip_addr = ip_addr |
|
341 | 341 | |
|
342 | 342 | self.name = '' |
|
343 | 343 | self.lastname = '' |
|
344 | 344 | self.email = '' |
|
345 | 345 | self.is_authenticated = False |
|
346 | 346 | self.admin = False |
|
347 | 347 | self.inherit_default_permissions = False |
|
348 | 348 | self.permissions = {} |
|
349 | 349 | self._api_key = api_key |
|
350 | 350 | self.propagate_data() |
|
351 | 351 | self._instance = None |
|
352 | 352 | |
|
353 | 353 | def propagate_data(self): |
|
354 | 354 | user_model = UserModel() |
|
355 | 355 | self.anonymous_user = User.get_by_username('default', cache=True) |
|
356 | 356 | is_user_loaded = False |
|
357 | 357 | |
|
358 | 358 | # try go get user by api key |
|
359 | 359 | if self._api_key and self._api_key != self.anonymous_user.api_key: |
|
360 | 360 | log.debug('Auth User lookup by API KEY %s' % self._api_key) |
|
361 | 361 | is_user_loaded = user_model.fill_data(self, api_key=self._api_key) |
|
362 | 362 | # lookup by userid |
|
363 | 363 | elif (self.user_id is not None and |
|
364 | 364 | self.user_id != self.anonymous_user.user_id): |
|
365 | 365 | log.debug('Auth User lookup by USER ID %s' % self.user_id) |
|
366 | 366 | is_user_loaded = user_model.fill_data(self, user_id=self.user_id) |
|
367 | 367 | # lookup by username |
|
368 | 368 | elif self.username and \ |
|
369 | 369 | str2bool(config.get('container_auth_enabled', False)): |
|
370 | 370 | |
|
371 | 371 | log.debug('Auth User lookup by USER NAME %s' % self.username) |
|
372 | 372 | dbuser = login_container_auth(self.username) |
|
373 | 373 | if dbuser is not None: |
|
374 | 374 | log.debug('filling all attributes to object') |
|
375 | 375 | for k, v in dbuser.get_dict().items(): |
|
376 | 376 | setattr(self, k, v) |
|
377 | 377 | self.set_authenticated() |
|
378 | 378 | is_user_loaded = True |
|
379 | 379 | else: |
|
380 | 380 | log.debug('No data in %s that could been used to log in' % self) |
|
381 | 381 | |
|
382 | 382 | if not is_user_loaded: |
|
383 | 383 | # if we cannot authenticate user try anonymous |
|
384 | 384 | if self.anonymous_user.active is True: |
|
385 | 385 | user_model.fill_data(self, user_id=self.anonymous_user.user_id) |
|
386 | 386 | # then we set this user is logged in |
|
387 | 387 | self.is_authenticated = True |
|
388 | 388 | else: |
|
389 | 389 | self.user_id = None |
|
390 | 390 | self.username = None |
|
391 | 391 | self.is_authenticated = False |
|
392 | 392 | |
|
393 | 393 | if not self.username: |
|
394 | 394 | self.username = 'None' |
|
395 | 395 | |
|
396 | 396 | log.debug('Auth User is now %s' % self) |
|
397 | 397 | user_model.fill_perms(self) |
|
398 | 398 | |
|
399 | 399 | @property |
|
400 | 400 | def is_admin(self): |
|
401 | 401 | return self.admin |
|
402 | 402 | |
|
403 | 403 | @property |
|
404 | 404 | def repos_admin(self): |
|
405 | 405 | """ |
|
406 | 406 | Returns list of repositories you're an admin of |
|
407 | 407 | """ |
|
408 | 408 | return [x[0] for x in self.permissions['repositories'].iteritems() |
|
409 | 409 | if x[1] == 'repository.admin'] |
|
410 | 410 | |
|
411 | 411 | @property |
|
412 | 412 | def groups_admin(self): |
|
413 | 413 | """ |
|
414 |
Returns list of repositor |
|
|
414 | Returns list of repository groups you're an admin of | |
|
415 | 415 | """ |
|
416 | 416 | return [x[0] for x in self.permissions['repositories_groups'].iteritems() |
|
417 | 417 | if x[1] == 'group.admin'] |
|
418 | 418 | |
|
419 | 419 | @property |
|
420 | 420 | def ip_allowed(self): |
|
421 | 421 | """ |
|
422 | 422 | Checks if ip_addr used in constructor is allowed from defined list of |
|
423 | 423 | allowed ip_addresses for user |
|
424 | 424 | |
|
425 | 425 | :returns: boolean, True if ip is in allowed ip range |
|
426 | 426 | """ |
|
427 | 427 | #check IP |
|
428 | 428 | allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True) |
|
429 | 429 | if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips): |
|
430 | 430 | log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips)) |
|
431 | 431 | return True |
|
432 | 432 | else: |
|
433 | 433 | log.info('Access for IP:%s forbidden, ' |
|
434 | 434 | 'not in %s' % (self.ip_addr, allowed_ips)) |
|
435 | 435 | return False |
|
436 | 436 | |
|
437 | 437 | def __repr__(self): |
|
438 | 438 | return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username, |
|
439 | 439 | self.is_authenticated) |
|
440 | 440 | |
|
441 | 441 | def set_authenticated(self, authenticated=True): |
|
442 | 442 | if self.user_id != self.anonymous_user.user_id: |
|
443 | 443 | self.is_authenticated = authenticated |
|
444 | 444 | |
|
445 | 445 | def get_cookie_store(self): |
|
446 | 446 | return {'username': self.username, |
|
447 | 447 | 'user_id': self.user_id, |
|
448 | 448 | 'is_authenticated': self.is_authenticated} |
|
449 | 449 | |
|
450 | 450 | @classmethod |
|
451 | 451 | def from_cookie_store(cls, cookie_store): |
|
452 | 452 | """ |
|
453 | 453 | Creates AuthUser from a cookie store |
|
454 | 454 | |
|
455 | 455 | :param cls: |
|
456 | 456 | :param cookie_store: |
|
457 | 457 | """ |
|
458 | 458 | user_id = cookie_store.get('user_id') |
|
459 | 459 | username = cookie_store.get('username') |
|
460 | 460 | api_key = cookie_store.get('api_key') |
|
461 | 461 | return AuthUser(user_id, api_key, username) |
|
462 | 462 | |
|
463 | 463 | @classmethod |
|
464 | 464 | def get_allowed_ips(cls, user_id, cache=False): |
|
465 | 465 | _set = set() |
|
466 | 466 | user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id) |
|
467 | 467 | if cache: |
|
468 | 468 | user_ips = user_ips.options(FromCache("sql_cache_short", |
|
469 | 469 | "get_user_ips_%s" % user_id)) |
|
470 | 470 | for ip in user_ips: |
|
471 | 471 | try: |
|
472 | 472 | _set.add(ip.ip_addr) |
|
473 | 473 | except ObjectDeletedError: |
|
474 | 474 | # since we use heavy caching sometimes it happens that we get |
|
475 | 475 | # deleted objects here, we just skip them |
|
476 | 476 | pass |
|
477 | 477 | return _set or set(['0.0.0.0/0', '::/0']) |
|
478 | 478 | |
|
479 | 479 | |
|
480 | 480 | def set_available_permissions(config): |
|
481 | 481 | """ |
|
482 | 482 | This function will propagate pylons globals with all available defined |
|
483 | 483 | permission given in db. We don't want to check each time from db for new |
|
484 | 484 | permissions since adding a new permission also requires application restart |
|
485 | 485 | ie. to decorate new views with the newly created permission |
|
486 | 486 | |
|
487 | 487 | :param config: current pylons config instance |
|
488 | 488 | |
|
489 | 489 | """ |
|
490 | 490 | log.info('getting information about all available permissions') |
|
491 | 491 | try: |
|
492 | 492 | sa = meta.Session |
|
493 | 493 | all_perms = sa.query(Permission).all() |
|
494 | 494 | except Exception: |
|
495 | 495 | pass |
|
496 | 496 | finally: |
|
497 | 497 | meta.Session.remove() |
|
498 | 498 | |
|
499 | 499 | config['available_permissions'] = [x.permission_name for x in all_perms] |
|
500 | 500 | |
|
501 | 501 | |
|
502 | 502 | #============================================================================== |
|
503 | 503 | # CHECK DECORATORS |
|
504 | 504 | #============================================================================== |
|
505 | 505 | class LoginRequired(object): |
|
506 | 506 | """ |
|
507 | 507 | Must be logged in to execute this function else |
|
508 | 508 | redirect to login page |
|
509 | 509 | |
|
510 | 510 | :param api_access: if enabled this checks only for valid auth token |
|
511 | 511 | and grants access based on valid token |
|
512 | 512 | """ |
|
513 | 513 | |
|
514 | 514 | def __init__(self, api_access=False): |
|
515 | 515 | self.api_access = api_access |
|
516 | 516 | |
|
517 | 517 | def __call__(self, func): |
|
518 | 518 | return decorator(self.__wrapper, func) |
|
519 | 519 | |
|
520 | 520 | def __wrapper(self, func, *fargs, **fkwargs): |
|
521 | 521 | cls = fargs[0] |
|
522 | 522 | user = cls.rhodecode_user |
|
523 | 523 | loc = "%s:%s" % (cls.__class__.__name__, func.__name__) |
|
524 | 524 | |
|
525 | 525 | #check IP |
|
526 | 526 | ip_access_ok = True |
|
527 | 527 | if not user.ip_allowed: |
|
528 | 528 | from rhodecode.lib import helpers as h |
|
529 | 529 | h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))), |
|
530 | 530 | category='warning') |
|
531 | 531 | ip_access_ok = False |
|
532 | 532 | |
|
533 | 533 | api_access_ok = False |
|
534 | 534 | if self.api_access: |
|
535 | 535 | log.debug('Checking API KEY access for %s' % cls) |
|
536 | 536 | if user.api_key == request.GET.get('api_key'): |
|
537 | 537 | api_access_ok = True |
|
538 | 538 | else: |
|
539 | 539 | log.debug("API KEY token not valid") |
|
540 | 540 | |
|
541 | 541 | log.debug('Checking if %s is authenticated @ %s' % (user.username, loc)) |
|
542 | 542 | if (user.is_authenticated or api_access_ok) and ip_access_ok: |
|
543 | 543 | reason = 'RegularAuth' if user.is_authenticated else 'APIAuth' |
|
544 | 544 | log.info('user %s is authenticated and granted access to %s ' |
|
545 | 545 | 'using %s' % (user.username, loc, reason) |
|
546 | 546 | ) |
|
547 | 547 | return func(*fargs, **fkwargs) |
|
548 | 548 | else: |
|
549 | 549 | log.warn('user %s NOT authenticated on func: %s' % ( |
|
550 | 550 | user, loc) |
|
551 | 551 | ) |
|
552 | 552 | p = url.current() |
|
553 | 553 | |
|
554 | 554 | log.debug('redirecting to login page with %s' % p) |
|
555 | 555 | return redirect(url('login_home', came_from=p)) |
|
556 | 556 | |
|
557 | 557 | |
|
558 | 558 | class NotAnonymous(object): |
|
559 | 559 | """ |
|
560 | 560 | Must be logged in to execute this function else |
|
561 | 561 | redirect to login page""" |
|
562 | 562 | |
|
563 | 563 | def __call__(self, func): |
|
564 | 564 | return decorator(self.__wrapper, func) |
|
565 | 565 | |
|
566 | 566 | def __wrapper(self, func, *fargs, **fkwargs): |
|
567 | 567 | cls = fargs[0] |
|
568 | 568 | self.user = cls.rhodecode_user |
|
569 | 569 | |
|
570 | 570 | log.debug('Checking if user is not anonymous @%s' % cls) |
|
571 | 571 | |
|
572 | 572 | anonymous = self.user.username == 'default' |
|
573 | 573 | |
|
574 | 574 | if anonymous: |
|
575 | 575 | p = url.current() |
|
576 | 576 | |
|
577 | 577 | import rhodecode.lib.helpers as h |
|
578 | 578 | h.flash(_('You need to be a registered user to ' |
|
579 | 579 | 'perform this action'), |
|
580 | 580 | category='warning') |
|
581 | 581 | return redirect(url('login_home', came_from=p)) |
|
582 | 582 | else: |
|
583 | 583 | return func(*fargs, **fkwargs) |
|
584 | 584 | |
|
585 | 585 | |
|
586 | 586 | class PermsDecorator(object): |
|
587 | 587 | """Base class for controller decorators""" |
|
588 | 588 | |
|
589 | 589 | def __init__(self, *required_perms): |
|
590 | 590 | available_perms = config['available_permissions'] |
|
591 | 591 | for perm in required_perms: |
|
592 | 592 | if perm not in available_perms: |
|
593 | 593 | raise Exception("'%s' permission is not defined" % perm) |
|
594 | 594 | self.required_perms = set(required_perms) |
|
595 | 595 | self.user_perms = None |
|
596 | 596 | |
|
597 | 597 | def __call__(self, func): |
|
598 | 598 | return decorator(self.__wrapper, func) |
|
599 | 599 | |
|
600 | 600 | def __wrapper(self, func, *fargs, **fkwargs): |
|
601 | 601 | cls = fargs[0] |
|
602 | 602 | self.user = cls.rhodecode_user |
|
603 | 603 | self.user_perms = self.user.permissions |
|
604 | 604 | log.debug('checking %s permissions %s for %s %s', |
|
605 | 605 | self.__class__.__name__, self.required_perms, cls, self.user) |
|
606 | 606 | |
|
607 | 607 | if self.check_permissions(): |
|
608 | 608 | log.debug('Permission granted for %s %s' % (cls, self.user)) |
|
609 | 609 | return func(*fargs, **fkwargs) |
|
610 | 610 | |
|
611 | 611 | else: |
|
612 | 612 | log.debug('Permission denied for %s %s' % (cls, self.user)) |
|
613 | 613 | anonymous = self.user.username == 'default' |
|
614 | 614 | |
|
615 | 615 | if anonymous: |
|
616 | 616 | p = url.current() |
|
617 | 617 | |
|
618 | 618 | import rhodecode.lib.helpers as h |
|
619 | 619 | h.flash(_('You need to be a signed in to ' |
|
620 | 620 | 'view this page'), |
|
621 | 621 | category='warning') |
|
622 | 622 | return redirect(url('login_home', came_from=p)) |
|
623 | 623 | |
|
624 | 624 | else: |
|
625 | 625 | # redirect with forbidden ret code |
|
626 | 626 | return abort(403) |
|
627 | 627 | |
|
628 | 628 | def check_permissions(self): |
|
629 | 629 | """Dummy function for overriding""" |
|
630 | 630 | raise Exception('You have to write this function in child class') |
|
631 | 631 | |
|
632 | 632 | |
|
633 | 633 | class HasPermissionAllDecorator(PermsDecorator): |
|
634 | 634 | """ |
|
635 | 635 | Checks for access permission for all given predicates. All of them |
|
636 | 636 | have to be meet in order to fulfill the request |
|
637 | 637 | """ |
|
638 | 638 | |
|
639 | 639 | def check_permissions(self): |
|
640 | 640 | if self.required_perms.issubset(self.user_perms.get('global')): |
|
641 | 641 | return True |
|
642 | 642 | return False |
|
643 | 643 | |
|
644 | 644 | |
|
645 | 645 | class HasPermissionAnyDecorator(PermsDecorator): |
|
646 | 646 | """ |
|
647 | 647 | Checks for access permission for any of given predicates. In order to |
|
648 | 648 | fulfill the request any of predicates must be meet |
|
649 | 649 | """ |
|
650 | 650 | |
|
651 | 651 | def check_permissions(self): |
|
652 | 652 | if self.required_perms.intersection(self.user_perms.get('global')): |
|
653 | 653 | return True |
|
654 | 654 | return False |
|
655 | 655 | |
|
656 | 656 | |
|
657 | 657 | class HasRepoPermissionAllDecorator(PermsDecorator): |
|
658 | 658 | """ |
|
659 | 659 | Checks for access permission for all given predicates for specific |
|
660 | 660 | repository. All of them have to be meet in order to fulfill the request |
|
661 | 661 | """ |
|
662 | 662 | |
|
663 | 663 | def check_permissions(self): |
|
664 | 664 | repo_name = get_repo_slug(request) |
|
665 | 665 | try: |
|
666 | 666 | user_perms = set([self.user_perms['repositories'][repo_name]]) |
|
667 | 667 | except KeyError: |
|
668 | 668 | return False |
|
669 | 669 | if self.required_perms.issubset(user_perms): |
|
670 | 670 | return True |
|
671 | 671 | return False |
|
672 | 672 | |
|
673 | 673 | |
|
674 | 674 | class HasRepoPermissionAnyDecorator(PermsDecorator): |
|
675 | 675 | """ |
|
676 | 676 | Checks for access permission for any of given predicates for specific |
|
677 | 677 | repository. In order to fulfill the request any of predicates must be meet |
|
678 | 678 | """ |
|
679 | 679 | |
|
680 | 680 | def check_permissions(self): |
|
681 | 681 | repo_name = get_repo_slug(request) |
|
682 | 682 | try: |
|
683 | 683 | user_perms = set([self.user_perms['repositories'][repo_name]]) |
|
684 | 684 | except KeyError: |
|
685 | 685 | return False |
|
686 | 686 | |
|
687 | 687 | if self.required_perms.intersection(user_perms): |
|
688 | 688 | return True |
|
689 | 689 | return False |
|
690 | 690 | |
|
691 | 691 | |
|
692 | 692 | class HasReposGroupPermissionAllDecorator(PermsDecorator): |
|
693 | 693 | """ |
|
694 | 694 | Checks for access permission for all given predicates for specific |
|
695 | 695 | repository. All of them have to be meet in order to fulfill the request |
|
696 | 696 | """ |
|
697 | 697 | |
|
698 | 698 | def check_permissions(self): |
|
699 | 699 | group_name = get_repos_group_slug(request) |
|
700 | 700 | try: |
|
701 | 701 | user_perms = set([self.user_perms['repositories_groups'][group_name]]) |
|
702 | 702 | except KeyError: |
|
703 | 703 | return False |
|
704 | 704 | |
|
705 | 705 | if self.required_perms.issubset(user_perms): |
|
706 | 706 | return True |
|
707 | 707 | return False |
|
708 | 708 | |
|
709 | 709 | |
|
710 | 710 | class HasReposGroupPermissionAnyDecorator(PermsDecorator): |
|
711 | 711 | """ |
|
712 | 712 | Checks for access permission for any of given predicates for specific |
|
713 | 713 | repository. In order to fulfill the request any of predicates must be meet |
|
714 | 714 | """ |
|
715 | 715 | |
|
716 | 716 | def check_permissions(self): |
|
717 | 717 | group_name = get_repos_group_slug(request) |
|
718 | 718 | try: |
|
719 | 719 | user_perms = set([self.user_perms['repositories_groups'][group_name]]) |
|
720 | 720 | except KeyError: |
|
721 | 721 | return False |
|
722 | 722 | |
|
723 | 723 | if self.required_perms.intersection(user_perms): |
|
724 | 724 | return True |
|
725 | 725 | return False |
|
726 | 726 | |
|
727 | 727 | |
|
728 | 728 | #============================================================================== |
|
729 | 729 | # CHECK FUNCTIONS |
|
730 | 730 | #============================================================================== |
|
731 | 731 | class PermsFunction(object): |
|
732 | 732 | """Base function for other check functions""" |
|
733 | 733 | |
|
734 | 734 | def __init__(self, *perms): |
|
735 | 735 | available_perms = config['available_permissions'] |
|
736 | 736 | |
|
737 | 737 | for perm in perms: |
|
738 | 738 | if perm not in available_perms: |
|
739 | 739 | raise Exception("'%s' permission is not defined" % perm) |
|
740 | 740 | self.required_perms = set(perms) |
|
741 | 741 | self.user_perms = None |
|
742 | 742 | self.repo_name = None |
|
743 | 743 | self.group_name = None |
|
744 | 744 | |
|
745 | 745 | def __call__(self, check_location=''): |
|
746 | 746 | #TODO: put user as attribute here |
|
747 | 747 | user = request.user |
|
748 | 748 | cls_name = self.__class__.__name__ |
|
749 | 749 | check_scope = { |
|
750 | 750 | 'HasPermissionAll': '', |
|
751 | 751 | 'HasPermissionAny': '', |
|
752 | 752 | 'HasRepoPermissionAll': 'repo:%s' % self.repo_name, |
|
753 | 753 | 'HasRepoPermissionAny': 'repo:%s' % self.repo_name, |
|
754 | 754 | 'HasReposGroupPermissionAll': 'group:%s' % self.group_name, |
|
755 | 755 | 'HasReposGroupPermissionAny': 'group:%s' % self.group_name, |
|
756 | 756 | }.get(cls_name, '?') |
|
757 | 757 | log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name, |
|
758 | 758 | self.required_perms, user, check_scope, |
|
759 | 759 | check_location or 'unspecified location') |
|
760 | 760 | if not user: |
|
761 | 761 | log.debug('Empty request user') |
|
762 | 762 | return False |
|
763 | 763 | self.user_perms = user.permissions |
|
764 | 764 | if self.check_permissions(): |
|
765 | 765 | log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user, |
|
766 | 766 | check_location or 'unspecified location') |
|
767 | 767 | return True |
|
768 | 768 | |
|
769 | 769 | else: |
|
770 | 770 | log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user, |
|
771 | 771 | check_location or 'unspecified location') |
|
772 | 772 | return False |
|
773 | 773 | |
|
774 | 774 | def check_permissions(self): |
|
775 | 775 | """Dummy function for overriding""" |
|
776 | 776 | raise Exception('You have to write this function in child class') |
|
777 | 777 | |
|
778 | 778 | |
|
779 | 779 | class HasPermissionAll(PermsFunction): |
|
780 | 780 | def check_permissions(self): |
|
781 | 781 | if self.required_perms.issubset(self.user_perms.get('global')): |
|
782 | 782 | return True |
|
783 | 783 | return False |
|
784 | 784 | |
|
785 | 785 | |
|
786 | 786 | class HasPermissionAny(PermsFunction): |
|
787 | 787 | def check_permissions(self): |
|
788 | 788 | if self.required_perms.intersection(self.user_perms.get('global')): |
|
789 | 789 | return True |
|
790 | 790 | return False |
|
791 | 791 | |
|
792 | 792 | |
|
793 | 793 | class HasRepoPermissionAll(PermsFunction): |
|
794 | 794 | def __call__(self, repo_name=None, check_location=''): |
|
795 | 795 | self.repo_name = repo_name |
|
796 | 796 | return super(HasRepoPermissionAll, self).__call__(check_location) |
|
797 | 797 | |
|
798 | 798 | def check_permissions(self): |
|
799 | 799 | if not self.repo_name: |
|
800 | 800 | self.repo_name = get_repo_slug(request) |
|
801 | 801 | |
|
802 | 802 | try: |
|
803 | 803 | self._user_perms = set( |
|
804 | 804 | [self.user_perms['repositories'][self.repo_name]] |
|
805 | 805 | ) |
|
806 | 806 | except KeyError: |
|
807 | 807 | return False |
|
808 | 808 | if self.required_perms.issubset(self._user_perms): |
|
809 | 809 | return True |
|
810 | 810 | return False |
|
811 | 811 | |
|
812 | 812 | |
|
813 | 813 | class HasRepoPermissionAny(PermsFunction): |
|
814 | 814 | def __call__(self, repo_name=None, check_location=''): |
|
815 | 815 | self.repo_name = repo_name |
|
816 | 816 | return super(HasRepoPermissionAny, self).__call__(check_location) |
|
817 | 817 | |
|
818 | 818 | def check_permissions(self): |
|
819 | 819 | if not self.repo_name: |
|
820 | 820 | self.repo_name = get_repo_slug(request) |
|
821 | 821 | |
|
822 | 822 | try: |
|
823 | 823 | self._user_perms = set( |
|
824 | 824 | [self.user_perms['repositories'][self.repo_name]] |
|
825 | 825 | ) |
|
826 | 826 | except KeyError: |
|
827 | 827 | return False |
|
828 | 828 | if self.required_perms.intersection(self._user_perms): |
|
829 | 829 | return True |
|
830 | 830 | return False |
|
831 | 831 | |
|
832 | 832 | |
|
833 | 833 | class HasReposGroupPermissionAny(PermsFunction): |
|
834 | 834 | def __call__(self, group_name=None, check_location=''): |
|
835 | 835 | self.group_name = group_name |
|
836 | 836 | return super(HasReposGroupPermissionAny, self).__call__(check_location) |
|
837 | 837 | |
|
838 | 838 | def check_permissions(self): |
|
839 | 839 | try: |
|
840 | 840 | self._user_perms = set( |
|
841 | 841 | [self.user_perms['repositories_groups'][self.group_name]] |
|
842 | 842 | ) |
|
843 | 843 | except KeyError: |
|
844 | 844 | return False |
|
845 | 845 | if self.required_perms.intersection(self._user_perms): |
|
846 | 846 | return True |
|
847 | 847 | return False |
|
848 | 848 | |
|
849 | 849 | |
|
850 | 850 | class HasReposGroupPermissionAll(PermsFunction): |
|
851 | 851 | def __call__(self, group_name=None, check_location=''): |
|
852 | 852 | self.group_name = group_name |
|
853 | 853 | return super(HasReposGroupPermissionAll, self).__call__(check_location) |
|
854 | 854 | |
|
855 | 855 | def check_permissions(self): |
|
856 | 856 | try: |
|
857 | 857 | self._user_perms = set( |
|
858 | 858 | [self.user_perms['repositories_groups'][self.group_name]] |
|
859 | 859 | ) |
|
860 | 860 | except KeyError: |
|
861 | 861 | return False |
|
862 | 862 | if self.required_perms.issubset(self._user_perms): |
|
863 | 863 | return True |
|
864 | 864 | return False |
|
865 | 865 | |
|
866 | 866 | |
|
867 | 867 | #============================================================================== |
|
868 | 868 | # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH |
|
869 | 869 | #============================================================================== |
|
870 | 870 | class HasPermissionAnyMiddleware(object): |
|
871 | 871 | def __init__(self, *perms): |
|
872 | 872 | self.required_perms = set(perms) |
|
873 | 873 | |
|
874 | 874 | def __call__(self, user, repo_name): |
|
875 | 875 | # repo_name MUST be unicode, since we handle keys in permission |
|
876 | 876 | # dict by unicode |
|
877 | 877 | repo_name = safe_unicode(repo_name) |
|
878 | 878 | usr = AuthUser(user.user_id) |
|
879 | 879 | try: |
|
880 | 880 | self.user_perms = set([usr.permissions['repositories'][repo_name]]) |
|
881 | 881 | except Exception: |
|
882 | 882 | log.error('Exception while accessing permissions %s' % |
|
883 | 883 | traceback.format_exc()) |
|
884 | 884 | self.user_perms = set() |
|
885 | 885 | self.username = user.username |
|
886 | 886 | self.repo_name = repo_name |
|
887 | 887 | return self.check_permissions() |
|
888 | 888 | |
|
889 | 889 | def check_permissions(self): |
|
890 | 890 | log.debug('checking VCS protocol ' |
|
891 | 891 | 'permissions %s for user:%s repository:%s', self.user_perms, |
|
892 | 892 | self.username, self.repo_name) |
|
893 | 893 | if self.required_perms.intersection(self.user_perms): |
|
894 | 894 | log.debug('permission granted for user:%s on repo:%s' % ( |
|
895 | 895 | self.username, self.repo_name |
|
896 | 896 | ) |
|
897 | 897 | ) |
|
898 | 898 | return True |
|
899 | 899 | log.debug('permission denied for user:%s on repo:%s' % ( |
|
900 | 900 | self.username, self.repo_name |
|
901 | 901 | ) |
|
902 | 902 | ) |
|
903 | 903 | return False |
|
904 | 904 | |
|
905 | 905 | |
|
906 | 906 | #============================================================================== |
|
907 | 907 | # SPECIAL VERSION TO HANDLE API AUTH |
|
908 | 908 | #============================================================================== |
|
909 | 909 | class _BaseApiPerm(object): |
|
910 | 910 | def __init__(self, *perms): |
|
911 | 911 | self.required_perms = set(perms) |
|
912 | 912 | |
|
913 | 913 | def __call__(self, check_location='unspecified', user=None, repo_name=None): |
|
914 | 914 | cls_name = self.__class__.__name__ |
|
915 | 915 | check_scope = 'user:%s, repo:%s' % (user, repo_name) |
|
916 | 916 | log.debug('checking cls:%s %s %s @ %s', cls_name, |
|
917 | 917 | self.required_perms, check_scope, check_location) |
|
918 | 918 | if not user: |
|
919 | 919 | log.debug('Empty User passed into arguments') |
|
920 | 920 | return False |
|
921 | 921 | |
|
922 | 922 | ## process user |
|
923 | 923 | if not isinstance(user, AuthUser): |
|
924 | 924 | user = AuthUser(user.user_id) |
|
925 | 925 | |
|
926 | 926 | if self.check_permissions(user.permissions, repo_name): |
|
927 | 927 | log.debug('Permission to %s granted for user: %s @ %s', repo_name, |
|
928 | 928 | user, check_location) |
|
929 | 929 | return True |
|
930 | 930 | |
|
931 | 931 | else: |
|
932 | 932 | log.debug('Permission to %s denied for user: %s @ %s', repo_name, |
|
933 | 933 | user, check_location) |
|
934 | 934 | return False |
|
935 | 935 | |
|
936 | 936 | def check_permissions(self, perm_defs, repo_name): |
|
937 | 937 | """ |
|
938 | 938 | implement in child class should return True if permissions are ok, |
|
939 | 939 | False otherwise |
|
940 | 940 | |
|
941 | 941 | :param perm_defs: dict with permission definitions |
|
942 | 942 | :param repo_name: repo name |
|
943 | 943 | """ |
|
944 | 944 | raise NotImplementedError() |
|
945 | 945 | |
|
946 | 946 | |
|
947 | 947 | class HasPermissionAllApi(_BaseApiPerm): |
|
948 | 948 | def __call__(self, user, check_location=''): |
|
949 | 949 | return super(HasPermissionAllApi, self)\ |
|
950 | 950 | .__call__(check_location=check_location, user=user) |
|
951 | 951 | |
|
952 | 952 | def check_permissions(self, perm_defs, repo): |
|
953 | 953 | if self.required_perms.issubset(perm_defs.get('global')): |
|
954 | 954 | return True |
|
955 | 955 | return False |
|
956 | 956 | |
|
957 | 957 | |
|
958 | 958 | class HasPermissionAnyApi(_BaseApiPerm): |
|
959 | 959 | def __call__(self, user, check_location=''): |
|
960 | 960 | return super(HasPermissionAnyApi, self)\ |
|
961 | 961 | .__call__(check_location=check_location, user=user) |
|
962 | 962 | |
|
963 | 963 | def check_permissions(self, perm_defs, repo): |
|
964 | 964 | if self.required_perms.intersection(perm_defs.get('global')): |
|
965 | 965 | return True |
|
966 | 966 | return False |
|
967 | 967 | |
|
968 | 968 | |
|
969 | 969 | class HasRepoPermissionAllApi(_BaseApiPerm): |
|
970 | 970 | def __call__(self, user, repo_name, check_location=''): |
|
971 | 971 | return super(HasRepoPermissionAllApi, self)\ |
|
972 | 972 | .__call__(check_location=check_location, user=user, |
|
973 | 973 | repo_name=repo_name) |
|
974 | 974 | |
|
975 | 975 | def check_permissions(self, perm_defs, repo_name): |
|
976 | 976 | |
|
977 | 977 | try: |
|
978 | 978 | self._user_perms = set( |
|
979 | 979 | [perm_defs['repositories'][repo_name]] |
|
980 | 980 | ) |
|
981 | 981 | except KeyError: |
|
982 | 982 | log.warning(traceback.format_exc()) |
|
983 | 983 | return False |
|
984 | 984 | if self.required_perms.issubset(self._user_perms): |
|
985 | 985 | return True |
|
986 | 986 | return False |
|
987 | 987 | |
|
988 | 988 | |
|
989 | 989 | class HasRepoPermissionAnyApi(_BaseApiPerm): |
|
990 | 990 | def __call__(self, user, repo_name, check_location=''): |
|
991 | 991 | return super(HasRepoPermissionAnyApi, self)\ |
|
992 | 992 | .__call__(check_location=check_location, user=user, |
|
993 | 993 | repo_name=repo_name) |
|
994 | 994 | |
|
995 | 995 | def check_permissions(self, perm_defs, repo_name): |
|
996 | 996 | |
|
997 | 997 | try: |
|
998 | 998 | _user_perms = set( |
|
999 | 999 | [perm_defs['repositories'][repo_name]] |
|
1000 | 1000 | ) |
|
1001 | 1001 | except KeyError: |
|
1002 | 1002 | log.warning(traceback.format_exc()) |
|
1003 | 1003 | return False |
|
1004 | 1004 | if self.required_perms.intersection(_user_perms): |
|
1005 | 1005 | return True |
|
1006 | 1006 | return False |
|
1007 | 1007 | |
|
1008 | 1008 | |
|
1009 | 1009 | def check_ip_access(source_ip, allowed_ips=None): |
|
1010 | 1010 | """ |
|
1011 | 1011 | Checks if source_ip is a subnet of any of allowed_ips. |
|
1012 | 1012 | |
|
1013 | 1013 | :param source_ip: |
|
1014 | 1014 | :param allowed_ips: list of allowed ips together with mask |
|
1015 | 1015 | """ |
|
1016 | 1016 | from rhodecode.lib import ipaddr |
|
1017 | 1017 | log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips)) |
|
1018 | 1018 | if isinstance(allowed_ips, (tuple, list, set)): |
|
1019 | 1019 | for ip in allowed_ips: |
|
1020 | 1020 | try: |
|
1021 | 1021 | if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip): |
|
1022 | 1022 | return True |
|
1023 | 1023 | # for any case we cannot determine the IP, don't crash just |
|
1024 | 1024 | # skip it and log as error, we want to say forbidden still when |
|
1025 | 1025 | # sending bad IP |
|
1026 | 1026 | except Exception: |
|
1027 | 1027 | log.error(traceback.format_exc()) |
|
1028 | 1028 | continue |
|
1029 | 1029 | return False |
@@ -1,1186 +1,1186 b'' | |||
|
1 | 1 | """Helper functions |
|
2 | 2 | |
|
3 | 3 | Consists of functions to typically be used within templates, but also |
|
4 | 4 | available to Controllers. This module is available to both as 'h'. |
|
5 | 5 | """ |
|
6 | 6 | import random |
|
7 | 7 | import hashlib |
|
8 | 8 | import StringIO |
|
9 | 9 | import urllib |
|
10 | 10 | import math |
|
11 | 11 | import logging |
|
12 | 12 | import re |
|
13 | 13 | import urlparse |
|
14 | 14 | import textwrap |
|
15 | 15 | |
|
16 | 16 | from datetime import datetime |
|
17 | 17 | from pygments.formatters.html import HtmlFormatter |
|
18 | 18 | from pygments import highlight as code_highlight |
|
19 | 19 | from pylons import url, request, config |
|
20 | 20 | from pylons.i18n.translation import _, ungettext |
|
21 | 21 | from hashlib import md5 |
|
22 | 22 | |
|
23 | 23 | from webhelpers.html import literal, HTML, escape |
|
24 | 24 | from webhelpers.html.tools import * |
|
25 | 25 | from webhelpers.html.builder import make_tag |
|
26 | 26 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ |
|
27 | 27 | end_form, file, form, hidden, image, javascript_link, link_to, \ |
|
28 | 28 | link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \ |
|
29 | 29 | submit, text, password, textarea, title, ul, xml_declaration, radio |
|
30 | 30 | from webhelpers.html.tools import auto_link, button_to, highlight, \ |
|
31 | 31 | js_obfuscate, mail_to, strip_links, strip_tags, tag_re |
|
32 | 32 | from webhelpers.number import format_byte_size, format_bit_size |
|
33 | 33 | from webhelpers.pylonslib import Flash as _Flash |
|
34 | 34 | from webhelpers.pylonslib.secure_form import secure_form |
|
35 | 35 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ |
|
36 | 36 | convert_misc_entities, lchop, plural, rchop, remove_formatting, \ |
|
37 | 37 | replace_whitespace, urlify, truncate, wrap_paragraphs |
|
38 | 38 | from webhelpers.date import time_ago_in_words |
|
39 | 39 | from webhelpers.paginate import Page |
|
40 | 40 | from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ |
|
41 | 41 | convert_boolean_attrs, NotGiven, _make_safe_id_component |
|
42 | 42 | |
|
43 | 43 | from rhodecode.lib.annotate import annotate_highlight |
|
44 | 44 | from rhodecode.lib.utils import repo_name_slug, get_custom_lexer |
|
45 | 45 | from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \ |
|
46 | 46 | get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict |
|
47 | 47 | from rhodecode.lib.markup_renderer import MarkupRenderer |
|
48 | 48 | from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError |
|
49 | 49 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset |
|
50 | 50 | from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT |
|
51 | 51 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
52 | 52 | from rhodecode.model.db import URL_SEP, Permission |
|
53 | 53 | |
|
54 | 54 | log = logging.getLogger(__name__) |
|
55 | 55 | |
|
56 | 56 | |
|
57 | 57 | html_escape_table = { |
|
58 | 58 | "&": "&", |
|
59 | 59 | '"': """, |
|
60 | 60 | "'": "'", |
|
61 | 61 | ">": ">", |
|
62 | 62 | "<": "<", |
|
63 | 63 | } |
|
64 | 64 | |
|
65 | 65 | |
|
66 | 66 | def html_escape(text): |
|
67 | 67 | """Produce entities within text.""" |
|
68 | 68 | return "".join(html_escape_table.get(c, c) for c in text) |
|
69 | 69 | |
|
70 | 70 | |
|
71 | 71 | def shorter(text, size=20): |
|
72 | 72 | postfix = '...' |
|
73 | 73 | if len(text) > size: |
|
74 | 74 | return text[:size - len(postfix)] + postfix |
|
75 | 75 | return text |
|
76 | 76 | |
|
77 | 77 | |
|
78 | 78 | def _reset(name, value=None, id=NotGiven, type="reset", **attrs): |
|
79 | 79 | """ |
|
80 | 80 | Reset button |
|
81 | 81 | """ |
|
82 | 82 | _set_input_attrs(attrs, type, name, value) |
|
83 | 83 | _set_id_attr(attrs, id, name) |
|
84 | 84 | convert_boolean_attrs(attrs, ["disabled"]) |
|
85 | 85 | return HTML.input(**attrs) |
|
86 | 86 | |
|
87 | 87 | reset = _reset |
|
88 | 88 | safeid = _make_safe_id_component |
|
89 | 89 | |
|
90 | 90 | |
|
91 | 91 | def FID(raw_id, path): |
|
92 | 92 | """ |
|
93 | 93 | Creates a uniqe ID for filenode based on it's hash of path and revision |
|
94 | 94 | it's safe to use in urls |
|
95 | 95 | |
|
96 | 96 | :param raw_id: |
|
97 | 97 | :param path: |
|
98 | 98 | """ |
|
99 | 99 | |
|
100 | 100 | return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12]) |
|
101 | 101 | |
|
102 | 102 | |
|
103 | 103 | def get_token(): |
|
104 | 104 | """Return the current authentication token, creating one if one doesn't |
|
105 | 105 | already exist. |
|
106 | 106 | """ |
|
107 | 107 | token_key = "_authentication_token" |
|
108 | 108 | from pylons import session |
|
109 | 109 | if not token_key in session: |
|
110 | 110 | try: |
|
111 | 111 | token = hashlib.sha1(str(random.getrandbits(128))).hexdigest() |
|
112 | 112 | except AttributeError: # Python < 2.4 |
|
113 | 113 | token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest() |
|
114 | 114 | session[token_key] = token |
|
115 | 115 | if hasattr(session, 'save'): |
|
116 | 116 | session.save() |
|
117 | 117 | return session[token_key] |
|
118 | 118 | |
|
119 | 119 | |
|
120 | 120 | class _GetError(object): |
|
121 | 121 | """Get error from form_errors, and represent it as span wrapped error |
|
122 | 122 | message |
|
123 | 123 | |
|
124 | 124 | :param field_name: field to fetch errors for |
|
125 | 125 | :param form_errors: form errors dict |
|
126 | 126 | """ |
|
127 | 127 | |
|
128 | 128 | def __call__(self, field_name, form_errors): |
|
129 | 129 | tmpl = """<span class="error_msg">%s</span>""" |
|
130 | 130 | if form_errors and field_name in form_errors: |
|
131 | 131 | return literal(tmpl % form_errors.get(field_name)) |
|
132 | 132 | |
|
133 | 133 | get_error = _GetError() |
|
134 | 134 | |
|
135 | 135 | |
|
136 | 136 | class _ToolTip(object): |
|
137 | 137 | |
|
138 | 138 | def __call__(self, tooltip_title, trim_at=50): |
|
139 | 139 | """ |
|
140 | 140 | Special function just to wrap our text into nice formatted |
|
141 | 141 | autowrapped text |
|
142 | 142 | |
|
143 | 143 | :param tooltip_title: |
|
144 | 144 | """ |
|
145 | 145 | tooltip_title = escape(tooltip_title) |
|
146 | 146 | tooltip_title = tooltip_title.replace('<', '<').replace('>', '>') |
|
147 | 147 | return tooltip_title |
|
148 | 148 | tooltip = _ToolTip() |
|
149 | 149 | |
|
150 | 150 | |
|
151 | 151 | class _FilesBreadCrumbs(object): |
|
152 | 152 | |
|
153 | 153 | def __call__(self, repo_name, rev, paths): |
|
154 | 154 | if isinstance(paths, str): |
|
155 | 155 | paths = safe_unicode(paths) |
|
156 | 156 | url_l = [link_to(repo_name, url('files_home', |
|
157 | 157 | repo_name=repo_name, |
|
158 | 158 | revision=rev, f_path=''), |
|
159 | 159 | class_='ypjax-link')] |
|
160 | 160 | paths_l = paths.split('/') |
|
161 | 161 | for cnt, p in enumerate(paths_l): |
|
162 | 162 | if p != '': |
|
163 | 163 | url_l.append(link_to(p, |
|
164 | 164 | url('files_home', |
|
165 | 165 | repo_name=repo_name, |
|
166 | 166 | revision=rev, |
|
167 | 167 | f_path='/'.join(paths_l[:cnt + 1]) |
|
168 | 168 | ), |
|
169 | 169 | class_='ypjax-link' |
|
170 | 170 | ) |
|
171 | 171 | ) |
|
172 | 172 | |
|
173 | 173 | return literal('/'.join(url_l)) |
|
174 | 174 | |
|
175 | 175 | files_breadcrumbs = _FilesBreadCrumbs() |
|
176 | 176 | |
|
177 | 177 | |
|
178 | 178 | class CodeHtmlFormatter(HtmlFormatter): |
|
179 | 179 | """ |
|
180 | 180 | My code Html Formatter for source codes |
|
181 | 181 | """ |
|
182 | 182 | |
|
183 | 183 | def wrap(self, source, outfile): |
|
184 | 184 | return self._wrap_div(self._wrap_pre(self._wrap_code(source))) |
|
185 | 185 | |
|
186 | 186 | def _wrap_code(self, source): |
|
187 | 187 | for cnt, it in enumerate(source): |
|
188 | 188 | i, t = it |
|
189 | 189 | t = '<div id="L%s">%s</div>' % (cnt + 1, t) |
|
190 | 190 | yield i, t |
|
191 | 191 | |
|
192 | 192 | def _wrap_tablelinenos(self, inner): |
|
193 | 193 | dummyoutfile = StringIO.StringIO() |
|
194 | 194 | lncount = 0 |
|
195 | 195 | for t, line in inner: |
|
196 | 196 | if t: |
|
197 | 197 | lncount += 1 |
|
198 | 198 | dummyoutfile.write(line) |
|
199 | 199 | |
|
200 | 200 | fl = self.linenostart |
|
201 | 201 | mw = len(str(lncount + fl - 1)) |
|
202 | 202 | sp = self.linenospecial |
|
203 | 203 | st = self.linenostep |
|
204 | 204 | la = self.lineanchors |
|
205 | 205 | aln = self.anchorlinenos |
|
206 | 206 | nocls = self.noclasses |
|
207 | 207 | if sp: |
|
208 | 208 | lines = [] |
|
209 | 209 | |
|
210 | 210 | for i in range(fl, fl + lncount): |
|
211 | 211 | if i % st == 0: |
|
212 | 212 | if i % sp == 0: |
|
213 | 213 | if aln: |
|
214 | 214 | lines.append('<a href="#%s%d" class="special">%*d</a>' % |
|
215 | 215 | (la, i, mw, i)) |
|
216 | 216 | else: |
|
217 | 217 | lines.append('<span class="special">%*d</span>' % (mw, i)) |
|
218 | 218 | else: |
|
219 | 219 | if aln: |
|
220 | 220 | lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i)) |
|
221 | 221 | else: |
|
222 | 222 | lines.append('%*d' % (mw, i)) |
|
223 | 223 | else: |
|
224 | 224 | lines.append('') |
|
225 | 225 | ls = '\n'.join(lines) |
|
226 | 226 | else: |
|
227 | 227 | lines = [] |
|
228 | 228 | for i in range(fl, fl + lncount): |
|
229 | 229 | if i % st == 0: |
|
230 | 230 | if aln: |
|
231 | 231 | lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i)) |
|
232 | 232 | else: |
|
233 | 233 | lines.append('%*d' % (mw, i)) |
|
234 | 234 | else: |
|
235 | 235 | lines.append('') |
|
236 | 236 | ls = '\n'.join(lines) |
|
237 | 237 | |
|
238 | 238 | # in case you wonder about the seemingly redundant <div> here: since the |
|
239 | 239 | # content in the other cell also is wrapped in a div, some browsers in |
|
240 | 240 | # some configurations seem to mess up the formatting... |
|
241 | 241 | if nocls: |
|
242 | 242 | yield 0, ('<table class="%stable">' % self.cssclass + |
|
243 | 243 | '<tr><td><div class="linenodiv" ' |
|
244 | 244 | 'style="background-color: #f0f0f0; padding-right: 10px">' |
|
245 | 245 | '<pre style="line-height: 125%">' + |
|
246 | 246 | ls + '</pre></div></td><td id="hlcode" class="code">') |
|
247 | 247 | else: |
|
248 | 248 | yield 0, ('<table class="%stable">' % self.cssclass + |
|
249 | 249 | '<tr><td class="linenos"><div class="linenodiv"><pre>' + |
|
250 | 250 | ls + '</pre></div></td><td id="hlcode" class="code">') |
|
251 | 251 | yield 0, dummyoutfile.getvalue() |
|
252 | 252 | yield 0, '</td></tr></table>' |
|
253 | 253 | |
|
254 | 254 | |
|
255 | 255 | def pygmentize(filenode, **kwargs): |
|
256 | 256 | """ |
|
257 | 257 | pygmentize function using pygments |
|
258 | 258 | |
|
259 | 259 | :param filenode: |
|
260 | 260 | """ |
|
261 | 261 | lexer = get_custom_lexer(filenode.extension) or filenode.lexer |
|
262 | 262 | return literal(code_highlight(filenode.content, lexer, |
|
263 | 263 | CodeHtmlFormatter(**kwargs))) |
|
264 | 264 | |
|
265 | 265 | |
|
266 | 266 | def pygmentize_annotation(repo_name, filenode, **kwargs): |
|
267 | 267 | """ |
|
268 | 268 | pygmentize function for annotation |
|
269 | 269 | |
|
270 | 270 | :param filenode: |
|
271 | 271 | """ |
|
272 | 272 | |
|
273 | 273 | color_dict = {} |
|
274 | 274 | |
|
275 | 275 | def gen_color(n=10000): |
|
276 | 276 | """generator for getting n of evenly distributed colors using |
|
277 | 277 | hsv color and golden ratio. It always return same order of colors |
|
278 | 278 | |
|
279 | 279 | :returns: RGB tuple |
|
280 | 280 | """ |
|
281 | 281 | |
|
282 | 282 | def hsv_to_rgb(h, s, v): |
|
283 | 283 | if s == 0.0: |
|
284 | 284 | return v, v, v |
|
285 | 285 | i = int(h * 6.0) # XXX assume int() truncates! |
|
286 | 286 | f = (h * 6.0) - i |
|
287 | 287 | p = v * (1.0 - s) |
|
288 | 288 | q = v * (1.0 - s * f) |
|
289 | 289 | t = v * (1.0 - s * (1.0 - f)) |
|
290 | 290 | i = i % 6 |
|
291 | 291 | if i == 0: |
|
292 | 292 | return v, t, p |
|
293 | 293 | if i == 1: |
|
294 | 294 | return q, v, p |
|
295 | 295 | if i == 2: |
|
296 | 296 | return p, v, t |
|
297 | 297 | if i == 3: |
|
298 | 298 | return p, q, v |
|
299 | 299 | if i == 4: |
|
300 | 300 | return t, p, v |
|
301 | 301 | if i == 5: |
|
302 | 302 | return v, p, q |
|
303 | 303 | |
|
304 | 304 | golden_ratio = 0.618033988749895 |
|
305 | 305 | h = 0.22717784590367374 |
|
306 | 306 | |
|
307 | 307 | for _ in xrange(n): |
|
308 | 308 | h += golden_ratio |
|
309 | 309 | h %= 1 |
|
310 | 310 | HSV_tuple = [h, 0.95, 0.95] |
|
311 | 311 | RGB_tuple = hsv_to_rgb(*HSV_tuple) |
|
312 | 312 | yield map(lambda x: str(int(x * 256)), RGB_tuple) |
|
313 | 313 | |
|
314 | 314 | cgenerator = gen_color() |
|
315 | 315 | |
|
316 | 316 | def get_color_string(cs): |
|
317 | 317 | if cs in color_dict: |
|
318 | 318 | col = color_dict[cs] |
|
319 | 319 | else: |
|
320 | 320 | col = color_dict[cs] = cgenerator.next() |
|
321 | 321 | return "color: rgb(%s)! important;" % (', '.join(col)) |
|
322 | 322 | |
|
323 | 323 | def url_func(repo_name): |
|
324 | 324 | |
|
325 | 325 | def _url_func(changeset): |
|
326 | 326 | author = changeset.author |
|
327 | 327 | date = changeset.date |
|
328 | 328 | message = tooltip(changeset.message) |
|
329 | 329 | |
|
330 | 330 | tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>" |
|
331 | 331 | " %s<br/><b>Date:</b> %s</b><br/><b>Message:" |
|
332 | 332 | "</b> %s<br/></div>") |
|
333 | 333 | |
|
334 | 334 | tooltip_html = tooltip_html % (author, date, message) |
|
335 | 335 | lnk_format = '%5s:%s' % ('r%s' % changeset.revision, |
|
336 | 336 | short_id(changeset.raw_id)) |
|
337 | 337 | uri = link_to( |
|
338 | 338 | lnk_format, |
|
339 | 339 | url('changeset_home', repo_name=repo_name, |
|
340 | 340 | revision=changeset.raw_id), |
|
341 | 341 | style=get_color_string(changeset.raw_id), |
|
342 | 342 | class_='tooltip', |
|
343 | 343 | title=tooltip_html |
|
344 | 344 | ) |
|
345 | 345 | |
|
346 | 346 | uri += '\n' |
|
347 | 347 | return uri |
|
348 | 348 | return _url_func |
|
349 | 349 | |
|
350 | 350 | return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs)) |
|
351 | 351 | |
|
352 | 352 | |
|
353 | 353 | def is_following_repo(repo_name, user_id): |
|
354 | 354 | from rhodecode.model.scm import ScmModel |
|
355 | 355 | return ScmModel().is_following_repo(repo_name, user_id) |
|
356 | 356 | |
|
357 | 357 | flash = _Flash() |
|
358 | 358 | |
|
359 | 359 | #============================================================================== |
|
360 | 360 | # SCM FILTERS available via h. |
|
361 | 361 | #============================================================================== |
|
362 | 362 | from rhodecode.lib.vcs.utils import author_name, author_email |
|
363 | 363 | from rhodecode.lib.utils2 import credentials_filter, age as _age |
|
364 | 364 | from rhodecode.model.db import User, ChangesetStatus |
|
365 | 365 | |
|
366 | 366 | age = lambda x: _age(x) |
|
367 | 367 | capitalize = lambda x: x.capitalize() |
|
368 | 368 | email = author_email |
|
369 | 369 | short_id = lambda x: x[:12] |
|
370 | 370 | hide_credentials = lambda x: ''.join(credentials_filter(x)) |
|
371 | 371 | |
|
372 | 372 | |
|
373 | 373 | def fmt_date(date): |
|
374 | 374 | if date: |
|
375 | 375 | _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8') |
|
376 | 376 | return date.strftime(_fmt).decode('utf8') |
|
377 | 377 | |
|
378 | 378 | return "" |
|
379 | 379 | |
|
380 | 380 | |
|
381 | 381 | def is_git(repository): |
|
382 | 382 | if hasattr(repository, 'alias'): |
|
383 | 383 | _type = repository.alias |
|
384 | 384 | elif hasattr(repository, 'repo_type'): |
|
385 | 385 | _type = repository.repo_type |
|
386 | 386 | else: |
|
387 | 387 | _type = repository |
|
388 | 388 | return _type == 'git' |
|
389 | 389 | |
|
390 | 390 | |
|
391 | 391 | def is_hg(repository): |
|
392 | 392 | if hasattr(repository, 'alias'): |
|
393 | 393 | _type = repository.alias |
|
394 | 394 | elif hasattr(repository, 'repo_type'): |
|
395 | 395 | _type = repository.repo_type |
|
396 | 396 | else: |
|
397 | 397 | _type = repository |
|
398 | 398 | return _type == 'hg' |
|
399 | 399 | |
|
400 | 400 | |
|
401 | 401 | def email_or_none(author): |
|
402 | 402 | # extract email from the commit string |
|
403 | 403 | _email = email(author) |
|
404 | 404 | if _email != '': |
|
405 | 405 | # check it against RhodeCode database, and use the MAIN email for this |
|
406 | 406 | # user |
|
407 | 407 | user = User.get_by_email(_email, case_insensitive=True, cache=True) |
|
408 | 408 | if user is not None: |
|
409 | 409 | return user.email |
|
410 | 410 | return _email |
|
411 | 411 | |
|
412 | 412 | # See if it contains a username we can get an email from |
|
413 | 413 | user = User.get_by_username(author_name(author), case_insensitive=True, |
|
414 | 414 | cache=True) |
|
415 | 415 | if user is not None: |
|
416 | 416 | return user.email |
|
417 | 417 | |
|
418 | 418 | # No valid email, not a valid user in the system, none! |
|
419 | 419 | return None |
|
420 | 420 | |
|
421 | 421 | |
|
422 | 422 | def person(author, show_attr="username_and_name"): |
|
423 | 423 | # attr to return from fetched user |
|
424 | 424 | person_getter = lambda usr: getattr(usr, show_attr) |
|
425 | 425 | |
|
426 | 426 | # Valid email in the attribute passed, see if they're in the system |
|
427 | 427 | _email = email(author) |
|
428 | 428 | if _email != '': |
|
429 | 429 | user = User.get_by_email(_email, case_insensitive=True, cache=True) |
|
430 | 430 | if user is not None: |
|
431 | 431 | return person_getter(user) |
|
432 | 432 | return _email |
|
433 | 433 | |
|
434 | 434 | # Maybe it's a username? |
|
435 | 435 | _author = author_name(author) |
|
436 | 436 | user = User.get_by_username(_author, case_insensitive=True, |
|
437 | 437 | cache=True) |
|
438 | 438 | if user is not None: |
|
439 | 439 | return person_getter(user) |
|
440 | 440 | |
|
441 | 441 | # Still nothing? Just pass back the author name then |
|
442 | 442 | return _author |
|
443 | 443 | |
|
444 | 444 | |
|
445 | 445 | def person_by_id(id_, show_attr="username_and_name"): |
|
446 | 446 | # attr to return from fetched user |
|
447 | 447 | person_getter = lambda usr: getattr(usr, show_attr) |
|
448 | 448 | |
|
449 | 449 | #maybe it's an ID ? |
|
450 | 450 | if str(id_).isdigit() or isinstance(id_, int): |
|
451 | 451 | id_ = int(id_) |
|
452 | 452 | user = User.get(id_) |
|
453 | 453 | if user is not None: |
|
454 | 454 | return person_getter(user) |
|
455 | 455 | return id_ |
|
456 | 456 | |
|
457 | 457 | |
|
458 | 458 | def desc_stylize(value): |
|
459 | 459 | """ |
|
460 | 460 | converts tags from value into html equivalent |
|
461 | 461 | |
|
462 | 462 | :param value: |
|
463 | 463 | """ |
|
464 | 464 | value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
465 | 465 | '<div class="metatag" tag="see">see => \\1 </div>', value) |
|
466 | 466 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
467 | 467 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) |
|
468 | 468 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', |
|
469 | 469 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) |
|
470 | 470 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', |
|
471 | 471 | '<div class="metatag" tag="lang">\\2</div>', value) |
|
472 | 472 | value = re.sub(r'\[([a-z]+)\]', |
|
473 | 473 | '<div class="metatag" tag="\\1">\\1</div>', value) |
|
474 | 474 | |
|
475 | 475 | return value |
|
476 | 476 | |
|
477 | 477 | |
|
478 | 478 | def bool2icon(value): |
|
479 | 479 | """Returns True/False values represented as small html image of true/false |
|
480 | 480 | icons |
|
481 | 481 | |
|
482 | 482 | :param value: bool value |
|
483 | 483 | """ |
|
484 | 484 | |
|
485 | 485 | if value is True: |
|
486 | 486 | return HTML.tag('img', src=url("/images/icons/accept.png"), |
|
487 | 487 | alt=_('True')) |
|
488 | 488 | |
|
489 | 489 | if value is False: |
|
490 | 490 | return HTML.tag('img', src=url("/images/icons/cancel.png"), |
|
491 | 491 | alt=_('False')) |
|
492 | 492 | |
|
493 | 493 | return value |
|
494 | 494 | |
|
495 | 495 | |
|
496 | 496 | def action_parser(user_log, feed=False, parse_cs=False): |
|
497 | 497 | """ |
|
498 | 498 | This helper will action_map the specified string action into translated |
|
499 | 499 | fancy names with icons and links |
|
500 | 500 | |
|
501 | 501 | :param user_log: user log instance |
|
502 | 502 | :param feed: use output for feeds (no html and fancy icons) |
|
503 | 503 | :param parse_cs: parse Changesets into VCS instances |
|
504 | 504 | """ |
|
505 | 505 | |
|
506 | 506 | action = user_log.action |
|
507 | 507 | action_params = ' ' |
|
508 | 508 | |
|
509 | 509 | x = action.split(':') |
|
510 | 510 | |
|
511 | 511 | if len(x) > 1: |
|
512 | 512 | action, action_params = x |
|
513 | 513 | |
|
514 | 514 | def get_cs_links(): |
|
515 | 515 | revs_limit = 3 # display this amount always |
|
516 | 516 | revs_top_limit = 50 # show upto this amount of changesets hidden |
|
517 | 517 | revs_ids = action_params.split(',') |
|
518 | 518 | deleted = user_log.repository is None |
|
519 | 519 | if deleted: |
|
520 | 520 | return ','.join(revs_ids) |
|
521 | 521 | |
|
522 | 522 | repo_name = user_log.repository.repo_name |
|
523 | 523 | |
|
524 | 524 | def lnk(rev, repo_name): |
|
525 | 525 | if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict): |
|
526 | 526 | lazy_cs = True |
|
527 | 527 | if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None): |
|
528 | 528 | lazy_cs = False |
|
529 | 529 | lbl = '?' |
|
530 | 530 | if rev.op == 'delete_branch': |
|
531 | 531 | lbl = '%s' % _('Deleted branch: %s') % rev.ref_name |
|
532 | 532 | title = '' |
|
533 | 533 | elif rev.op == 'tag': |
|
534 | 534 | lbl = '%s' % _('Created tag: %s') % rev.ref_name |
|
535 | 535 | title = '' |
|
536 | 536 | _url = '#' |
|
537 | 537 | |
|
538 | 538 | else: |
|
539 | 539 | lbl = '%s' % (rev.short_id[:8]) |
|
540 | 540 | _url = url('changeset_home', repo_name=repo_name, |
|
541 | 541 | revision=rev.raw_id) |
|
542 | 542 | title = tooltip(rev.message) |
|
543 | 543 | else: |
|
544 | 544 | ## changeset cannot be found/striped/removed etc. |
|
545 | 545 | lbl = ('%s' % rev)[:12] |
|
546 | 546 | _url = '#' |
|
547 | 547 | title = _('Changeset not found') |
|
548 | 548 | if parse_cs: |
|
549 | 549 | return link_to(lbl, _url, title=title, class_='tooltip') |
|
550 | 550 | return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name, |
|
551 | 551 | class_='lazy-cs' if lazy_cs else '') |
|
552 | 552 | |
|
553 | 553 | revs = [] |
|
554 | 554 | if len(filter(lambda v: v != '', revs_ids)) > 0: |
|
555 | 555 | repo = None |
|
556 | 556 | for rev in revs_ids[:revs_top_limit]: |
|
557 | 557 | _op = _name = None |
|
558 | 558 | if len(rev.split('=>')) == 2: |
|
559 | 559 | _op, _name = rev.split('=>') |
|
560 | 560 | |
|
561 | 561 | # we want parsed changesets, or new log store format is bad |
|
562 | 562 | if parse_cs: |
|
563 | 563 | try: |
|
564 | 564 | if repo is None: |
|
565 | 565 | repo = user_log.repository.scm_instance |
|
566 | 566 | _rev = repo.get_changeset(rev) |
|
567 | 567 | revs.append(_rev) |
|
568 | 568 | except ChangesetDoesNotExistError: |
|
569 | 569 | log.error('cannot find revision %s in this repo' % rev) |
|
570 | 570 | revs.append(rev) |
|
571 | 571 | continue |
|
572 | 572 | else: |
|
573 | 573 | _rev = AttributeDict({ |
|
574 | 574 | 'short_id': rev[:12], |
|
575 | 575 | 'raw_id': rev, |
|
576 | 576 | 'message': '', |
|
577 | 577 | 'op': _op, |
|
578 | 578 | 'ref_name': _name |
|
579 | 579 | }) |
|
580 | 580 | revs.append(_rev) |
|
581 | 581 | cs_links = [] |
|
582 | 582 | cs_links.append(" " + ', '.join( |
|
583 | 583 | [lnk(rev, repo_name) for rev in revs[:revs_limit]] |
|
584 | 584 | ) |
|
585 | 585 | ) |
|
586 | 586 | |
|
587 | 587 | compare_view = ( |
|
588 | 588 | ' <div class="compare_view tooltip" title="%s">' |
|
589 | 589 | '<a href="%s">%s</a> </div>' % ( |
|
590 | 590 | _('Show all combined changesets %s->%s') % ( |
|
591 | 591 | revs_ids[0][:12], revs_ids[-1][:12] |
|
592 | 592 | ), |
|
593 | 593 | url('changeset_home', repo_name=repo_name, |
|
594 | 594 | revision='%s...%s' % (revs_ids[0], revs_ids[-1]) |
|
595 | 595 | ), |
|
596 | 596 | _('compare view') |
|
597 | 597 | ) |
|
598 | 598 | ) |
|
599 | 599 | |
|
600 | 600 | # if we have exactly one more than normally displayed |
|
601 | 601 | # just display it, takes less space than displaying |
|
602 | 602 | # "and 1 more revisions" |
|
603 | 603 | if len(revs_ids) == revs_limit + 1: |
|
604 | 604 | rev = revs[revs_limit] |
|
605 | 605 | cs_links.append(", " + lnk(rev, repo_name)) |
|
606 | 606 | |
|
607 | 607 | # hidden-by-default ones |
|
608 | 608 | if len(revs_ids) > revs_limit + 1: |
|
609 | 609 | uniq_id = revs_ids[0] |
|
610 | 610 | html_tmpl = ( |
|
611 | 611 | '<span> %s <a class="show_more" id="_%s" ' |
|
612 | 612 | 'href="#more">%s</a> %s</span>' |
|
613 | 613 | ) |
|
614 | 614 | if not feed: |
|
615 | 615 | cs_links.append(html_tmpl % ( |
|
616 | 616 | _('and'), |
|
617 | 617 | uniq_id, _('%s more') % (len(revs_ids) - revs_limit), |
|
618 | 618 | _('revisions') |
|
619 | 619 | ) |
|
620 | 620 | ) |
|
621 | 621 | |
|
622 | 622 | if not feed: |
|
623 | 623 | html_tmpl = '<span id="%s" style="display:none">, %s </span>' |
|
624 | 624 | else: |
|
625 | 625 | html_tmpl = '<span id="%s"> %s </span>' |
|
626 | 626 | |
|
627 | 627 | morelinks = ', '.join( |
|
628 | 628 | [lnk(rev, repo_name) for rev in revs[revs_limit:]] |
|
629 | 629 | ) |
|
630 | 630 | |
|
631 | 631 | if len(revs_ids) > revs_top_limit: |
|
632 | 632 | morelinks += ', ...' |
|
633 | 633 | |
|
634 | 634 | cs_links.append(html_tmpl % (uniq_id, morelinks)) |
|
635 | 635 | if len(revs) > 1: |
|
636 | 636 | cs_links.append(compare_view) |
|
637 | 637 | return ''.join(cs_links) |
|
638 | 638 | |
|
639 | 639 | def get_fork_name(): |
|
640 | 640 | repo_name = action_params |
|
641 | 641 | _url = url('summary_home', repo_name=repo_name) |
|
642 | 642 | return _('fork name %s') % link_to(action_params, _url) |
|
643 | 643 | |
|
644 | 644 | def get_user_name(): |
|
645 | 645 | user_name = action_params |
|
646 | 646 | return user_name |
|
647 | 647 | |
|
648 | 648 | def get_users_group(): |
|
649 | 649 | group_name = action_params |
|
650 | 650 | return group_name |
|
651 | 651 | |
|
652 | 652 | def get_pull_request(): |
|
653 | 653 | pull_request_id = action_params |
|
654 | 654 | deleted = user_log.repository is None |
|
655 | 655 | if deleted: |
|
656 | 656 | repo_name = user_log.repository_name |
|
657 | 657 | else: |
|
658 | 658 | repo_name = user_log.repository.repo_name |
|
659 | 659 | return link_to(_('Pull request #%s') % pull_request_id, |
|
660 | 660 | url('pullrequest_show', repo_name=repo_name, |
|
661 | 661 | pull_request_id=pull_request_id)) |
|
662 | 662 | |
|
663 | 663 | # action : translated str, callback(extractor), icon |
|
664 | 664 | action_map = { |
|
665 | 665 | 'user_deleted_repo': (_('[deleted] repository'), |
|
666 | 666 | None, 'database_delete.png'), |
|
667 | 667 | 'user_created_repo': (_('[created] repository'), |
|
668 | 668 | None, 'database_add.png'), |
|
669 | 669 | 'user_created_fork': (_('[created] repository as fork'), |
|
670 | 670 | None, 'arrow_divide.png'), |
|
671 | 671 | 'user_forked_repo': (_('[forked] repository'), |
|
672 | 672 | get_fork_name, 'arrow_divide.png'), |
|
673 | 673 | 'user_updated_repo': (_('[updated] repository'), |
|
674 | 674 | None, 'database_edit.png'), |
|
675 | 675 | 'admin_deleted_repo': (_('[delete] repository'), |
|
676 | 676 | None, 'database_delete.png'), |
|
677 | 677 | 'admin_created_repo': (_('[created] repository'), |
|
678 | 678 | None, 'database_add.png'), |
|
679 | 679 | 'admin_forked_repo': (_('[forked] repository'), |
|
680 | 680 | None, 'arrow_divide.png'), |
|
681 | 681 | 'admin_updated_repo': (_('[updated] repository'), |
|
682 | 682 | None, 'database_edit.png'), |
|
683 | 683 | 'admin_created_user': (_('[created] user'), |
|
684 | 684 | get_user_name, 'user_add.png'), |
|
685 | 685 | 'admin_updated_user': (_('[updated] user'), |
|
686 | 686 | get_user_name, 'user_edit.png'), |
|
687 |
'admin_created_users_group': (_('[created] user |
|
|
687 | 'admin_created_users_group': (_('[created] user group'), | |
|
688 | 688 | get_users_group, 'group_add.png'), |
|
689 |
'admin_updated_users_group': (_('[updated] user |
|
|
689 | 'admin_updated_users_group': (_('[updated] user group'), | |
|
690 | 690 | get_users_group, 'group_edit.png'), |
|
691 | 691 | 'user_commented_revision': (_('[commented] on revision in repository'), |
|
692 | 692 | get_cs_links, 'comment_add.png'), |
|
693 | 693 | 'user_commented_pull_request': (_('[commented] on pull request for'), |
|
694 | 694 | get_pull_request, 'comment_add.png'), |
|
695 | 695 | 'user_closed_pull_request': (_('[closed] pull request for'), |
|
696 | 696 | get_pull_request, 'tick.png'), |
|
697 | 697 | 'push': (_('[pushed] into'), |
|
698 | 698 | get_cs_links, 'script_add.png'), |
|
699 | 699 | 'push_local': (_('[committed via RhodeCode] into repository'), |
|
700 | 700 | get_cs_links, 'script_edit.png'), |
|
701 | 701 | 'push_remote': (_('[pulled from remote] into repository'), |
|
702 | 702 | get_cs_links, 'connect.png'), |
|
703 | 703 | 'pull': (_('[pulled] from'), |
|
704 | 704 | None, 'down_16.png'), |
|
705 | 705 | 'started_following_repo': (_('[started following] repository'), |
|
706 | 706 | None, 'heart_add.png'), |
|
707 | 707 | 'stopped_following_repo': (_('[stopped following] repository'), |
|
708 | 708 | None, 'heart_delete.png'), |
|
709 | 709 | } |
|
710 | 710 | |
|
711 | 711 | action_str = action_map.get(action, action) |
|
712 | 712 | if feed: |
|
713 | 713 | action = action_str[0].replace('[', '').replace(']', '') |
|
714 | 714 | else: |
|
715 | 715 | action = action_str[0]\ |
|
716 | 716 | .replace('[', '<span class="journal_highlight">')\ |
|
717 | 717 | .replace(']', '</span>') |
|
718 | 718 | |
|
719 | 719 | action_params_func = lambda: "" |
|
720 | 720 | |
|
721 | 721 | if callable(action_str[1]): |
|
722 | 722 | action_params_func = action_str[1] |
|
723 | 723 | |
|
724 | 724 | def action_parser_icon(): |
|
725 | 725 | action = user_log.action |
|
726 | 726 | action_params = None |
|
727 | 727 | x = action.split(':') |
|
728 | 728 | |
|
729 | 729 | if len(x) > 1: |
|
730 | 730 | action, action_params = x |
|
731 | 731 | |
|
732 | 732 | tmpl = """<img src="%s%s" alt="%s"/>""" |
|
733 | 733 | ico = action_map.get(action, ['', '', ''])[2] |
|
734 | 734 | return literal(tmpl % ((url('/images/icons/')), ico, action)) |
|
735 | 735 | |
|
736 | 736 | # returned callbacks we need to call to get |
|
737 | 737 | return [lambda: literal(action), action_params_func, action_parser_icon] |
|
738 | 738 | |
|
739 | 739 | |
|
740 | 740 | |
|
741 | 741 | #============================================================================== |
|
742 | 742 | # PERMS |
|
743 | 743 | #============================================================================== |
|
744 | 744 | from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ |
|
745 | 745 | HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \ |
|
746 | 746 | HasReposGroupPermissionAny |
|
747 | 747 | |
|
748 | 748 | |
|
749 | 749 | #============================================================================== |
|
750 | 750 | # GRAVATAR URL |
|
751 | 751 | #============================================================================== |
|
752 | 752 | |
|
753 | 753 | def gravatar_url(email_address, size=30): |
|
754 | 754 | from pylons import url # doh, we need to re-import url to mock it later |
|
755 | 755 | _def = 'anonymous@rhodecode.org' |
|
756 | 756 | use_gravatar = str2bool(config['app_conf'].get('use_gravatar')) |
|
757 | 757 | email_address = email_address or _def |
|
758 | 758 | if (not use_gravatar or not email_address or email_address == _def): |
|
759 | 759 | f = lambda a, l: min(l, key=lambda x: abs(x - a)) |
|
760 | 760 | return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30])) |
|
761 | 761 | |
|
762 | 762 | if use_gravatar and config['app_conf'].get('alternative_gravatar_url'): |
|
763 | 763 | tmpl = config['app_conf'].get('alternative_gravatar_url', '') |
|
764 | 764 | parsed_url = urlparse.urlparse(url.current(qualified=True)) |
|
765 | 765 | tmpl = tmpl.replace('{email}', email_address)\ |
|
766 | 766 | .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \ |
|
767 | 767 | .replace('{netloc}', parsed_url.netloc)\ |
|
768 | 768 | .replace('{scheme}', parsed_url.scheme)\ |
|
769 | 769 | .replace('{size}', str(size)) |
|
770 | 770 | return tmpl |
|
771 | 771 | |
|
772 | 772 | ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme') |
|
773 | 773 | default = 'identicon' |
|
774 | 774 | baseurl_nossl = "http://www.gravatar.com/avatar/" |
|
775 | 775 | baseurl_ssl = "https://secure.gravatar.com/avatar/" |
|
776 | 776 | baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl |
|
777 | 777 | |
|
778 | 778 | if isinstance(email_address, unicode): |
|
779 | 779 | #hashlib crashes on unicode items |
|
780 | 780 | email_address = safe_str(email_address) |
|
781 | 781 | # construct the url |
|
782 | 782 | gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?" |
|
783 | 783 | gravatar_url += urllib.urlencode({'d': default, 's': str(size)}) |
|
784 | 784 | |
|
785 | 785 | return gravatar_url |
|
786 | 786 | |
|
787 | 787 | |
|
788 | 788 | #============================================================================== |
|
789 | 789 | # REPO PAGER, PAGER FOR REPOSITORY |
|
790 | 790 | #============================================================================== |
|
791 | 791 | class RepoPage(Page): |
|
792 | 792 | |
|
793 | 793 | def __init__(self, collection, page=1, items_per_page=20, |
|
794 | 794 | item_count=None, url=None, **kwargs): |
|
795 | 795 | |
|
796 | 796 | """Create a "RepoPage" instance. special pager for paging |
|
797 | 797 | repository |
|
798 | 798 | """ |
|
799 | 799 | self._url_generator = url |
|
800 | 800 | |
|
801 | 801 | # Safe the kwargs class-wide so they can be used in the pager() method |
|
802 | 802 | self.kwargs = kwargs |
|
803 | 803 | |
|
804 | 804 | # Save a reference to the collection |
|
805 | 805 | self.original_collection = collection |
|
806 | 806 | |
|
807 | 807 | self.collection = collection |
|
808 | 808 | |
|
809 | 809 | # The self.page is the number of the current page. |
|
810 | 810 | # The first page has the number 1! |
|
811 | 811 | try: |
|
812 | 812 | self.page = int(page) # make it int() if we get it as a string |
|
813 | 813 | except (ValueError, TypeError): |
|
814 | 814 | self.page = 1 |
|
815 | 815 | |
|
816 | 816 | self.items_per_page = items_per_page |
|
817 | 817 | |
|
818 | 818 | # Unless the user tells us how many items the collections has |
|
819 | 819 | # we calculate that ourselves. |
|
820 | 820 | if item_count is not None: |
|
821 | 821 | self.item_count = item_count |
|
822 | 822 | else: |
|
823 | 823 | self.item_count = len(self.collection) |
|
824 | 824 | |
|
825 | 825 | # Compute the number of the first and last available page |
|
826 | 826 | if self.item_count > 0: |
|
827 | 827 | self.first_page = 1 |
|
828 | 828 | self.page_count = int(math.ceil(float(self.item_count) / |
|
829 | 829 | self.items_per_page)) |
|
830 | 830 | self.last_page = self.first_page + self.page_count - 1 |
|
831 | 831 | |
|
832 | 832 | # Make sure that the requested page number is the range of |
|
833 | 833 | # valid pages |
|
834 | 834 | if self.page > self.last_page: |
|
835 | 835 | self.page = self.last_page |
|
836 | 836 | elif self.page < self.first_page: |
|
837 | 837 | self.page = self.first_page |
|
838 | 838 | |
|
839 | 839 | # Note: the number of items on this page can be less than |
|
840 | 840 | # items_per_page if the last page is not full |
|
841 | 841 | self.first_item = max(0, (self.item_count) - (self.page * |
|
842 | 842 | items_per_page)) |
|
843 | 843 | self.last_item = ((self.item_count - 1) - items_per_page * |
|
844 | 844 | (self.page - 1)) |
|
845 | 845 | |
|
846 | 846 | self.items = list(self.collection[self.first_item:self.last_item + 1]) |
|
847 | 847 | |
|
848 | 848 | # Links to previous and next page |
|
849 | 849 | if self.page > self.first_page: |
|
850 | 850 | self.previous_page = self.page - 1 |
|
851 | 851 | else: |
|
852 | 852 | self.previous_page = None |
|
853 | 853 | |
|
854 | 854 | if self.page < self.last_page: |
|
855 | 855 | self.next_page = self.page + 1 |
|
856 | 856 | else: |
|
857 | 857 | self.next_page = None |
|
858 | 858 | |
|
859 | 859 | # No items available |
|
860 | 860 | else: |
|
861 | 861 | self.first_page = None |
|
862 | 862 | self.page_count = 0 |
|
863 | 863 | self.last_page = None |
|
864 | 864 | self.first_item = None |
|
865 | 865 | self.last_item = None |
|
866 | 866 | self.previous_page = None |
|
867 | 867 | self.next_page = None |
|
868 | 868 | self.items = [] |
|
869 | 869 | |
|
870 | 870 | # This is a subclass of the 'list' type. Initialise the list now. |
|
871 | 871 | list.__init__(self, reversed(self.items)) |
|
872 | 872 | |
|
873 | 873 | |
|
874 | 874 | def changed_tooltip(nodes): |
|
875 | 875 | """ |
|
876 | 876 | Generates a html string for changed nodes in changeset page. |
|
877 | 877 | It limits the output to 30 entries |
|
878 | 878 | |
|
879 | 879 | :param nodes: LazyNodesGenerator |
|
880 | 880 | """ |
|
881 | 881 | if nodes: |
|
882 | 882 | pref = ': <br/> ' |
|
883 | 883 | suf = '' |
|
884 | 884 | if len(nodes) > 30: |
|
885 | 885 | suf = '<br/>' + _(' and %s more') % (len(nodes) - 30) |
|
886 | 886 | return literal(pref + '<br/> '.join([safe_unicode(x.path) |
|
887 | 887 | for x in nodes[:30]]) + suf) |
|
888 | 888 | else: |
|
889 | 889 | return ': ' + _('No Files') |
|
890 | 890 | |
|
891 | 891 | |
|
892 | 892 | def repo_link(groups_and_repos, last_url=None): |
|
893 | 893 | """ |
|
894 | 894 | Makes a breadcrumbs link to repo within a group |
|
895 | 895 | joins » on each group to create a fancy link |
|
896 | 896 | |
|
897 | 897 | ex:: |
|
898 | 898 | group >> subgroup >> repo |
|
899 | 899 | |
|
900 | 900 | :param groups_and_repos: |
|
901 | 901 | :param last_url: |
|
902 | 902 | """ |
|
903 | 903 | groups, repo_name = groups_and_repos |
|
904 | 904 | last_link = link_to(repo_name, last_url) if last_url else repo_name |
|
905 | 905 | |
|
906 | 906 | if not groups: |
|
907 | 907 | if last_url: |
|
908 | 908 | return last_link |
|
909 | 909 | return repo_name |
|
910 | 910 | else: |
|
911 | 911 | def make_link(group): |
|
912 | 912 | return link_to(group.name, |
|
913 | 913 | url('repos_group_home', group_name=group.group_name)) |
|
914 | 914 | return literal(' » '.join(map(make_link, groups) + [last_link])) |
|
915 | 915 | |
|
916 | 916 | |
|
917 | 917 | def fancy_file_stats(stats): |
|
918 | 918 | """ |
|
919 | 919 | Displays a fancy two colored bar for number of added/deleted |
|
920 | 920 | lines of code on file |
|
921 | 921 | |
|
922 | 922 | :param stats: two element list of added/deleted lines of code |
|
923 | 923 | """ |
|
924 | 924 | def cgen(l_type, a_v, d_v): |
|
925 | 925 | mapping = {'tr': 'top-right-rounded-corner-mid', |
|
926 | 926 | 'tl': 'top-left-rounded-corner-mid', |
|
927 | 927 | 'br': 'bottom-right-rounded-corner-mid', |
|
928 | 928 | 'bl': 'bottom-left-rounded-corner-mid'} |
|
929 | 929 | map_getter = lambda x: mapping[x] |
|
930 | 930 | |
|
931 | 931 | if l_type == 'a' and d_v: |
|
932 | 932 | #case when added and deleted are present |
|
933 | 933 | return ' '.join(map(map_getter, ['tl', 'bl'])) |
|
934 | 934 | |
|
935 | 935 | if l_type == 'a' and not d_v: |
|
936 | 936 | return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl'])) |
|
937 | 937 | |
|
938 | 938 | if l_type == 'd' and a_v: |
|
939 | 939 | return ' '.join(map(map_getter, ['tr', 'br'])) |
|
940 | 940 | |
|
941 | 941 | if l_type == 'd' and not a_v: |
|
942 | 942 | return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl'])) |
|
943 | 943 | |
|
944 | 944 | a, d = stats[0], stats[1] |
|
945 | 945 | width = 100 |
|
946 | 946 | |
|
947 | 947 | if a == 'b': |
|
948 | 948 | #binary mode |
|
949 | 949 | b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin') |
|
950 | 950 | b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin') |
|
951 | 951 | return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d)) |
|
952 | 952 | |
|
953 | 953 | t = stats[0] + stats[1] |
|
954 | 954 | unit = float(width) / (t or 1) |
|
955 | 955 | |
|
956 | 956 | # needs > 9% of width to be visible or 0 to be hidden |
|
957 | 957 | a_p = max(9, unit * a) if a > 0 else 0 |
|
958 | 958 | d_p = max(9, unit * d) if d > 0 else 0 |
|
959 | 959 | p_sum = a_p + d_p |
|
960 | 960 | |
|
961 | 961 | if p_sum > width: |
|
962 | 962 | #adjust the percentage to be == 100% since we adjusted to 9 |
|
963 | 963 | if a_p > d_p: |
|
964 | 964 | a_p = a_p - (p_sum - width) |
|
965 | 965 | else: |
|
966 | 966 | d_p = d_p - (p_sum - width) |
|
967 | 967 | |
|
968 | 968 | a_v = a if a > 0 else '' |
|
969 | 969 | d_v = d if d > 0 else '' |
|
970 | 970 | |
|
971 | 971 | d_a = '<div class="added %s" style="width:%s%%">%s</div>' % ( |
|
972 | 972 | cgen('a', a_v, d_v), a_p, a_v |
|
973 | 973 | ) |
|
974 | 974 | d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % ( |
|
975 | 975 | cgen('d', a_v, d_v), d_p, d_v |
|
976 | 976 | ) |
|
977 | 977 | return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d)) |
|
978 | 978 | |
|
979 | 979 | |
|
980 | 980 | def urlify_text(text_, safe=True): |
|
981 | 981 | """ |
|
982 | 982 | Extrac urls from text and make html links out of them |
|
983 | 983 | |
|
984 | 984 | :param text_: |
|
985 | 985 | """ |
|
986 | 986 | |
|
987 | 987 | url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]''' |
|
988 | 988 | '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''') |
|
989 | 989 | |
|
990 | 990 | def url_func(match_obj): |
|
991 | 991 | url_full = match_obj.groups()[0] |
|
992 | 992 | return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full}) |
|
993 | 993 | _newtext = url_pat.sub(url_func, text_) |
|
994 | 994 | if safe: |
|
995 | 995 | return literal(_newtext) |
|
996 | 996 | return _newtext |
|
997 | 997 | |
|
998 | 998 | |
|
999 | 999 | def urlify_changesets(text_, repository): |
|
1000 | 1000 | """ |
|
1001 | 1001 | Extract revision ids from changeset and make link from them |
|
1002 | 1002 | |
|
1003 | 1003 | :param text_: |
|
1004 | 1004 | :param repository: repo name to build the URL with |
|
1005 | 1005 | """ |
|
1006 | 1006 | from pylons import url # doh, we need to re-import url to mock it later |
|
1007 | 1007 | URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') |
|
1008 | 1008 | |
|
1009 | 1009 | def url_func(match_obj): |
|
1010 | 1010 | rev = match_obj.groups()[1] |
|
1011 | 1011 | pref = match_obj.groups()[0] |
|
1012 | 1012 | suf = match_obj.groups()[2] |
|
1013 | 1013 | |
|
1014 | 1014 | tmpl = ( |
|
1015 | 1015 | '%(pref)s<a class="%(cls)s" href="%(url)s">' |
|
1016 | 1016 | '%(rev)s</a>%(suf)s' |
|
1017 | 1017 | ) |
|
1018 | 1018 | return tmpl % { |
|
1019 | 1019 | 'pref': pref, |
|
1020 | 1020 | 'cls': 'revision-link', |
|
1021 | 1021 | 'url': url('changeset_home', repo_name=repository, revision=rev), |
|
1022 | 1022 | 'rev': rev, |
|
1023 | 1023 | 'suf': suf |
|
1024 | 1024 | } |
|
1025 | 1025 | |
|
1026 | 1026 | newtext = URL_PAT.sub(url_func, text_) |
|
1027 | 1027 | |
|
1028 | 1028 | return newtext |
|
1029 | 1029 | |
|
1030 | 1030 | |
|
1031 | 1031 | def urlify_commit(text_, repository=None, link_=None): |
|
1032 | 1032 | """ |
|
1033 | 1033 | Parses given text message and makes proper links. |
|
1034 | 1034 | issues are linked to given issue-server, and rest is a changeset link |
|
1035 | 1035 | if link_ is given, in other case it's a plain text |
|
1036 | 1036 | |
|
1037 | 1037 | :param text_: |
|
1038 | 1038 | :param repository: |
|
1039 | 1039 | :param link_: changeset link |
|
1040 | 1040 | """ |
|
1041 | 1041 | import traceback |
|
1042 | 1042 | from pylons import url # doh, we need to re-import url to mock it later |
|
1043 | 1043 | |
|
1044 | 1044 | def escaper(string): |
|
1045 | 1045 | return string.replace('<', '<').replace('>', '>') |
|
1046 | 1046 | |
|
1047 | 1047 | def linkify_others(t, l): |
|
1048 | 1048 | urls = re.compile(r'(\<a.*?\<\/a\>)',) |
|
1049 | 1049 | links = [] |
|
1050 | 1050 | for e in urls.split(t): |
|
1051 | 1051 | if not urls.match(e): |
|
1052 | 1052 | links.append('<a class="message-link" href="%s">%s</a>' % (l, e)) |
|
1053 | 1053 | else: |
|
1054 | 1054 | links.append(e) |
|
1055 | 1055 | |
|
1056 | 1056 | return ''.join(links) |
|
1057 | 1057 | |
|
1058 | 1058 | # urlify changesets - extrac revisions and make link out of them |
|
1059 | 1059 | newtext = urlify_changesets(escaper(text_), repository) |
|
1060 | 1060 | |
|
1061 | 1061 | # extract http/https links and make them real urls |
|
1062 | 1062 | newtext = urlify_text(newtext, safe=False) |
|
1063 | 1063 | |
|
1064 | 1064 | try: |
|
1065 | 1065 | from rhodecode import CONFIG |
|
1066 | 1066 | conf = CONFIG |
|
1067 | 1067 | |
|
1068 | 1068 | # allow multiple issue servers to be used |
|
1069 | 1069 | valid_indices = [ |
|
1070 | 1070 | x.group(1) |
|
1071 | 1071 | for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys()) |
|
1072 | 1072 | if x and 'issue_server_link%s' % x.group(1) in conf |
|
1073 | 1073 | and 'issue_prefix%s' % x.group(1) in conf |
|
1074 | 1074 | ] |
|
1075 | 1075 | |
|
1076 | 1076 | log.debug('found issue server suffixes `%s` during valuation of: %s' |
|
1077 | 1077 | % (','.join(valid_indices), newtext)) |
|
1078 | 1078 | |
|
1079 | 1079 | for pattern_index in valid_indices: |
|
1080 | 1080 | ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index) |
|
1081 | 1081 | ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index) |
|
1082 | 1082 | ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index) |
|
1083 | 1083 | |
|
1084 | 1084 | log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s' |
|
1085 | 1085 | % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK, |
|
1086 | 1086 | ISSUE_PREFIX)) |
|
1087 | 1087 | |
|
1088 | 1088 | URL_PAT = re.compile(r'%s' % ISSUE_PATTERN) |
|
1089 | 1089 | |
|
1090 | 1090 | def url_func(match_obj): |
|
1091 | 1091 | pref = '' |
|
1092 | 1092 | if match_obj.group().startswith(' '): |
|
1093 | 1093 | pref = ' ' |
|
1094 | 1094 | |
|
1095 | 1095 | issue_id = ''.join(match_obj.groups()) |
|
1096 | 1096 | tmpl = ( |
|
1097 | 1097 | '%(pref)s<a class="%(cls)s" href="%(url)s">' |
|
1098 | 1098 | '%(issue-prefix)s%(id-repr)s' |
|
1099 | 1099 | '</a>' |
|
1100 | 1100 | ) |
|
1101 | 1101 | url = ISSUE_SERVER_LNK.replace('{id}', issue_id) |
|
1102 | 1102 | if repository: |
|
1103 | 1103 | url = url.replace('{repo}', repository) |
|
1104 | 1104 | repo_name = repository.split(URL_SEP)[-1] |
|
1105 | 1105 | url = url.replace('{repo_name}', repo_name) |
|
1106 | 1106 | |
|
1107 | 1107 | return tmpl % { |
|
1108 | 1108 | 'pref': pref, |
|
1109 | 1109 | 'cls': 'issue-tracker-link', |
|
1110 | 1110 | 'url': url, |
|
1111 | 1111 | 'id-repr': issue_id, |
|
1112 | 1112 | 'issue-prefix': ISSUE_PREFIX, |
|
1113 | 1113 | 'serv': ISSUE_SERVER_LNK, |
|
1114 | 1114 | } |
|
1115 | 1115 | newtext = URL_PAT.sub(url_func, newtext) |
|
1116 | 1116 | log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext)) |
|
1117 | 1117 | |
|
1118 | 1118 | # if we actually did something above |
|
1119 | 1119 | if link_: |
|
1120 | 1120 | # wrap not links into final link => link_ |
|
1121 | 1121 | newtext = linkify_others(newtext, link_) |
|
1122 | 1122 | except: |
|
1123 | 1123 | log.error(traceback.format_exc()) |
|
1124 | 1124 | pass |
|
1125 | 1125 | |
|
1126 | 1126 | return literal(newtext) |
|
1127 | 1127 | |
|
1128 | 1128 | |
|
1129 | 1129 | def rst(source): |
|
1130 | 1130 | return literal('<div class="rst-block">%s</div>' % |
|
1131 | 1131 | MarkupRenderer.rst(source)) |
|
1132 | 1132 | |
|
1133 | 1133 | |
|
1134 | 1134 | def rst_w_mentions(source): |
|
1135 | 1135 | """ |
|
1136 | 1136 | Wrapped rst renderer with @mention highlighting |
|
1137 | 1137 | |
|
1138 | 1138 | :param source: |
|
1139 | 1139 | """ |
|
1140 | 1140 | return literal('<div class="rst-block">%s</div>' % |
|
1141 | 1141 | MarkupRenderer.rst_with_mentions(source)) |
|
1142 | 1142 | |
|
1143 | 1143 | |
|
1144 | 1144 | def changeset_status(repo, revision): |
|
1145 | 1145 | return ChangesetStatusModel().get_status(repo, revision) |
|
1146 | 1146 | |
|
1147 | 1147 | |
|
1148 | 1148 | def changeset_status_lbl(changeset_status): |
|
1149 | 1149 | return dict(ChangesetStatus.STATUSES).get(changeset_status) |
|
1150 | 1150 | |
|
1151 | 1151 | |
|
1152 | 1152 | def get_permission_name(key): |
|
1153 | 1153 | return dict(Permission.PERMS).get(key) |
|
1154 | 1154 | |
|
1155 | 1155 | |
|
1156 | 1156 | def journal_filter_help(): |
|
1157 | 1157 | return _(textwrap.dedent(''' |
|
1158 | 1158 | Example filter terms: |
|
1159 | 1159 | repository:vcs |
|
1160 | 1160 | username:marcin |
|
1161 | 1161 | action:*push* |
|
1162 | 1162 | ip:127.0.0.1 |
|
1163 | 1163 | date:20120101 |
|
1164 | 1164 | date:[20120101100000 TO 20120102] |
|
1165 | 1165 | |
|
1166 | 1166 | Generate wildcards using '*' character: |
|
1167 | 1167 | "repositroy:vcs*" - search everything starting with 'vcs' |
|
1168 | 1168 | "repository:*vcs*" - search for repository containing 'vcs' |
|
1169 | 1169 | |
|
1170 | 1170 | Optional AND / OR operators in queries |
|
1171 | 1171 | "repository:vcs OR repository:test" |
|
1172 | 1172 | "username:test AND repository:test*" |
|
1173 | 1173 | ''')) |
|
1174 | 1174 | |
|
1175 | 1175 | |
|
1176 | 1176 | def not_mapped_error(repo_name): |
|
1177 | 1177 | flash(_('%s repository is not mapped to db perhaps' |
|
1178 | 1178 | ' it was created or renamed from the filesystem' |
|
1179 | 1179 | ' please run the application again' |
|
1180 | 1180 | ' in order to rescan repositories') % repo_name, category='error') |
|
1181 | 1181 | |
|
1182 | 1182 | |
|
1183 | 1183 | def ip_range(ip_addr): |
|
1184 | 1184 | from rhodecode.model.db import UserIpMap |
|
1185 | 1185 | s, e = UserIpMap._get_ip_range(ip_addr) |
|
1186 | 1186 | return '%s - %s' % (s, e) |
@@ -1,705 +1,705 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | rhodecode.model.repo |
|
4 | 4 | ~~~~~~~~~~~~~~~~~~~~ |
|
5 | 5 | |
|
6 | 6 | Repository model for rhodecode |
|
7 | 7 | |
|
8 | 8 | :created_on: Jun 5, 2010 |
|
9 | 9 | :author: marcink |
|
10 | 10 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> |
|
11 | 11 | :license: GPLv3, see COPYING for more details. |
|
12 | 12 | """ |
|
13 | 13 | # This program is free software: you can redistribute it and/or modify |
|
14 | 14 | # it under the terms of the GNU General Public License as published by |
|
15 | 15 | # the Free Software Foundation, either version 3 of the License, or |
|
16 | 16 | # (at your option) any later version. |
|
17 | 17 | # |
|
18 | 18 | # This program is distributed in the hope that it will be useful, |
|
19 | 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
20 | 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
21 | 21 | # GNU General Public License for more details. |
|
22 | 22 | # |
|
23 | 23 | # You should have received a copy of the GNU General Public License |
|
24 | 24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 | 25 | from __future__ import with_statement |
|
26 | 26 | import os |
|
27 | 27 | import shutil |
|
28 | 28 | import logging |
|
29 | 29 | import traceback |
|
30 | 30 | from datetime import datetime |
|
31 | 31 | |
|
32 | 32 | from rhodecode.lib.vcs.backends import get_backend |
|
33 | 33 | from rhodecode.lib.compat import json |
|
34 | 34 | from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\ |
|
35 | 35 | remove_prefix |
|
36 | 36 | from rhodecode.lib.caching_query import FromCache |
|
37 | 37 | from rhodecode.lib.hooks import log_create_repository, log_delete_repository |
|
38 | 38 | |
|
39 | 39 | from rhodecode.model import BaseModel |
|
40 | 40 | from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \ |
|
41 | 41 | Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup,\ |
|
42 | 42 | RhodeCodeSetting, RepositoryField |
|
43 | 43 | from rhodecode.lib import helpers as h |
|
44 | 44 | from rhodecode.lib.auth import HasRepoPermissionAny |
|
45 | 45 | from rhodecode.lib.vcs.backends.base import EmptyChangeset |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | log = logging.getLogger(__name__) |
|
49 | 49 | |
|
50 | 50 | |
|
51 | 51 | class RepoModel(BaseModel): |
|
52 | 52 | |
|
53 | 53 | cls = Repository |
|
54 | 54 | URL_SEPARATOR = Repository.url_sep() |
|
55 | 55 | |
|
56 | 56 | def __get_users_group(self, users_group): |
|
57 | 57 | return self._get_instance(UsersGroup, users_group, |
|
58 | 58 | callback=UsersGroup.get_by_group_name) |
|
59 | 59 | |
|
60 | 60 | def _get_repos_group(self, repos_group): |
|
61 | 61 | return self._get_instance(RepoGroup, repos_group, |
|
62 | 62 | callback=RepoGroup.get_by_group_name) |
|
63 | 63 | |
|
64 | 64 | @LazyProperty |
|
65 | 65 | def repos_path(self): |
|
66 | 66 | """ |
|
67 | 67 | Get's the repositories root path from database |
|
68 | 68 | """ |
|
69 | 69 | |
|
70 | 70 | q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one() |
|
71 | 71 | return q.ui_value |
|
72 | 72 | |
|
73 | 73 | def get(self, repo_id, cache=False): |
|
74 | 74 | repo = self.sa.query(Repository)\ |
|
75 | 75 | .filter(Repository.repo_id == repo_id) |
|
76 | 76 | |
|
77 | 77 | if cache: |
|
78 | 78 | repo = repo.options(FromCache("sql_cache_short", |
|
79 | 79 | "get_repo_%s" % repo_id)) |
|
80 | 80 | return repo.scalar() |
|
81 | 81 | |
|
82 | 82 | def get_repo(self, repository): |
|
83 | 83 | return self._get_repo(repository) |
|
84 | 84 | |
|
85 | 85 | def get_by_repo_name(self, repo_name, cache=False): |
|
86 | 86 | repo = self.sa.query(Repository)\ |
|
87 | 87 | .filter(Repository.repo_name == repo_name) |
|
88 | 88 | |
|
89 | 89 | if cache: |
|
90 | 90 | repo = repo.options(FromCache("sql_cache_short", |
|
91 | 91 | "get_repo_%s" % repo_name)) |
|
92 | 92 | return repo.scalar() |
|
93 | 93 | |
|
94 | 94 | def get_all_user_repos(self, user): |
|
95 | 95 | """ |
|
96 | 96 | Get's all repositories that user have at least read access |
|
97 | 97 | |
|
98 | 98 | :param user: |
|
99 | 99 | :type user: |
|
100 | 100 | """ |
|
101 | 101 | from rhodecode.lib.auth import AuthUser |
|
102 | 102 | user = self._get_user(user) |
|
103 | 103 | repos = AuthUser(user_id=user.user_id).permissions['repositories'] |
|
104 | 104 | access_check = lambda r: r[1] in ['repository.read', |
|
105 | 105 | 'repository.write', |
|
106 | 106 | 'repository.admin'] |
|
107 | 107 | repos = [x[0] for x in filter(access_check, repos.items())] |
|
108 | 108 | return Repository.query().filter(Repository.repo_name.in_(repos)) |
|
109 | 109 | |
|
110 | 110 | def get_users_js(self): |
|
111 | 111 | users = self.sa.query(User).filter(User.active == True).all() |
|
112 | 112 | return json.dumps([ |
|
113 | 113 | { |
|
114 | 114 | 'id': u.user_id, |
|
115 | 115 | 'fname': u.name, |
|
116 | 116 | 'lname': u.lastname, |
|
117 | 117 | 'nname': u.username, |
|
118 | 118 | 'gravatar_lnk': h.gravatar_url(u.email, 14) |
|
119 | 119 | } for u in users] |
|
120 | 120 | ) |
|
121 | 121 | |
|
122 | 122 | def get_users_groups_js(self): |
|
123 | 123 | users_groups = self.sa.query(UsersGroup)\ |
|
124 | 124 | .filter(UsersGroup.users_group_active == True).all() |
|
125 | 125 | |
|
126 | 126 | return json.dumps([ |
|
127 | 127 | { |
|
128 | 128 | 'id': gr.users_group_id, |
|
129 | 129 | 'grname': gr.users_group_name, |
|
130 | 130 | 'grmembers': len(gr.members), |
|
131 | 131 | } for gr in users_groups] |
|
132 | 132 | ) |
|
133 | 133 | |
|
134 | 134 | @classmethod |
|
135 | 135 | def _render_datatable(cls, tmpl, *args, **kwargs): |
|
136 | 136 | import rhodecode |
|
137 | 137 | from pylons import tmpl_context as c |
|
138 | 138 | from pylons.i18n.translation import _ |
|
139 | 139 | |
|
140 | 140 | _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup |
|
141 | 141 | template = _tmpl_lookup.get_template('data_table/_dt_elements.html') |
|
142 | 142 | |
|
143 | 143 | tmpl = template.get_def(tmpl) |
|
144 | 144 | kwargs.update(dict(_=_, h=h, c=c)) |
|
145 | 145 | return tmpl.render(*args, **kwargs) |
|
146 | 146 | |
|
147 | 147 | @classmethod |
|
148 | 148 | def update_repoinfo(cls, repositories=None): |
|
149 | 149 | if not repositories: |
|
150 | 150 | repositories = Repository.getAll() |
|
151 | 151 | for repo in repositories: |
|
152 | 152 | scm_repo = repo.scm_instance_no_cache |
|
153 | 153 | last_cs = EmptyChangeset() |
|
154 | 154 | if scm_repo: |
|
155 | 155 | last_cs = scm_repo.get_changeset() |
|
156 | 156 | repo.update_changeset_cache(last_cs) |
|
157 | 157 | |
|
158 | 158 | def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True, |
|
159 | 159 | super_user_actions=False): |
|
160 | 160 | _render = self._render_datatable |
|
161 | 161 | |
|
162 | 162 | def quick_menu(repo_name): |
|
163 | 163 | return _render('quick_menu', repo_name) |
|
164 | 164 | |
|
165 | 165 | def repo_lnk(name, rtype, private, fork_of): |
|
166 | 166 | return _render('repo_name', name, rtype, private, fork_of, |
|
167 | 167 | short_name=not admin, admin=False) |
|
168 | 168 | |
|
169 | 169 | def last_change(last_change): |
|
170 | 170 | return _render("last_change", last_change) |
|
171 | 171 | |
|
172 | 172 | def rss_lnk(repo_name): |
|
173 | 173 | return _render("rss", repo_name) |
|
174 | 174 | |
|
175 | 175 | def atom_lnk(repo_name): |
|
176 | 176 | return _render("atom", repo_name) |
|
177 | 177 | |
|
178 | 178 | def last_rev(repo_name, cs_cache): |
|
179 | 179 | return _render('revision', repo_name, cs_cache.get('revision'), |
|
180 | 180 | cs_cache.get('raw_id'), cs_cache.get('author'), |
|
181 | 181 | cs_cache.get('message')) |
|
182 | 182 | |
|
183 | 183 | def desc(desc): |
|
184 | 184 | from pylons import tmpl_context as c |
|
185 | 185 | if c.visual.stylify_metatags: |
|
186 | 186 | return h.urlify_text(h.desc_stylize(h.truncate(desc, 60))) |
|
187 | 187 | else: |
|
188 | 188 | return h.urlify_text(h.truncate(desc, 60)) |
|
189 | 189 | |
|
190 | 190 | def repo_actions(repo_name): |
|
191 | 191 | return _render('repo_actions', repo_name, super_user_actions) |
|
192 | 192 | |
|
193 | 193 | def owner_actions(user_id, username): |
|
194 | 194 | return _render('user_name', user_id, username) |
|
195 | 195 | |
|
196 | 196 | repos_data = [] |
|
197 | 197 | for repo in repos_list: |
|
198 | 198 | if perm_check: |
|
199 | 199 | # check permission at this level |
|
200 | 200 | if not HasRepoPermissionAny( |
|
201 | 201 | 'repository.read', 'repository.write', 'repository.admin' |
|
202 | 202 | )(repo.repo_name, 'get_repos_as_dict check'): |
|
203 | 203 | continue |
|
204 | 204 | cs_cache = repo.changeset_cache |
|
205 | 205 | row = { |
|
206 | 206 | "menu": quick_menu(repo.repo_name), |
|
207 | 207 | "raw_name": repo.repo_name.lower(), |
|
208 | 208 | "name": repo_lnk(repo.repo_name, repo.repo_type, |
|
209 | 209 | repo.private, repo.fork), |
|
210 | 210 | "last_change": last_change(repo.last_db_change), |
|
211 | 211 | "last_changeset": last_rev(repo.repo_name, cs_cache), |
|
212 | 212 | "raw_tip": cs_cache.get('revision'), |
|
213 | 213 | "desc": desc(repo.description), |
|
214 | 214 | "owner": h.person(repo.user.username), |
|
215 | 215 | "rss": rss_lnk(repo.repo_name), |
|
216 | 216 | "atom": atom_lnk(repo.repo_name), |
|
217 | 217 | |
|
218 | 218 | } |
|
219 | 219 | if admin: |
|
220 | 220 | row.update({ |
|
221 | 221 | "action": repo_actions(repo.repo_name), |
|
222 | 222 | "owner": owner_actions(repo.user.user_id, |
|
223 | 223 | h.person(repo.user.username)) |
|
224 | 224 | }) |
|
225 | 225 | repos_data.append(row) |
|
226 | 226 | |
|
227 | 227 | return { |
|
228 | 228 | "totalRecords": len(repos_list), |
|
229 | 229 | "startIndex": 0, |
|
230 | 230 | "sort": "name", |
|
231 | 231 | "dir": "asc", |
|
232 | 232 | "records": repos_data |
|
233 | 233 | } |
|
234 | 234 | |
|
235 | 235 | def _get_defaults(self, repo_name): |
|
236 | 236 | """ |
|
237 | 237 | Get's information about repository, and returns a dict for |
|
238 | 238 | usage in forms |
|
239 | 239 | |
|
240 | 240 | :param repo_name: |
|
241 | 241 | """ |
|
242 | 242 | |
|
243 | 243 | repo_info = Repository.get_by_repo_name(repo_name) |
|
244 | 244 | |
|
245 | 245 | if repo_info is None: |
|
246 | 246 | return None |
|
247 | 247 | |
|
248 | 248 | defaults = repo_info.get_dict() |
|
249 | 249 | group, repo_name = repo_info.groups_and_repo |
|
250 | 250 | defaults['repo_name'] = repo_name |
|
251 | 251 | defaults['repo_group'] = getattr(group[-1] if group else None, |
|
252 | 252 | 'group_id', None) |
|
253 | 253 | |
|
254 | 254 | for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'), |
|
255 | 255 | (1, 'repo_description'), (1, 'repo_enable_locking'), |
|
256 | 256 | (1, 'repo_landing_rev'), (0, 'clone_uri'), |
|
257 | 257 | (1, 'repo_private'), (1, 'repo_enable_statistics')]: |
|
258 | 258 | attr = k |
|
259 | 259 | if strip: |
|
260 | 260 | attr = remove_prefix(k, 'repo_') |
|
261 | 261 | |
|
262 | 262 | defaults[k] = defaults[attr] |
|
263 | 263 | |
|
264 | 264 | # fill owner |
|
265 | 265 | if repo_info.user: |
|
266 | 266 | defaults.update({'user': repo_info.user.username}) |
|
267 | 267 | else: |
|
268 | 268 | replacement_user = User.query().filter(User.admin == |
|
269 | 269 | True).first().username |
|
270 | 270 | defaults.update({'user': replacement_user}) |
|
271 | 271 | |
|
272 | 272 | # fill repository users |
|
273 | 273 | for p in repo_info.repo_to_perm: |
|
274 | 274 | defaults.update({'u_perm_%s' % p.user.username: |
|
275 | 275 | p.permission.permission_name}) |
|
276 | 276 | |
|
277 | 277 | # fill repository groups |
|
278 | 278 | for p in repo_info.users_group_to_perm: |
|
279 | 279 | defaults.update({'g_perm_%s' % p.users_group.users_group_name: |
|
280 | 280 | p.permission.permission_name}) |
|
281 | 281 | |
|
282 | 282 | return defaults |
|
283 | 283 | |
|
284 | 284 | def update(self, org_repo_name, **kwargs): |
|
285 | 285 | try: |
|
286 | 286 | cur_repo = self.get_by_repo_name(org_repo_name, cache=False) |
|
287 | 287 | |
|
288 | 288 | # update permissions |
|
289 | 289 | for member, perm, member_type in kwargs['perms_updates']: |
|
290 | 290 | if member_type == 'user': |
|
291 | 291 | # this updates existing one |
|
292 | 292 | RepoModel().grant_user_permission( |
|
293 | 293 | repo=cur_repo, user=member, perm=perm |
|
294 | 294 | ) |
|
295 | 295 | else: |
|
296 | 296 | RepoModel().grant_users_group_permission( |
|
297 | 297 | repo=cur_repo, group_name=member, perm=perm |
|
298 | 298 | ) |
|
299 | 299 | # set new permissions |
|
300 | 300 | for member, perm, member_type in kwargs['perms_new']: |
|
301 | 301 | if member_type == 'user': |
|
302 | 302 | RepoModel().grant_user_permission( |
|
303 | 303 | repo=cur_repo, user=member, perm=perm |
|
304 | 304 | ) |
|
305 | 305 | else: |
|
306 | 306 | RepoModel().grant_users_group_permission( |
|
307 | 307 | repo=cur_repo, group_name=member, perm=perm |
|
308 | 308 | ) |
|
309 | 309 | |
|
310 | 310 | if 'user' in kwargs: |
|
311 | 311 | cur_repo.user = User.get_by_username(kwargs['user']) |
|
312 | 312 | |
|
313 | 313 | if 'repo_group' in kwargs: |
|
314 | 314 | cur_repo.group = RepoGroup.get(kwargs['repo_group']) |
|
315 | 315 | |
|
316 | 316 | for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'), |
|
317 | 317 | (1, 'repo_description'), (1, 'repo_enable_locking'), |
|
318 | 318 | (1, 'repo_landing_rev'), (0, 'clone_uri'), |
|
319 | 319 | (1, 'repo_private'), (1, 'repo_enable_statistics')]: |
|
320 | 320 | if k in kwargs: |
|
321 | 321 | val = kwargs[k] |
|
322 | 322 | if strip: |
|
323 | 323 | k = remove_prefix(k, 'repo_') |
|
324 | 324 | setattr(cur_repo, k, val) |
|
325 | 325 | |
|
326 | 326 | new_name = cur_repo.get_new_name(kwargs['repo_name']) |
|
327 | 327 | cur_repo.repo_name = new_name |
|
328 | 328 | |
|
329 | 329 | #handle extra fields |
|
330 | 330 | for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs): |
|
331 | 331 | k = RepositoryField.un_prefix_key(field) |
|
332 | 332 | ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo) |
|
333 | 333 | if ex_field: |
|
334 | 334 | ex_field.field_value = kwargs[field] |
|
335 | 335 | self.sa.add(ex_field) |
|
336 | 336 | self.sa.add(cur_repo) |
|
337 | 337 | |
|
338 | 338 | if org_repo_name != new_name: |
|
339 | 339 | # rename repository |
|
340 | 340 | self.__rename_repo(old=org_repo_name, new=new_name) |
|
341 | 341 | |
|
342 | 342 | return cur_repo |
|
343 | 343 | except: |
|
344 | 344 | log.error(traceback.format_exc()) |
|
345 | 345 | raise |
|
346 | 346 | |
|
347 | 347 | def create_repo(self, repo_name, repo_type, description, owner, |
|
348 | 348 | private=False, clone_uri=None, repos_group=None, |
|
349 | 349 | landing_rev='tip', just_db=False, fork_of=None, |
|
350 | 350 | copy_fork_permissions=False, enable_statistics=False, |
|
351 | 351 | enable_locking=False, enable_downloads=False): |
|
352 | 352 | """ |
|
353 | 353 | Create repository |
|
354 | 354 | |
|
355 | 355 | """ |
|
356 | 356 | from rhodecode.model.scm import ScmModel |
|
357 | 357 | |
|
358 | 358 | owner = self._get_user(owner) |
|
359 | 359 | fork_of = self._get_repo(fork_of) |
|
360 | 360 | repos_group = self._get_repos_group(repos_group) |
|
361 | 361 | try: |
|
362 | 362 | |
|
363 | 363 | # repo name is just a name of repository |
|
364 | 364 | # while repo_name_full is a full qualified name that is combined |
|
365 | 365 | # with name and path of group |
|
366 | 366 | repo_name_full = repo_name |
|
367 | 367 | repo_name = repo_name.split(self.URL_SEPARATOR)[-1] |
|
368 | 368 | |
|
369 | 369 | new_repo = Repository() |
|
370 | 370 | new_repo.enable_statistics = False |
|
371 | 371 | new_repo.repo_name = repo_name_full |
|
372 | 372 | new_repo.repo_type = repo_type |
|
373 | 373 | new_repo.user = owner |
|
374 | 374 | new_repo.group = repos_group |
|
375 | 375 | new_repo.description = description or repo_name |
|
376 | 376 | new_repo.private = private |
|
377 | 377 | new_repo.clone_uri = clone_uri |
|
378 | 378 | new_repo.landing_rev = landing_rev |
|
379 | 379 | |
|
380 | 380 | new_repo.enable_statistics = enable_statistics |
|
381 | 381 | new_repo.enable_locking = enable_locking |
|
382 | 382 | new_repo.enable_downloads = enable_downloads |
|
383 | 383 | |
|
384 | 384 | if repos_group: |
|
385 | 385 | new_repo.enable_locking = repos_group.enable_locking |
|
386 | 386 | |
|
387 | 387 | if fork_of: |
|
388 | 388 | parent_repo = fork_of |
|
389 | 389 | new_repo.fork = parent_repo |
|
390 | 390 | |
|
391 | 391 | self.sa.add(new_repo) |
|
392 | 392 | |
|
393 | 393 | def _create_default_perms(): |
|
394 | 394 | # create default permission |
|
395 | 395 | repo_to_perm = UserRepoToPerm() |
|
396 | 396 | default = 'repository.read' |
|
397 | 397 | for p in User.get_by_username('default').user_perms: |
|
398 | 398 | if p.permission.permission_name.startswith('repository.'): |
|
399 | 399 | default = p.permission.permission_name |
|
400 | 400 | break |
|
401 | 401 | |
|
402 | 402 | default_perm = 'repository.none' if private else default |
|
403 | 403 | |
|
404 | 404 | repo_to_perm.permission_id = self.sa.query(Permission)\ |
|
405 | 405 | .filter(Permission.permission_name == default_perm)\ |
|
406 | 406 | .one().permission_id |
|
407 | 407 | |
|
408 | 408 | repo_to_perm.repository = new_repo |
|
409 | 409 | repo_to_perm.user_id = User.get_by_username('default').user_id |
|
410 | 410 | |
|
411 | 411 | self.sa.add(repo_to_perm) |
|
412 | 412 | |
|
413 | 413 | if fork_of: |
|
414 | 414 | if copy_fork_permissions: |
|
415 | 415 | repo = fork_of |
|
416 | 416 | user_perms = UserRepoToPerm.query()\ |
|
417 | 417 | .filter(UserRepoToPerm.repository == repo).all() |
|
418 | 418 | group_perms = UsersGroupRepoToPerm.query()\ |
|
419 | 419 | .filter(UsersGroupRepoToPerm.repository == repo).all() |
|
420 | 420 | |
|
421 | 421 | for perm in user_perms: |
|
422 | 422 | UserRepoToPerm.create(perm.user, new_repo, |
|
423 | 423 | perm.permission) |
|
424 | 424 | |
|
425 | 425 | for perm in group_perms: |
|
426 | 426 | UsersGroupRepoToPerm.create(perm.users_group, new_repo, |
|
427 | 427 | perm.permission) |
|
428 | 428 | else: |
|
429 | 429 | _create_default_perms() |
|
430 | 430 | else: |
|
431 | 431 | _create_default_perms() |
|
432 | 432 | |
|
433 | 433 | if not just_db: |
|
434 | 434 | self.__create_repo(repo_name, repo_type, |
|
435 | 435 | repos_group, |
|
436 | 436 | clone_uri) |
|
437 | 437 | log_create_repository(new_repo.get_dict(), |
|
438 | 438 | created_by=owner.username) |
|
439 | 439 | |
|
440 | 440 | # now automatically start following this repository as owner |
|
441 | 441 | ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, |
|
442 | 442 | owner.user_id) |
|
443 | 443 | return new_repo |
|
444 | 444 | except: |
|
445 | 445 | log.error(traceback.format_exc()) |
|
446 | 446 | raise |
|
447 | 447 | |
|
448 | 448 | def create(self, form_data, cur_user, just_db=False, fork=None): |
|
449 | 449 | """ |
|
450 | 450 | Backward compatibility function, just a wrapper on top of create_repo |
|
451 | 451 | |
|
452 | 452 | :param form_data: |
|
453 | 453 | :param cur_user: |
|
454 | 454 | :param just_db: |
|
455 | 455 | :param fork: |
|
456 | 456 | """ |
|
457 | 457 | owner = cur_user |
|
458 | 458 | repo_name = form_data['repo_name_full'] |
|
459 | 459 | repo_type = form_data['repo_type'] |
|
460 | 460 | description = form_data['repo_description'] |
|
461 | 461 | private = form_data['repo_private'] |
|
462 | 462 | clone_uri = form_data.get('clone_uri') |
|
463 | 463 | repos_group = form_data['repo_group'] |
|
464 | 464 | landing_rev = form_data['repo_landing_rev'] |
|
465 | 465 | copy_fork_permissions = form_data.get('copy_permissions') |
|
466 | 466 | fork_of = form_data.get('fork_parent_id') |
|
467 | 467 | |
|
468 | 468 | ## repo creation defaults, private and repo_type are filled in form |
|
469 | 469 | defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) |
|
470 | 470 | enable_statistics = defs.get('repo_enable_statistics') |
|
471 | 471 | enable_locking = defs.get('repo_enable_locking') |
|
472 | 472 | enable_downloads = defs.get('repo_enable_downloads') |
|
473 | 473 | |
|
474 | 474 | return self.create_repo( |
|
475 | 475 | repo_name, repo_type, description, owner, private, clone_uri, |
|
476 | 476 | repos_group, landing_rev, just_db, fork_of, copy_fork_permissions, |
|
477 | 477 | enable_statistics, enable_locking, enable_downloads |
|
478 | 478 | ) |
|
479 | 479 | |
|
480 | 480 | def create_fork(self, form_data, cur_user): |
|
481 | 481 | """ |
|
482 | 482 | Simple wrapper into executing celery task for fork creation |
|
483 | 483 | |
|
484 | 484 | :param form_data: |
|
485 | 485 | :param cur_user: |
|
486 | 486 | """ |
|
487 | 487 | from rhodecode.lib.celerylib import tasks, run_task |
|
488 | 488 | run_task(tasks.create_repo_fork, form_data, cur_user) |
|
489 | 489 | |
|
490 | 490 | def delete(self, repo): |
|
491 | 491 | repo = self._get_repo(repo) |
|
492 | 492 | if repo: |
|
493 | 493 | old_repo_dict = repo.get_dict() |
|
494 | 494 | owner = repo.user |
|
495 | 495 | try: |
|
496 | 496 | self.sa.delete(repo) |
|
497 | 497 | self.__delete_repo(repo) |
|
498 | 498 | log_delete_repository(old_repo_dict, |
|
499 | 499 | deleted_by=owner.username) |
|
500 | 500 | except: |
|
501 | 501 | log.error(traceback.format_exc()) |
|
502 | 502 | raise |
|
503 | 503 | |
|
504 | 504 | def grant_user_permission(self, repo, user, perm): |
|
505 | 505 | """ |
|
506 | 506 | Grant permission for user on given repository, or update existing one |
|
507 | 507 | if found |
|
508 | 508 | |
|
509 | 509 | :param repo: Instance of Repository, repository_id, or repository name |
|
510 | 510 | :param user: Instance of User, user_id or username |
|
511 | 511 | :param perm: Instance of Permission, or permission_name |
|
512 | 512 | """ |
|
513 | 513 | user = self._get_user(user) |
|
514 | 514 | repo = self._get_repo(repo) |
|
515 | 515 | permission = self._get_perm(perm) |
|
516 | 516 | |
|
517 | 517 | # check if we have that permission already |
|
518 | 518 | obj = self.sa.query(UserRepoToPerm)\ |
|
519 | 519 | .filter(UserRepoToPerm.user == user)\ |
|
520 | 520 | .filter(UserRepoToPerm.repository == repo)\ |
|
521 | 521 | .scalar() |
|
522 | 522 | if obj is None: |
|
523 | 523 | # create new ! |
|
524 | 524 | obj = UserRepoToPerm() |
|
525 | 525 | obj.repository = repo |
|
526 | 526 | obj.user = user |
|
527 | 527 | obj.permission = permission |
|
528 | 528 | self.sa.add(obj) |
|
529 | 529 | log.debug('Granted perm %s to %s on %s' % (perm, user, repo)) |
|
530 | 530 | |
|
531 | 531 | def revoke_user_permission(self, repo, user): |
|
532 | 532 | """ |
|
533 | 533 | Revoke permission for user on given repository |
|
534 | 534 | |
|
535 | 535 | :param repo: Instance of Repository, repository_id, or repository name |
|
536 | 536 | :param user: Instance of User, user_id or username |
|
537 | 537 | """ |
|
538 | 538 | |
|
539 | 539 | user = self._get_user(user) |
|
540 | 540 | repo = self._get_repo(repo) |
|
541 | 541 | |
|
542 | 542 | obj = self.sa.query(UserRepoToPerm)\ |
|
543 | 543 | .filter(UserRepoToPerm.repository == repo)\ |
|
544 | 544 | .filter(UserRepoToPerm.user == user)\ |
|
545 | 545 | .scalar() |
|
546 | 546 | if obj: |
|
547 | 547 | self.sa.delete(obj) |
|
548 | 548 | log.debug('Revoked perm on %s on %s' % (repo, user)) |
|
549 | 549 | |
|
550 | 550 | def grant_users_group_permission(self, repo, group_name, perm): |
|
551 | 551 | """ |
|
552 |
Grant permission for user |
|
|
552 | Grant permission for user group on given repository, or update | |
|
553 | 553 | existing one if found |
|
554 | 554 | |
|
555 | 555 | :param repo: Instance of Repository, repository_id, or repository name |
|
556 | 556 | :param group_name: Instance of UserGroup, users_group_id, |
|
557 |
or user |
|
|
557 | or user group name | |
|
558 | 558 | :param perm: Instance of Permission, or permission_name |
|
559 | 559 | """ |
|
560 | 560 | repo = self._get_repo(repo) |
|
561 | 561 | group_name = self.__get_users_group(group_name) |
|
562 | 562 | permission = self._get_perm(perm) |
|
563 | 563 | |
|
564 | 564 | # check if we have that permission already |
|
565 | 565 | obj = self.sa.query(UsersGroupRepoToPerm)\ |
|
566 | 566 | .filter(UsersGroupRepoToPerm.users_group == group_name)\ |
|
567 | 567 | .filter(UsersGroupRepoToPerm.repository == repo)\ |
|
568 | 568 | .scalar() |
|
569 | 569 | |
|
570 | 570 | if obj is None: |
|
571 | 571 | # create new |
|
572 | 572 | obj = UsersGroupRepoToPerm() |
|
573 | 573 | |
|
574 | 574 | obj.repository = repo |
|
575 | 575 | obj.users_group = group_name |
|
576 | 576 | obj.permission = permission |
|
577 | 577 | self.sa.add(obj) |
|
578 | 578 | log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo)) |
|
579 | 579 | |
|
580 | 580 | def revoke_users_group_permission(self, repo, group_name): |
|
581 | 581 | """ |
|
582 |
Revoke permission for user |
|
|
582 | Revoke permission for user group on given repository | |
|
583 | 583 | |
|
584 | 584 | :param repo: Instance of Repository, repository_id, or repository name |
|
585 | 585 | :param group_name: Instance of UserGroup, users_group_id, |
|
586 |
or user |
|
|
586 | or user group name | |
|
587 | 587 | """ |
|
588 | 588 | repo = self._get_repo(repo) |
|
589 | 589 | group_name = self.__get_users_group(group_name) |
|
590 | 590 | |
|
591 | 591 | obj = self.sa.query(UsersGroupRepoToPerm)\ |
|
592 | 592 | .filter(UsersGroupRepoToPerm.repository == repo)\ |
|
593 | 593 | .filter(UsersGroupRepoToPerm.users_group == group_name)\ |
|
594 | 594 | .scalar() |
|
595 | 595 | if obj: |
|
596 | 596 | self.sa.delete(obj) |
|
597 | 597 | log.debug('Revoked perm to %s on %s' % (repo, group_name)) |
|
598 | 598 | |
|
599 | 599 | def delete_stats(self, repo_name): |
|
600 | 600 | """ |
|
601 | 601 | removes stats for given repo |
|
602 | 602 | |
|
603 | 603 | :param repo_name: |
|
604 | 604 | """ |
|
605 | 605 | repo = self._get_repo(repo_name) |
|
606 | 606 | try: |
|
607 | 607 | obj = self.sa.query(Statistics)\ |
|
608 | 608 | .filter(Statistics.repository == repo).scalar() |
|
609 | 609 | if obj: |
|
610 | 610 | self.sa.delete(obj) |
|
611 | 611 | except: |
|
612 | 612 | log.error(traceback.format_exc()) |
|
613 | 613 | raise |
|
614 | 614 | |
|
615 | 615 | def __create_repo(self, repo_name, alias, parent, clone_uri=False): |
|
616 | 616 | """ |
|
617 | 617 | makes repository on filesystem. It's group aware means it'll create |
|
618 | 618 | a repository within a group, and alter the paths accordingly of |
|
619 | 619 | group location |
|
620 | 620 | |
|
621 | 621 | :param repo_name: |
|
622 | 622 | :param alias: |
|
623 | 623 | :param parent_id: |
|
624 | 624 | :param clone_uri: |
|
625 | 625 | """ |
|
626 | 626 | from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group |
|
627 | 627 | from rhodecode.model.scm import ScmModel |
|
628 | 628 | |
|
629 | 629 | if parent: |
|
630 | 630 | new_parent_path = os.sep.join(parent.full_path_splitted) |
|
631 | 631 | else: |
|
632 | 632 | new_parent_path = '' |
|
633 | 633 | |
|
634 | 634 | # we need to make it str for mercurial |
|
635 | 635 | repo_path = os.path.join(*map(lambda x: safe_str(x), |
|
636 | 636 | [self.repos_path, new_parent_path, repo_name])) |
|
637 | 637 | |
|
638 | 638 | # check if this path is not a repository |
|
639 | 639 | if is_valid_repo(repo_path, self.repos_path): |
|
640 | 640 | raise Exception('This path %s is a valid repository' % repo_path) |
|
641 | 641 | |
|
642 | 642 | # check if this path is a group |
|
643 | 643 | if is_valid_repos_group(repo_path, self.repos_path): |
|
644 | 644 | raise Exception('This path %s is a valid group' % repo_path) |
|
645 | 645 | |
|
646 | 646 | log.info('creating repo %s in %s @ %s' % ( |
|
647 | 647 | repo_name, safe_unicode(repo_path), clone_uri |
|
648 | 648 | ) |
|
649 | 649 | ) |
|
650 | 650 | backend = get_backend(alias) |
|
651 | 651 | if alias == 'hg': |
|
652 | 652 | backend(repo_path, create=True, src_url=clone_uri) |
|
653 | 653 | elif alias == 'git': |
|
654 | 654 | r = backend(repo_path, create=True, src_url=clone_uri, bare=True) |
|
655 | 655 | # add rhodecode hook into this repo |
|
656 | 656 | ScmModel().install_git_hook(repo=r) |
|
657 | 657 | else: |
|
658 | 658 | raise Exception('Undefined alias %s' % alias) |
|
659 | 659 | |
|
660 | 660 | def __rename_repo(self, old, new): |
|
661 | 661 | """ |
|
662 | 662 | renames repository on filesystem |
|
663 | 663 | |
|
664 | 664 | :param old: old name |
|
665 | 665 | :param new: new name |
|
666 | 666 | """ |
|
667 | 667 | log.info('renaming repo from %s to %s' % (old, new)) |
|
668 | 668 | |
|
669 | 669 | old_path = os.path.join(self.repos_path, old) |
|
670 | 670 | new_path = os.path.join(self.repos_path, new) |
|
671 | 671 | if os.path.isdir(new_path): |
|
672 | 672 | raise Exception( |
|
673 | 673 | 'Was trying to rename to already existing dir %s' % new_path |
|
674 | 674 | ) |
|
675 | 675 | shutil.move(old_path, new_path) |
|
676 | 676 | |
|
677 | 677 | def __delete_repo(self, repo): |
|
678 | 678 | """ |
|
679 | 679 | removes repo from filesystem, the removal is acctually made by |
|
680 | 680 | added rm__ prefix into dir, and rename internat .hg/.git dirs so this |
|
681 | 681 | repository is no longer valid for rhodecode, can be undeleted later on |
|
682 | 682 | by reverting the renames on this repository |
|
683 | 683 | |
|
684 | 684 | :param repo: repo object |
|
685 | 685 | """ |
|
686 | 686 | rm_path = os.path.join(self.repos_path, repo.repo_name) |
|
687 | 687 | log.info("Removing %s" % (rm_path)) |
|
688 | 688 | # disable hg/git internal that it doesn't get detected as repo |
|
689 | 689 | alias = repo.repo_type |
|
690 | 690 | |
|
691 | 691 | bare = getattr(repo.scm_instance, 'bare', False) |
|
692 | 692 | |
|
693 | 693 | if not bare: |
|
694 | 694 | # skip this for bare git repos |
|
695 | 695 | shutil.move(os.path.join(rm_path, '.%s' % alias), |
|
696 | 696 | os.path.join(rm_path, 'rm__.%s' % alias)) |
|
697 | 697 | # disable repo |
|
698 | 698 | _now = datetime.now() |
|
699 | 699 | _ms = str(_now.microsecond).rjust(6, '0') |
|
700 | 700 | _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms), |
|
701 | 701 | repo.just_name) |
|
702 | 702 | if repo.group: |
|
703 | 703 | args = repo.group.full_path_splitted + [_d] |
|
704 | 704 | _d = os.path.join(*args) |
|
705 | 705 | shutil.move(rm_path, os.path.join(self.repos_path, _d)) |
@@ -1,763 +1,763 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | rhodecode.model.user |
|
4 | 4 | ~~~~~~~~~~~~~~~~~~~~ |
|
5 | 5 | |
|
6 | 6 | users model for RhodeCode |
|
7 | 7 | |
|
8 | 8 | :created_on: Apr 9, 2010 |
|
9 | 9 | :author: marcink |
|
10 | 10 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> |
|
11 | 11 | :license: GPLv3, see COPYING for more details. |
|
12 | 12 | """ |
|
13 | 13 | # This program is free software: you can redistribute it and/or modify |
|
14 | 14 | # it under the terms of the GNU General Public License as published by |
|
15 | 15 | # the Free Software Foundation, either version 3 of the License, or |
|
16 | 16 | # (at your option) any later version. |
|
17 | 17 | # |
|
18 | 18 | # This program is distributed in the hope that it will be useful, |
|
19 | 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
20 | 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
21 | 21 | # GNU General Public License for more details. |
|
22 | 22 | # |
|
23 | 23 | # You should have received a copy of the GNU General Public License |
|
24 | 24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 | 25 | |
|
26 | 26 | import logging |
|
27 | 27 | import traceback |
|
28 | 28 | import itertools |
|
29 | 29 | import collections |
|
30 | 30 | from pylons import url |
|
31 | 31 | from pylons.i18n.translation import _ |
|
32 | 32 | |
|
33 | 33 | from sqlalchemy.exc import DatabaseError |
|
34 | 34 | from sqlalchemy.orm import joinedload |
|
35 | 35 | |
|
36 | 36 | from rhodecode.lib.utils2 import safe_unicode, generate_api_key |
|
37 | 37 | from rhodecode.lib.caching_query import FromCache |
|
38 | 38 | from rhodecode.model import BaseModel |
|
39 | 39 | from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ |
|
40 | 40 | UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ |
|
41 | 41 | Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \ |
|
42 | 42 | UserEmailMap, UserIpMap |
|
43 | 43 | from rhodecode.lib.exceptions import DefaultUserException, \ |
|
44 | 44 | UserOwnsReposException |
|
45 | 45 | from rhodecode.model.meta import Session |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | log = logging.getLogger(__name__) |
|
49 | 49 | |
|
50 | 50 | PERM_WEIGHTS = Permission.PERM_WEIGHTS |
|
51 | 51 | |
|
52 | 52 | |
|
53 | 53 | class UserModel(BaseModel): |
|
54 | 54 | cls = User |
|
55 | 55 | |
|
56 | 56 | def get(self, user_id, cache=False): |
|
57 | 57 | user = self.sa.query(User) |
|
58 | 58 | if cache: |
|
59 | 59 | user = user.options(FromCache("sql_cache_short", |
|
60 | 60 | "get_user_%s" % user_id)) |
|
61 | 61 | return user.get(user_id) |
|
62 | 62 | |
|
63 | 63 | def get_user(self, user): |
|
64 | 64 | return self._get_user(user) |
|
65 | 65 | |
|
66 | 66 | def get_by_username(self, username, cache=False, case_insensitive=False): |
|
67 | 67 | |
|
68 | 68 | if case_insensitive: |
|
69 | 69 | user = self.sa.query(User).filter(User.username.ilike(username)) |
|
70 | 70 | else: |
|
71 | 71 | user = self.sa.query(User)\ |
|
72 | 72 | .filter(User.username == username) |
|
73 | 73 | if cache: |
|
74 | 74 | user = user.options(FromCache("sql_cache_short", |
|
75 | 75 | "get_user_%s" % username)) |
|
76 | 76 | return user.scalar() |
|
77 | 77 | |
|
78 | 78 | def get_by_email(self, email, cache=False, case_insensitive=False): |
|
79 | 79 | return User.get_by_email(email, case_insensitive, cache) |
|
80 | 80 | |
|
81 | 81 | def get_by_api_key(self, api_key, cache=False): |
|
82 | 82 | return User.get_by_api_key(api_key, cache) |
|
83 | 83 | |
|
84 | 84 | def create(self, form_data): |
|
85 | 85 | from rhodecode.lib.auth import get_crypt_password |
|
86 | 86 | try: |
|
87 | 87 | new_user = User() |
|
88 | 88 | for k, v in form_data.items(): |
|
89 | 89 | if k == 'password': |
|
90 | 90 | v = get_crypt_password(v) |
|
91 | 91 | if k == 'firstname': |
|
92 | 92 | k = 'name' |
|
93 | 93 | setattr(new_user, k, v) |
|
94 | 94 | |
|
95 | 95 | new_user.api_key = generate_api_key(form_data['username']) |
|
96 | 96 | self.sa.add(new_user) |
|
97 | 97 | return new_user |
|
98 | 98 | except: |
|
99 | 99 | log.error(traceback.format_exc()) |
|
100 | 100 | raise |
|
101 | 101 | |
|
102 | 102 | def create_or_update(self, username, password, email, firstname='', |
|
103 | 103 | lastname='', active=True, admin=False, ldap_dn=None): |
|
104 | 104 | """ |
|
105 | 105 | Creates a new instance if not found, or updates current one |
|
106 | 106 | |
|
107 | 107 | :param username: |
|
108 | 108 | :param password: |
|
109 | 109 | :param email: |
|
110 | 110 | :param active: |
|
111 | 111 | :param firstname: |
|
112 | 112 | :param lastname: |
|
113 | 113 | :param active: |
|
114 | 114 | :param admin: |
|
115 | 115 | :param ldap_dn: |
|
116 | 116 | """ |
|
117 | 117 | |
|
118 | 118 | from rhodecode.lib.auth import get_crypt_password |
|
119 | 119 | |
|
120 | 120 | log.debug('Checking for %s account in RhodeCode database' % username) |
|
121 | 121 | user = User.get_by_username(username, case_insensitive=True) |
|
122 | 122 | if user is None: |
|
123 | 123 | log.debug('creating new user %s' % username) |
|
124 | 124 | new_user = User() |
|
125 | 125 | edit = False |
|
126 | 126 | else: |
|
127 | 127 | log.debug('updating user %s' % username) |
|
128 | 128 | new_user = user |
|
129 | 129 | edit = True |
|
130 | 130 | |
|
131 | 131 | try: |
|
132 | 132 | new_user.username = username |
|
133 | 133 | new_user.admin = admin |
|
134 | 134 | # set password only if creating an user or password is changed |
|
135 | 135 | if edit is False or user.password != password: |
|
136 | 136 | new_user.password = get_crypt_password(password) |
|
137 | 137 | new_user.api_key = generate_api_key(username) |
|
138 | 138 | new_user.email = email |
|
139 | 139 | new_user.active = active |
|
140 | 140 | new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None |
|
141 | 141 | new_user.name = firstname |
|
142 | 142 | new_user.lastname = lastname |
|
143 | 143 | self.sa.add(new_user) |
|
144 | 144 | return new_user |
|
145 | 145 | except (DatabaseError,): |
|
146 | 146 | log.error(traceback.format_exc()) |
|
147 | 147 | raise |
|
148 | 148 | |
|
149 | 149 | def create_for_container_auth(self, username, attrs): |
|
150 | 150 | """ |
|
151 | 151 | Creates the given user if it's not already in the database |
|
152 | 152 | |
|
153 | 153 | :param username: |
|
154 | 154 | :param attrs: |
|
155 | 155 | """ |
|
156 | 156 | if self.get_by_username(username, case_insensitive=True) is None: |
|
157 | 157 | |
|
158 | 158 | # autogenerate email for container account without one |
|
159 | 159 | generate_email = lambda usr: '%s@container_auth.account' % usr |
|
160 | 160 | |
|
161 | 161 | try: |
|
162 | 162 | new_user = User() |
|
163 | 163 | new_user.username = username |
|
164 | 164 | new_user.password = None |
|
165 | 165 | new_user.api_key = generate_api_key(username) |
|
166 | 166 | new_user.email = attrs['email'] |
|
167 | 167 | new_user.active = attrs.get('active', True) |
|
168 | 168 | new_user.name = attrs['name'] or generate_email(username) |
|
169 | 169 | new_user.lastname = attrs['lastname'] |
|
170 | 170 | |
|
171 | 171 | self.sa.add(new_user) |
|
172 | 172 | return new_user |
|
173 | 173 | except (DatabaseError,): |
|
174 | 174 | log.error(traceback.format_exc()) |
|
175 | 175 | self.sa.rollback() |
|
176 | 176 | raise |
|
177 | 177 | log.debug('User %s already exists. Skipping creation of account' |
|
178 | 178 | ' for container auth.', username) |
|
179 | 179 | return None |
|
180 | 180 | |
|
181 | 181 | def create_ldap(self, username, password, user_dn, attrs): |
|
182 | 182 | """ |
|
183 | 183 | Checks if user is in database, if not creates this user marked |
|
184 | 184 | as ldap user |
|
185 | 185 | |
|
186 | 186 | :param username: |
|
187 | 187 | :param password: |
|
188 | 188 | :param user_dn: |
|
189 | 189 | :param attrs: |
|
190 | 190 | """ |
|
191 | 191 | from rhodecode.lib.auth import get_crypt_password |
|
192 | 192 | log.debug('Checking for such ldap account in RhodeCode database') |
|
193 | 193 | if self.get_by_username(username, case_insensitive=True) is None: |
|
194 | 194 | |
|
195 | 195 | # autogenerate email for ldap account without one |
|
196 | 196 | generate_email = lambda usr: '%s@ldap.account' % usr |
|
197 | 197 | |
|
198 | 198 | try: |
|
199 | 199 | new_user = User() |
|
200 | 200 | username = username.lower() |
|
201 | 201 | # add ldap account always lowercase |
|
202 | 202 | new_user.username = username |
|
203 | 203 | new_user.password = get_crypt_password(password) |
|
204 | 204 | new_user.api_key = generate_api_key(username) |
|
205 | 205 | new_user.email = attrs['email'] or generate_email(username) |
|
206 | 206 | new_user.active = attrs.get('active', True) |
|
207 | 207 | new_user.ldap_dn = safe_unicode(user_dn) |
|
208 | 208 | new_user.name = attrs['name'] |
|
209 | 209 | new_user.lastname = attrs['lastname'] |
|
210 | 210 | |
|
211 | 211 | self.sa.add(new_user) |
|
212 | 212 | return new_user |
|
213 | 213 | except (DatabaseError,): |
|
214 | 214 | log.error(traceback.format_exc()) |
|
215 | 215 | self.sa.rollback() |
|
216 | 216 | raise |
|
217 | 217 | log.debug('this %s user exists skipping creation of ldap account', |
|
218 | 218 | username) |
|
219 | 219 | return None |
|
220 | 220 | |
|
221 | 221 | def create_registration(self, form_data): |
|
222 | 222 | from rhodecode.model.notification import NotificationModel |
|
223 | 223 | |
|
224 | 224 | try: |
|
225 | 225 | form_data['admin'] = False |
|
226 | 226 | new_user = self.create(form_data) |
|
227 | 227 | |
|
228 | 228 | self.sa.add(new_user) |
|
229 | 229 | self.sa.flush() |
|
230 | 230 | |
|
231 | 231 | # notification to admins |
|
232 | 232 | subject = _('new user registration') |
|
233 | 233 | body = ('New user registration\n' |
|
234 | 234 | '---------------------\n' |
|
235 | 235 | '- Username: %s\n' |
|
236 | 236 | '- Full Name: %s\n' |
|
237 | 237 | '- Email: %s\n') |
|
238 | 238 | body = body % (new_user.username, new_user.full_name, |
|
239 | 239 | new_user.email) |
|
240 | 240 | edit_url = url('edit_user', id=new_user.user_id, qualified=True) |
|
241 | 241 | kw = {'registered_user_url': edit_url} |
|
242 | 242 | NotificationModel().create(created_by=new_user, subject=subject, |
|
243 | 243 | body=body, recipients=None, |
|
244 | 244 | type_=Notification.TYPE_REGISTRATION, |
|
245 | 245 | email_kwargs=kw) |
|
246 | 246 | |
|
247 | 247 | except: |
|
248 | 248 | log.error(traceback.format_exc()) |
|
249 | 249 | raise |
|
250 | 250 | |
|
251 | 251 | def update(self, user_id, form_data, skip_attrs=[]): |
|
252 | 252 | from rhodecode.lib.auth import get_crypt_password |
|
253 | 253 | try: |
|
254 | 254 | user = self.get(user_id, cache=False) |
|
255 | 255 | if user.username == 'default': |
|
256 | 256 | raise DefaultUserException( |
|
257 | 257 | _("You can't Edit this user since it's" |
|
258 | 258 | " crucial for entire application")) |
|
259 | 259 | |
|
260 | 260 | for k, v in form_data.items(): |
|
261 | 261 | if k in skip_attrs: |
|
262 | 262 | continue |
|
263 | 263 | if k == 'new_password' and v: |
|
264 | 264 | user.password = get_crypt_password(v) |
|
265 | 265 | user.api_key = generate_api_key(user.username) |
|
266 | 266 | else: |
|
267 | 267 | if k == 'firstname': |
|
268 | 268 | k = 'name' |
|
269 | 269 | setattr(user, k, v) |
|
270 | 270 | self.sa.add(user) |
|
271 | 271 | except: |
|
272 | 272 | log.error(traceback.format_exc()) |
|
273 | 273 | raise |
|
274 | 274 | |
|
275 | 275 | def update_user(self, user, **kwargs): |
|
276 | 276 | from rhodecode.lib.auth import get_crypt_password |
|
277 | 277 | try: |
|
278 | 278 | user = self._get_user(user) |
|
279 | 279 | if user.username == 'default': |
|
280 | 280 | raise DefaultUserException( |
|
281 | 281 | _("You can't Edit this user since it's" |
|
282 | 282 | " crucial for entire application") |
|
283 | 283 | ) |
|
284 | 284 | |
|
285 | 285 | for k, v in kwargs.items(): |
|
286 | 286 | if k == 'password' and v: |
|
287 | 287 | v = get_crypt_password(v) |
|
288 | 288 | user.api_key = generate_api_key(user.username) |
|
289 | 289 | |
|
290 | 290 | setattr(user, k, v) |
|
291 | 291 | self.sa.add(user) |
|
292 | 292 | return user |
|
293 | 293 | except: |
|
294 | 294 | log.error(traceback.format_exc()) |
|
295 | 295 | raise |
|
296 | 296 | |
|
297 | 297 | def delete(self, user): |
|
298 | 298 | user = self._get_user(user) |
|
299 | 299 | |
|
300 | 300 | try: |
|
301 | 301 | if user.username == 'default': |
|
302 | 302 | raise DefaultUserException( |
|
303 | 303 | _(u"You can't remove this user since it's" |
|
304 | 304 | " crucial for entire application") |
|
305 | 305 | ) |
|
306 | 306 | if user.repositories: |
|
307 | 307 | repos = [x.repo_name for x in user.repositories] |
|
308 | 308 | raise UserOwnsReposException( |
|
309 | 309 | _(u'user "%s" still owns %s repositories and cannot be ' |
|
310 | 310 | 'removed. Switch owners or remove those repositories. %s') |
|
311 | 311 | % (user.username, len(repos), ', '.join(repos)) |
|
312 | 312 | ) |
|
313 | 313 | self.sa.delete(user) |
|
314 | 314 | except: |
|
315 | 315 | log.error(traceback.format_exc()) |
|
316 | 316 | raise |
|
317 | 317 | |
|
318 | 318 | def reset_password_link(self, data): |
|
319 | 319 | from rhodecode.lib.celerylib import tasks, run_task |
|
320 | 320 | from rhodecode.model.notification import EmailNotificationModel |
|
321 | 321 | user_email = data['email'] |
|
322 | 322 | try: |
|
323 | 323 | user = User.get_by_email(user_email) |
|
324 | 324 | if user: |
|
325 | 325 | log.debug('password reset user found %s' % user) |
|
326 | 326 | link = url('reset_password_confirmation', key=user.api_key, |
|
327 | 327 | qualified=True) |
|
328 | 328 | reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET |
|
329 | 329 | body = EmailNotificationModel().get_email_tmpl(reg_type, |
|
330 | 330 | **{'user': user.short_contact, |
|
331 | 331 | 'reset_url': link}) |
|
332 | 332 | log.debug('sending email') |
|
333 | 333 | run_task(tasks.send_email, user_email, |
|
334 | 334 | _("password reset link"), body, body) |
|
335 | 335 | log.info('send new password mail to %s' % user_email) |
|
336 | 336 | else: |
|
337 | 337 | log.debug("password reset email %s not found" % user_email) |
|
338 | 338 | except: |
|
339 | 339 | log.error(traceback.format_exc()) |
|
340 | 340 | return False |
|
341 | 341 | |
|
342 | 342 | return True |
|
343 | 343 | |
|
344 | 344 | def reset_password(self, data): |
|
345 | 345 | from rhodecode.lib.celerylib import tasks, run_task |
|
346 | 346 | from rhodecode.lib import auth |
|
347 | 347 | user_email = data['email'] |
|
348 | 348 | try: |
|
349 | 349 | try: |
|
350 | 350 | user = User.get_by_email(user_email) |
|
351 | 351 | new_passwd = auth.PasswordGenerator().gen_password(8, |
|
352 | 352 | auth.PasswordGenerator.ALPHABETS_BIG_SMALL) |
|
353 | 353 | if user: |
|
354 | 354 | user.password = auth.get_crypt_password(new_passwd) |
|
355 | 355 | user.api_key = auth.generate_api_key(user.username) |
|
356 | 356 | Session().add(user) |
|
357 | 357 | Session().commit() |
|
358 | 358 | log.info('change password for %s' % user_email) |
|
359 | 359 | if new_passwd is None: |
|
360 | 360 | raise Exception('unable to generate new password') |
|
361 | 361 | except: |
|
362 | 362 | log.error(traceback.format_exc()) |
|
363 | 363 | Session().rollback() |
|
364 | 364 | |
|
365 | 365 | run_task(tasks.send_email, user_email, |
|
366 | 366 | _('Your new password'), |
|
367 | 367 | _('Your new RhodeCode password:%s') % (new_passwd)) |
|
368 | 368 | log.info('send new password mail to %s' % user_email) |
|
369 | 369 | |
|
370 | 370 | except: |
|
371 | 371 | log.error('Failed to update user password') |
|
372 | 372 | log.error(traceback.format_exc()) |
|
373 | 373 | |
|
374 | 374 | return True |
|
375 | 375 | |
|
376 | 376 | def fill_data(self, auth_user, user_id=None, api_key=None): |
|
377 | 377 | """ |
|
378 | 378 | Fetches auth_user by user_id,or api_key if present. |
|
379 | 379 | Fills auth_user attributes with those taken from database. |
|
380 | 380 | Additionally set's is_authenitated if lookup fails |
|
381 | 381 | present in database |
|
382 | 382 | |
|
383 | 383 | :param auth_user: instance of user to set attributes |
|
384 | 384 | :param user_id: user id to fetch by |
|
385 | 385 | :param api_key: api key to fetch by |
|
386 | 386 | """ |
|
387 | 387 | if user_id is None and api_key is None: |
|
388 | 388 | raise Exception('You need to pass user_id or api_key') |
|
389 | 389 | |
|
390 | 390 | try: |
|
391 | 391 | if api_key: |
|
392 | 392 | dbuser = self.get_by_api_key(api_key) |
|
393 | 393 | else: |
|
394 | 394 | dbuser = self.get(user_id) |
|
395 | 395 | |
|
396 | 396 | if dbuser is not None and dbuser.active: |
|
397 | 397 | log.debug('filling %s data' % dbuser) |
|
398 | 398 | for k, v in dbuser.get_dict().items(): |
|
399 | 399 | setattr(auth_user, k, v) |
|
400 | 400 | else: |
|
401 | 401 | return False |
|
402 | 402 | |
|
403 | 403 | except: |
|
404 | 404 | log.error(traceback.format_exc()) |
|
405 | 405 | auth_user.is_authenticated = False |
|
406 | 406 | return False |
|
407 | 407 | |
|
408 | 408 | return True |
|
409 | 409 | |
|
410 | 410 | def fill_perms(self, user, explicit=True, algo='higherwin'): |
|
411 | 411 | """ |
|
412 | 412 | Fills user permission attribute with permissions taken from database |
|
413 | 413 | works for permissions given for repositories, and for permissions that |
|
414 | 414 | are granted to groups |
|
415 | 415 | |
|
416 | 416 | :param user: user instance to fill his perms |
|
417 | 417 | :param explicit: In case there are permissions both for user and a group |
|
418 | 418 | that user is part of, explicit flag will defiine if user will |
|
419 | 419 | explicitly override permissions from group, if it's False it will |
|
420 | 420 | make decision based on the algo |
|
421 | 421 | :param algo: algorithm to decide what permission should be choose if |
|
422 | 422 | it's multiple defined, eg user in two different groups. It also |
|
423 | 423 | decides if explicit flag is turned off how to specify the permission |
|
424 | 424 | for case when user is in a group + have defined separate permission |
|
425 | 425 | """ |
|
426 | 426 | RK = 'repositories' |
|
427 | 427 | GK = 'repositories_groups' |
|
428 | 428 | GLOBAL = 'global' |
|
429 | 429 | user.permissions[RK] = {} |
|
430 | 430 | user.permissions[GK] = {} |
|
431 | 431 | user.permissions[GLOBAL] = set() |
|
432 | 432 | |
|
433 | 433 | def _choose_perm(new_perm, cur_perm): |
|
434 | 434 | new_perm_val = PERM_WEIGHTS[new_perm] |
|
435 | 435 | cur_perm_val = PERM_WEIGHTS[cur_perm] |
|
436 | 436 | if algo == 'higherwin': |
|
437 | 437 | if new_perm_val > cur_perm_val: |
|
438 | 438 | return new_perm |
|
439 | 439 | return cur_perm |
|
440 | 440 | elif algo == 'lowerwin': |
|
441 | 441 | if new_perm_val < cur_perm_val: |
|
442 | 442 | return new_perm |
|
443 | 443 | return cur_perm |
|
444 | 444 | |
|
445 | 445 | #====================================================================== |
|
446 | 446 | # fetch default permissions |
|
447 | 447 | #====================================================================== |
|
448 | 448 | default_user = User.get_by_username('default', cache=True) |
|
449 | 449 | default_user_id = default_user.user_id |
|
450 | 450 | |
|
451 | 451 | default_repo_perms = Permission.get_default_perms(default_user_id) |
|
452 | 452 | default_repo_groups_perms = Permission.get_default_group_perms(default_user_id) |
|
453 | 453 | |
|
454 | 454 | if user.is_admin: |
|
455 | 455 | #================================================================== |
|
456 | 456 | # admin user have all default rights for repositories |
|
457 | 457 | # and groups set to admin |
|
458 | 458 | #================================================================== |
|
459 | 459 | user.permissions[GLOBAL].add('hg.admin') |
|
460 | 460 | |
|
461 | 461 | # repositories |
|
462 | 462 | for perm in default_repo_perms: |
|
463 | 463 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
464 | 464 | p = 'repository.admin' |
|
465 | 465 | user.permissions[RK][r_k] = p |
|
466 | 466 | |
|
467 | 467 | # repository groups |
|
468 | 468 | for perm in default_repo_groups_perms: |
|
469 | 469 | rg_k = perm.UserRepoGroupToPerm.group.group_name |
|
470 | 470 | p = 'group.admin' |
|
471 | 471 | user.permissions[GK][rg_k] = p |
|
472 | 472 | return user |
|
473 | 473 | |
|
474 | 474 | #================================================================== |
|
475 | 475 | # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS |
|
476 | 476 | #================================================================== |
|
477 | 477 | uid = user.user_id |
|
478 | 478 | |
|
479 | 479 | # default global permissions taken fron the default user |
|
480 | 480 | default_global_perms = self.sa.query(UserToPerm)\ |
|
481 | 481 | .filter(UserToPerm.user_id == default_user_id) |
|
482 | 482 | |
|
483 | 483 | for perm in default_global_perms: |
|
484 | 484 | user.permissions[GLOBAL].add(perm.permission.permission_name) |
|
485 | 485 | |
|
486 | 486 | # defaults for repositories, taken from default user |
|
487 | 487 | for perm in default_repo_perms: |
|
488 | 488 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
489 | 489 | if perm.Repository.private and not (perm.Repository.user_id == uid): |
|
490 | 490 | # disable defaults for private repos, |
|
491 | 491 | p = 'repository.none' |
|
492 | 492 | elif perm.Repository.user_id == uid: |
|
493 | 493 | # set admin if owner |
|
494 | 494 | p = 'repository.admin' |
|
495 | 495 | else: |
|
496 | 496 | p = perm.Permission.permission_name |
|
497 | 497 | |
|
498 | 498 | user.permissions[RK][r_k] = p |
|
499 | 499 | |
|
500 | 500 | # defaults for repository groups taken from default user permission |
|
501 | 501 | # on given group |
|
502 | 502 | for perm in default_repo_groups_perms: |
|
503 | 503 | rg_k = perm.UserRepoGroupToPerm.group.group_name |
|
504 | 504 | p = perm.Permission.permission_name |
|
505 | 505 | user.permissions[GK][rg_k] = p |
|
506 | 506 | |
|
507 | 507 | #====================================================================== |
|
508 | 508 | # !! OVERRIDE GLOBALS !! with user permissions if any found |
|
509 | 509 | #====================================================================== |
|
510 | 510 | # those can be configured from groups or users explicitly |
|
511 | 511 | _configurable = set(['hg.fork.none', 'hg.fork.repository', |
|
512 | 512 | 'hg.create.none', 'hg.create.repository']) |
|
513 | 513 | |
|
514 | 514 | # USER GROUPS comes first |
|
515 |
# user |
|
|
515 | # user group global permissions | |
|
516 | 516 | user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\ |
|
517 | 517 | .options(joinedload(UsersGroupToPerm.permission))\ |
|
518 | 518 | .join((UsersGroupMember, UsersGroupToPerm.users_group_id == |
|
519 | 519 | UsersGroupMember.users_group_id))\ |
|
520 | 520 | .filter(UsersGroupMember.user_id == uid)\ |
|
521 | 521 | .order_by(UsersGroupToPerm.users_group_id)\ |
|
522 | 522 | .all() |
|
523 | 523 | #need to group here by groups since user can be in more than one group |
|
524 | 524 | _grouped = [[x, list(y)] for x, y in |
|
525 | 525 | itertools.groupby(user_perms_from_users_groups, |
|
526 | 526 | lambda x:x.users_group)] |
|
527 | 527 | for gr, perms in _grouped: |
|
528 | 528 | # since user can be in multiple groups iterate over them and |
|
529 | 529 | # select the lowest permissions first (more explicit) |
|
530 | 530 | ##TODO: do this^^ |
|
531 | 531 | if not gr.inherit_default_permissions: |
|
532 | 532 | # NEED TO IGNORE all configurable permissions and |
|
533 | 533 | # replace them with explicitly set |
|
534 | 534 | user.permissions[GLOBAL] = user.permissions[GLOBAL]\ |
|
535 | 535 | .difference(_configurable) |
|
536 | 536 | for perm in perms: |
|
537 | 537 | user.permissions[GLOBAL].add(perm.permission.permission_name) |
|
538 | 538 | |
|
539 | 539 | # user specific global permissions |
|
540 | 540 | user_perms = self.sa.query(UserToPerm)\ |
|
541 | 541 | .options(joinedload(UserToPerm.permission))\ |
|
542 | 542 | .filter(UserToPerm.user_id == uid).all() |
|
543 | 543 | |
|
544 | 544 | if not user.inherit_default_permissions: |
|
545 | 545 | # NEED TO IGNORE all configurable permissions and |
|
546 | 546 | # replace them with explicitly set |
|
547 | 547 | user.permissions[GLOBAL] = user.permissions[GLOBAL]\ |
|
548 | 548 | .difference(_configurable) |
|
549 | 549 | |
|
550 | 550 | for perm in user_perms: |
|
551 | 551 | user.permissions[GLOBAL].add(perm.permission.permission_name) |
|
552 | 552 | |
|
553 | 553 | #====================================================================== |
|
554 | 554 | # !! PERMISSIONS FOR REPOSITORIES !! |
|
555 | 555 | #====================================================================== |
|
556 | 556 | #====================================================================== |
|
557 | 557 | # check if user is part of user groups for this repository and |
|
558 | 558 | # fill in his permission from it. _choose_perm decides of which |
|
559 | 559 | # permission should be selected based on selected method |
|
560 | 560 | #====================================================================== |
|
561 | 561 | |
|
562 |
# user |
|
|
562 | # user group for repositories permissions | |
|
563 | 563 | user_repo_perms_from_users_groups = \ |
|
564 | 564 | self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\ |
|
565 | 565 | .join((Repository, UsersGroupRepoToPerm.repository_id == |
|
566 | 566 | Repository.repo_id))\ |
|
567 | 567 | .join((Permission, UsersGroupRepoToPerm.permission_id == |
|
568 | 568 | Permission.permission_id))\ |
|
569 | 569 | .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == |
|
570 | 570 | UsersGroupMember.users_group_id))\ |
|
571 | 571 | .filter(UsersGroupMember.user_id == uid)\ |
|
572 | 572 | .all() |
|
573 | 573 | |
|
574 | 574 | multiple_counter = collections.defaultdict(int) |
|
575 | 575 | for perm in user_repo_perms_from_users_groups: |
|
576 | 576 | r_k = perm.UsersGroupRepoToPerm.repository.repo_name |
|
577 | 577 | multiple_counter[r_k] += 1 |
|
578 | 578 | p = perm.Permission.permission_name |
|
579 | 579 | cur_perm = user.permissions[RK][r_k] |
|
580 | 580 | |
|
581 | 581 | if perm.Repository.user_id == uid: |
|
582 | 582 | # set admin if owner |
|
583 | 583 | p = 'repository.admin' |
|
584 | 584 | else: |
|
585 | 585 | if multiple_counter[r_k] > 1: |
|
586 | 586 | p = _choose_perm(p, cur_perm) |
|
587 | 587 | user.permissions[RK][r_k] = p |
|
588 | 588 | |
|
589 | 589 | # user explicit permissions for repositories, overrides any specified |
|
590 | 590 | # by the group permission |
|
591 | 591 | user_repo_perms = \ |
|
592 | 592 | self.sa.query(UserRepoToPerm, Permission, Repository)\ |
|
593 | 593 | .join((Repository, UserRepoToPerm.repository_id == |
|
594 | 594 | Repository.repo_id))\ |
|
595 | 595 | .join((Permission, UserRepoToPerm.permission_id == |
|
596 | 596 | Permission.permission_id))\ |
|
597 | 597 | .filter(UserRepoToPerm.user_id == uid)\ |
|
598 | 598 | .all() |
|
599 | 599 | |
|
600 | 600 | for perm in user_repo_perms: |
|
601 | 601 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
602 | 602 | cur_perm = user.permissions[RK][r_k] |
|
603 | 603 | # set admin if owner |
|
604 | 604 | if perm.Repository.user_id == uid: |
|
605 | 605 | p = 'repository.admin' |
|
606 | 606 | else: |
|
607 | 607 | p = perm.Permission.permission_name |
|
608 | 608 | if not explicit: |
|
609 | 609 | p = _choose_perm(p, cur_perm) |
|
610 | 610 | user.permissions[RK][r_k] = p |
|
611 | 611 | |
|
612 | 612 | #====================================================================== |
|
613 | 613 | # !! PERMISSIONS FOR REPOSITORY GROUPS !! |
|
614 | 614 | #====================================================================== |
|
615 | 615 | #====================================================================== |
|
616 | 616 | # check if user is part of user groups for this repository groups and |
|
617 | 617 | # fill in his permission from it. _choose_perm decides of which |
|
618 | 618 | # permission should be selected based on selected method |
|
619 | 619 | #====================================================================== |
|
620 |
# user |
|
|
620 | # user group for repo groups permissions | |
|
621 | 621 | user_repo_group_perms_from_users_groups = \ |
|
622 | 622 | self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\ |
|
623 | 623 | .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\ |
|
624 | 624 | .join((Permission, UsersGroupRepoGroupToPerm.permission_id |
|
625 | 625 | == Permission.permission_id))\ |
|
626 | 626 | .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id |
|
627 | 627 | == UsersGroupMember.users_group_id))\ |
|
628 | 628 | .filter(UsersGroupMember.user_id == uid)\ |
|
629 | 629 | .all() |
|
630 | 630 | |
|
631 | 631 | multiple_counter = collections.defaultdict(int) |
|
632 | 632 | for perm in user_repo_group_perms_from_users_groups: |
|
633 | 633 | g_k = perm.UsersGroupRepoGroupToPerm.group.group_name |
|
634 | 634 | multiple_counter[g_k] += 1 |
|
635 | 635 | p = perm.Permission.permission_name |
|
636 | 636 | cur_perm = user.permissions[GK][g_k] |
|
637 | 637 | if multiple_counter[g_k] > 1: |
|
638 | 638 | p = _choose_perm(p, cur_perm) |
|
639 | 639 | user.permissions[GK][g_k] = p |
|
640 | 640 | |
|
641 | 641 | # user explicit permissions for repository groups |
|
642 | 642 | user_repo_groups_perms = \ |
|
643 | 643 | self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\ |
|
644 | 644 | .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ |
|
645 | 645 | .join((Permission, UserRepoGroupToPerm.permission_id |
|
646 | 646 | == Permission.permission_id))\ |
|
647 | 647 | .filter(UserRepoGroupToPerm.user_id == uid)\ |
|
648 | 648 | .all() |
|
649 | 649 | |
|
650 | 650 | for perm in user_repo_groups_perms: |
|
651 | 651 | rg_k = perm.UserRepoGroupToPerm.group.group_name |
|
652 | 652 | p = perm.Permission.permission_name |
|
653 | 653 | cur_perm = user.permissions[GK][rg_k] |
|
654 | 654 | if not explicit: |
|
655 | 655 | p = _choose_perm(p, cur_perm) |
|
656 | 656 | user.permissions[GK][rg_k] = p |
|
657 | 657 | |
|
658 | 658 | return user |
|
659 | 659 | |
|
660 | 660 | def has_perm(self, user, perm): |
|
661 | 661 | perm = self._get_perm(perm) |
|
662 | 662 | user = self._get_user(user) |
|
663 | 663 | |
|
664 | 664 | return UserToPerm.query().filter(UserToPerm.user == user)\ |
|
665 | 665 | .filter(UserToPerm.permission == perm).scalar() is not None |
|
666 | 666 | |
|
667 | 667 | def grant_perm(self, user, perm): |
|
668 | 668 | """ |
|
669 | 669 | Grant user global permissions |
|
670 | 670 | |
|
671 | 671 | :param user: |
|
672 | 672 | :param perm: |
|
673 | 673 | """ |
|
674 | 674 | user = self._get_user(user) |
|
675 | 675 | perm = self._get_perm(perm) |
|
676 | 676 | # if this permission is already granted skip it |
|
677 | 677 | _perm = UserToPerm.query()\ |
|
678 | 678 | .filter(UserToPerm.user == user)\ |
|
679 | 679 | .filter(UserToPerm.permission == perm)\ |
|
680 | 680 | .scalar() |
|
681 | 681 | if _perm: |
|
682 | 682 | return |
|
683 | 683 | new = UserToPerm() |
|
684 | 684 | new.user = user |
|
685 | 685 | new.permission = perm |
|
686 | 686 | self.sa.add(new) |
|
687 | 687 | |
|
688 | 688 | def revoke_perm(self, user, perm): |
|
689 | 689 | """ |
|
690 | 690 | Revoke users global permissions |
|
691 | 691 | |
|
692 | 692 | :param user: |
|
693 | 693 | :param perm: |
|
694 | 694 | """ |
|
695 | 695 | user = self._get_user(user) |
|
696 | 696 | perm = self._get_perm(perm) |
|
697 | 697 | |
|
698 | 698 | obj = UserToPerm.query()\ |
|
699 | 699 | .filter(UserToPerm.user == user)\ |
|
700 | 700 | .filter(UserToPerm.permission == perm)\ |
|
701 | 701 | .scalar() |
|
702 | 702 | if obj: |
|
703 | 703 | self.sa.delete(obj) |
|
704 | 704 | |
|
705 | 705 | def add_extra_email(self, user, email): |
|
706 | 706 | """ |
|
707 | 707 | Adds email address to UserEmailMap |
|
708 | 708 | |
|
709 | 709 | :param user: |
|
710 | 710 | :param email: |
|
711 | 711 | """ |
|
712 | 712 | from rhodecode.model import forms |
|
713 | 713 | form = forms.UserExtraEmailForm()() |
|
714 | 714 | data = form.to_python(dict(email=email)) |
|
715 | 715 | user = self._get_user(user) |
|
716 | 716 | |
|
717 | 717 | obj = UserEmailMap() |
|
718 | 718 | obj.user = user |
|
719 | 719 | obj.email = data['email'] |
|
720 | 720 | self.sa.add(obj) |
|
721 | 721 | return obj |
|
722 | 722 | |
|
723 | 723 | def delete_extra_email(self, user, email_id): |
|
724 | 724 | """ |
|
725 | 725 | Removes email address from UserEmailMap |
|
726 | 726 | |
|
727 | 727 | :param user: |
|
728 | 728 | :param email_id: |
|
729 | 729 | """ |
|
730 | 730 | user = self._get_user(user) |
|
731 | 731 | obj = UserEmailMap.query().get(email_id) |
|
732 | 732 | if obj: |
|
733 | 733 | self.sa.delete(obj) |
|
734 | 734 | |
|
735 | 735 | def add_extra_ip(self, user, ip): |
|
736 | 736 | """ |
|
737 | 737 | Adds ip address to UserIpMap |
|
738 | 738 | |
|
739 | 739 | :param user: |
|
740 | 740 | :param ip: |
|
741 | 741 | """ |
|
742 | 742 | from rhodecode.model import forms |
|
743 | 743 | form = forms.UserExtraIpForm()() |
|
744 | 744 | data = form.to_python(dict(ip=ip)) |
|
745 | 745 | user = self._get_user(user) |
|
746 | 746 | |
|
747 | 747 | obj = UserIpMap() |
|
748 | 748 | obj.user = user |
|
749 | 749 | obj.ip_addr = data['ip'] |
|
750 | 750 | self.sa.add(obj) |
|
751 | 751 | return obj |
|
752 | 752 | |
|
753 | 753 | def delete_extra_ip(self, user, ip_id): |
|
754 | 754 | """ |
|
755 | 755 | Removes ip address from UserIpMap |
|
756 | 756 | |
|
757 | 757 | :param user: |
|
758 | 758 | :param ip_id: |
|
759 | 759 | """ |
|
760 | 760 | user = self._get_user(user) |
|
761 | 761 | obj = UserIpMap.query().get(ip_id) |
|
762 | 762 | if obj: |
|
763 | 763 | self.sa.delete(obj) |
@@ -1,809 +1,809 b'' | |||
|
1 | 1 | """ |
|
2 | 2 | Set of generic validators |
|
3 | 3 | """ |
|
4 | 4 | import os |
|
5 | 5 | import re |
|
6 | 6 | import formencode |
|
7 | 7 | import logging |
|
8 | 8 | from collections import defaultdict |
|
9 | 9 | from pylons.i18n.translation import _ |
|
10 | 10 | from webhelpers.pylonslib.secure_form import authentication_token |
|
11 | 11 | |
|
12 | 12 | from formencode.validators import ( |
|
13 | 13 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, |
|
14 | 14 | NotEmpty, IPAddress, CIDR |
|
15 | 15 | ) |
|
16 | 16 | from rhodecode.lib.compat import OrderedSet |
|
17 | 17 | from rhodecode.lib import ipaddr |
|
18 | 18 | from rhodecode.lib.utils import repo_name_slug |
|
19 | 19 | from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\ |
|
20 | 20 | ChangesetStatus |
|
21 | 21 | from rhodecode.lib.exceptions import LdapImportError |
|
22 | 22 | from rhodecode.config.routing import ADMIN_PREFIX |
|
23 | 23 | from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny |
|
24 | 24 | |
|
25 | 25 | # silence warnings and pylint |
|
26 | 26 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \ |
|
27 | 27 | NotEmpty, IPAddress, CIDR |
|
28 | 28 | |
|
29 | 29 | log = logging.getLogger(__name__) |
|
30 | 30 | |
|
31 | 31 | |
|
32 | 32 | class UniqueList(formencode.FancyValidator): |
|
33 | 33 | """ |
|
34 | 34 | Unique List ! |
|
35 | 35 | """ |
|
36 | 36 | messages = dict( |
|
37 | 37 | empty=_('Value cannot be an empty list'), |
|
38 | 38 | missing_value=_('Value cannot be an empty list'), |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | def _to_python(self, value, state): |
|
42 | 42 | if isinstance(value, list): |
|
43 | 43 | return value |
|
44 | 44 | elif isinstance(value, set): |
|
45 | 45 | return list(value) |
|
46 | 46 | elif isinstance(value, tuple): |
|
47 | 47 | return list(value) |
|
48 | 48 | elif value is None: |
|
49 | 49 | return [] |
|
50 | 50 | else: |
|
51 | 51 | return [value] |
|
52 | 52 | |
|
53 | 53 | def empty_value(self, value): |
|
54 | 54 | return [] |
|
55 | 55 | |
|
56 | 56 | |
|
57 | 57 | class StateObj(object): |
|
58 | 58 | """ |
|
59 | 59 | this is needed to translate the messages using _() in validators |
|
60 | 60 | """ |
|
61 | 61 | _ = staticmethod(_) |
|
62 | 62 | |
|
63 | 63 | |
|
64 | 64 | def M(self, key, state=None, **kwargs): |
|
65 | 65 | """ |
|
66 | 66 | returns string from self.message based on given key, |
|
67 | 67 | passed kw params are used to substitute %(named)s params inside |
|
68 | 68 | translated strings |
|
69 | 69 | |
|
70 | 70 | :param msg: |
|
71 | 71 | :param state: |
|
72 | 72 | """ |
|
73 | 73 | if state is None: |
|
74 | 74 | state = StateObj() |
|
75 | 75 | else: |
|
76 | 76 | state._ = staticmethod(_) |
|
77 | 77 | #inject validator into state object |
|
78 | 78 | return self.message(key, state, **kwargs) |
|
79 | 79 | |
|
80 | 80 | |
|
81 | 81 | def ValidUsername(edit=False, old_data={}): |
|
82 | 82 | class _validator(formencode.validators.FancyValidator): |
|
83 | 83 | messages = { |
|
84 | 84 | 'username_exists': _(u'Username "%(username)s" already exists'), |
|
85 | 85 | 'system_invalid_username': |
|
86 | 86 | _(u'Username "%(username)s" is forbidden'), |
|
87 | 87 | 'invalid_username': |
|
88 | 88 | _(u'Username may only contain alphanumeric characters ' |
|
89 | 89 | 'underscores, periods or dashes and must begin with ' |
|
90 | 90 | 'alphanumeric character') |
|
91 | 91 | } |
|
92 | 92 | |
|
93 | 93 | def validate_python(self, value, state): |
|
94 | 94 | if value in ['default', 'new_user']: |
|
95 | 95 | msg = M(self, 'system_invalid_username', state, username=value) |
|
96 | 96 | raise formencode.Invalid(msg, value, state) |
|
97 | 97 | #check if user is unique |
|
98 | 98 | old_un = None |
|
99 | 99 | if edit: |
|
100 | 100 | old_un = User.get(old_data.get('user_id')).username |
|
101 | 101 | |
|
102 | 102 | if old_un != value or not edit: |
|
103 | 103 | if User.get_by_username(value, case_insensitive=True): |
|
104 | 104 | msg = M(self, 'username_exists', state, username=value) |
|
105 | 105 | raise formencode.Invalid(msg, value, state) |
|
106 | 106 | |
|
107 | 107 | if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*$', value) is None: |
|
108 | 108 | msg = M(self, 'invalid_username', state) |
|
109 | 109 | raise formencode.Invalid(msg, value, state) |
|
110 | 110 | return _validator |
|
111 | 111 | |
|
112 | 112 | |
|
113 | 113 | def ValidRepoUser(): |
|
114 | 114 | class _validator(formencode.validators.FancyValidator): |
|
115 | 115 | messages = { |
|
116 | 116 | 'invalid_username': _(u'Username %(username)s is not valid') |
|
117 | 117 | } |
|
118 | 118 | |
|
119 | 119 | def validate_python(self, value, state): |
|
120 | 120 | try: |
|
121 | 121 | User.query().filter(User.active == True)\ |
|
122 | 122 | .filter(User.username == value).one() |
|
123 | 123 | except Exception: |
|
124 | 124 | msg = M(self, 'invalid_username', state, username=value) |
|
125 | 125 | raise formencode.Invalid(msg, value, state, |
|
126 | 126 | error_dict=dict(username=msg) |
|
127 | 127 | ) |
|
128 | 128 | |
|
129 | 129 | return _validator |
|
130 | 130 | |
|
131 | 131 | |
|
132 | 132 | def ValidUsersGroup(edit=False, old_data={}): |
|
133 | 133 | class _validator(formencode.validators.FancyValidator): |
|
134 | 134 | messages = { |
|
135 |
'invalid_group': _(u'Invalid user |
|
|
135 | 'invalid_group': _(u'Invalid user group name'), | |
|
136 | 136 | 'group_exist': _(u'Users group "%(usersgroup)s" already exists'), |
|
137 | 137 | 'invalid_usersgroup_name': |
|
138 |
_(u'user |
|
|
138 | _(u'user group name may only contain alphanumeric ' | |
|
139 | 139 | 'characters underscores, periods or dashes and must begin ' |
|
140 | 140 | 'with alphanumeric character') |
|
141 | 141 | } |
|
142 | 142 | |
|
143 | 143 | def validate_python(self, value, state): |
|
144 | 144 | if value in ['default']: |
|
145 | 145 | msg = M(self, 'invalid_group', state) |
|
146 | 146 | raise formencode.Invalid(msg, value, state, |
|
147 | 147 | error_dict=dict(users_group_name=msg) |
|
148 | 148 | ) |
|
149 | 149 | #check if group is unique |
|
150 | 150 | old_ugname = None |
|
151 | 151 | if edit: |
|
152 | 152 | old_id = old_data.get('users_group_id') |
|
153 | 153 | old_ugname = UsersGroup.get(old_id).users_group_name |
|
154 | 154 | |
|
155 | 155 | if old_ugname != value or not edit: |
|
156 | 156 | is_existing_group = UsersGroup.get_by_group_name(value, |
|
157 | 157 | case_insensitive=True) |
|
158 | 158 | if is_existing_group: |
|
159 | 159 | msg = M(self, 'group_exist', state, usersgroup=value) |
|
160 | 160 | raise formencode.Invalid(msg, value, state, |
|
161 | 161 | error_dict=dict(users_group_name=msg) |
|
162 | 162 | ) |
|
163 | 163 | |
|
164 | 164 | if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: |
|
165 | 165 | msg = M(self, 'invalid_usersgroup_name', state) |
|
166 | 166 | raise formencode.Invalid(msg, value, state, |
|
167 | 167 | error_dict=dict(users_group_name=msg) |
|
168 | 168 | ) |
|
169 | 169 | |
|
170 | 170 | return _validator |
|
171 | 171 | |
|
172 | 172 | |
|
173 | 173 | def ValidReposGroup(edit=False, old_data={}): |
|
174 | 174 | class _validator(formencode.validators.FancyValidator): |
|
175 | 175 | messages = { |
|
176 | 176 | 'group_parent_id': _(u'Cannot assign this group as parent'), |
|
177 | 177 | 'group_exists': _(u'Group "%(group_name)s" already exists'), |
|
178 | 178 | 'repo_exists': |
|
179 | 179 | _(u'Repository with name "%(group_name)s" already exists') |
|
180 | 180 | } |
|
181 | 181 | |
|
182 | 182 | def validate_python(self, value, state): |
|
183 | 183 | # TODO WRITE VALIDATIONS |
|
184 | 184 | group_name = value.get('group_name') |
|
185 | 185 | group_parent_id = value.get('group_parent_id') |
|
186 | 186 | |
|
187 | 187 | # slugify repo group just in case :) |
|
188 | 188 | slug = repo_name_slug(group_name) |
|
189 | 189 | |
|
190 | 190 | # check for parent of self |
|
191 | 191 | parent_of_self = lambda: ( |
|
192 | 192 | old_data['group_id'] == int(group_parent_id) |
|
193 | 193 | if group_parent_id else False |
|
194 | 194 | ) |
|
195 | 195 | if edit and parent_of_self(): |
|
196 | 196 | msg = M(self, 'group_parent_id', state) |
|
197 | 197 | raise formencode.Invalid(msg, value, state, |
|
198 | 198 | error_dict=dict(group_parent_id=msg) |
|
199 | 199 | ) |
|
200 | 200 | |
|
201 | 201 | old_gname = None |
|
202 | 202 | if edit: |
|
203 | 203 | old_gname = RepoGroup.get(old_data.get('group_id')).group_name |
|
204 | 204 | |
|
205 | 205 | if old_gname != group_name or not edit: |
|
206 | 206 | |
|
207 | 207 | # check group |
|
208 | 208 | gr = RepoGroup.query()\ |
|
209 | 209 | .filter(RepoGroup.group_name == slug)\ |
|
210 | 210 | .filter(RepoGroup.group_parent_id == group_parent_id)\ |
|
211 | 211 | .scalar() |
|
212 | 212 | |
|
213 | 213 | if gr: |
|
214 | 214 | msg = M(self, 'group_exists', state, group_name=slug) |
|
215 | 215 | raise formencode.Invalid(msg, value, state, |
|
216 | 216 | error_dict=dict(group_name=msg) |
|
217 | 217 | ) |
|
218 | 218 | |
|
219 | 219 | # check for same repo |
|
220 | 220 | repo = Repository.query()\ |
|
221 | 221 | .filter(Repository.repo_name == slug)\ |
|
222 | 222 | .scalar() |
|
223 | 223 | |
|
224 | 224 | if repo: |
|
225 | 225 | msg = M(self, 'repo_exists', state, group_name=slug) |
|
226 | 226 | raise formencode.Invalid(msg, value, state, |
|
227 | 227 | error_dict=dict(group_name=msg) |
|
228 | 228 | ) |
|
229 | 229 | |
|
230 | 230 | return _validator |
|
231 | 231 | |
|
232 | 232 | |
|
233 | 233 | def ValidPassword(): |
|
234 | 234 | class _validator(formencode.validators.FancyValidator): |
|
235 | 235 | messages = { |
|
236 | 236 | 'invalid_password': |
|
237 | 237 | _(u'Invalid characters (non-ascii) in password') |
|
238 | 238 | } |
|
239 | 239 | |
|
240 | 240 | def validate_python(self, value, state): |
|
241 | 241 | try: |
|
242 | 242 | (value or '').decode('ascii') |
|
243 | 243 | except UnicodeError: |
|
244 | 244 | msg = M(self, 'invalid_password', state) |
|
245 | 245 | raise formencode.Invalid(msg, value, state,) |
|
246 | 246 | return _validator |
|
247 | 247 | |
|
248 | 248 | |
|
249 | 249 | def ValidPasswordsMatch(): |
|
250 | 250 | class _validator(formencode.validators.FancyValidator): |
|
251 | 251 | messages = { |
|
252 | 252 | 'password_mismatch': _(u'Passwords do not match'), |
|
253 | 253 | } |
|
254 | 254 | |
|
255 | 255 | def validate_python(self, value, state): |
|
256 | 256 | |
|
257 | 257 | pass_val = value.get('password') or value.get('new_password') |
|
258 | 258 | if pass_val != value['password_confirmation']: |
|
259 | 259 | msg = M(self, 'password_mismatch', state) |
|
260 | 260 | raise formencode.Invalid(msg, value, state, |
|
261 | 261 | error_dict=dict(password_confirmation=msg) |
|
262 | 262 | ) |
|
263 | 263 | return _validator |
|
264 | 264 | |
|
265 | 265 | |
|
266 | 266 | def ValidAuth(): |
|
267 | 267 | class _validator(formencode.validators.FancyValidator): |
|
268 | 268 | messages = { |
|
269 | 269 | 'invalid_password': _(u'invalid password'), |
|
270 | 270 | 'invalid_username': _(u'invalid user name'), |
|
271 | 271 | 'disabled_account': _(u'Your account is disabled') |
|
272 | 272 | } |
|
273 | 273 | |
|
274 | 274 | def validate_python(self, value, state): |
|
275 | 275 | from rhodecode.lib.auth import authenticate |
|
276 | 276 | |
|
277 | 277 | password = value['password'] |
|
278 | 278 | username = value['username'] |
|
279 | 279 | |
|
280 | 280 | if not authenticate(username, password): |
|
281 | 281 | user = User.get_by_username(username) |
|
282 | 282 | if user and user.active is False: |
|
283 | 283 | log.warning('user %s is disabled' % username) |
|
284 | 284 | msg = M(self, 'disabled_account', state) |
|
285 | 285 | raise formencode.Invalid(msg, value, state, |
|
286 | 286 | error_dict=dict(username=msg) |
|
287 | 287 | ) |
|
288 | 288 | else: |
|
289 | 289 | log.warning('user %s failed to authenticate' % username) |
|
290 | 290 | msg = M(self, 'invalid_username', state) |
|
291 | 291 | msg2 = M(self, 'invalid_password', state) |
|
292 | 292 | raise formencode.Invalid(msg, value, state, |
|
293 | 293 | error_dict=dict(username=msg, password=msg2) |
|
294 | 294 | ) |
|
295 | 295 | return _validator |
|
296 | 296 | |
|
297 | 297 | |
|
298 | 298 | def ValidAuthToken(): |
|
299 | 299 | class _validator(formencode.validators.FancyValidator): |
|
300 | 300 | messages = { |
|
301 | 301 | 'invalid_token': _(u'Token mismatch') |
|
302 | 302 | } |
|
303 | 303 | |
|
304 | 304 | def validate_python(self, value, state): |
|
305 | 305 | if value != authentication_token(): |
|
306 | 306 | msg = M(self, 'invalid_token', state) |
|
307 | 307 | raise formencode.Invalid(msg, value, state) |
|
308 | 308 | return _validator |
|
309 | 309 | |
|
310 | 310 | |
|
311 | 311 | def ValidRepoName(edit=False, old_data={}): |
|
312 | 312 | class _validator(formencode.validators.FancyValidator): |
|
313 | 313 | messages = { |
|
314 | 314 | 'invalid_repo_name': |
|
315 | 315 | _(u'Repository name %(repo)s is disallowed'), |
|
316 | 316 | 'repository_exists': |
|
317 | 317 | _(u'Repository named %(repo)s already exists'), |
|
318 | 318 | 'repository_in_group_exists': _(u'Repository "%(repo)s" already ' |
|
319 | 319 | 'exists in group "%(group)s"'), |
|
320 | 320 | 'same_group_exists': _(u'Repositories group with name "%(repo)s" ' |
|
321 | 321 | 'already exists') |
|
322 | 322 | } |
|
323 | 323 | |
|
324 | 324 | def _to_python(self, value, state): |
|
325 | 325 | repo_name = repo_name_slug(value.get('repo_name', '')) |
|
326 | 326 | repo_group = value.get('repo_group') |
|
327 | 327 | if repo_group: |
|
328 | 328 | gr = RepoGroup.get(repo_group) |
|
329 | 329 | group_path = gr.full_path |
|
330 | 330 | group_name = gr.group_name |
|
331 | 331 | # value needs to be aware of group name in order to check |
|
332 | 332 | # db key This is an actual just the name to store in the |
|
333 | 333 | # database |
|
334 | 334 | repo_name_full = group_path + RepoGroup.url_sep() + repo_name |
|
335 | 335 | else: |
|
336 | 336 | group_name = group_path = '' |
|
337 | 337 | repo_name_full = repo_name |
|
338 | 338 | |
|
339 | 339 | value['repo_name'] = repo_name |
|
340 | 340 | value['repo_name_full'] = repo_name_full |
|
341 | 341 | value['group_path'] = group_path |
|
342 | 342 | value['group_name'] = group_name |
|
343 | 343 | return value |
|
344 | 344 | |
|
345 | 345 | def validate_python(self, value, state): |
|
346 | 346 | |
|
347 | 347 | repo_name = value.get('repo_name') |
|
348 | 348 | repo_name_full = value.get('repo_name_full') |
|
349 | 349 | group_path = value.get('group_path') |
|
350 | 350 | group_name = value.get('group_name') |
|
351 | 351 | |
|
352 | 352 | if repo_name in [ADMIN_PREFIX, '']: |
|
353 | 353 | msg = M(self, 'invalid_repo_name', state, repo=repo_name) |
|
354 | 354 | raise formencode.Invalid(msg, value, state, |
|
355 | 355 | error_dict=dict(repo_name=msg) |
|
356 | 356 | ) |
|
357 | 357 | |
|
358 | 358 | rename = old_data.get('repo_name') != repo_name_full |
|
359 | 359 | create = not edit |
|
360 | 360 | if rename or create: |
|
361 | 361 | |
|
362 | 362 | if group_path != '': |
|
363 | 363 | if Repository.get_by_repo_name(repo_name_full): |
|
364 | 364 | msg = M(self, 'repository_in_group_exists', state, |
|
365 | 365 | repo=repo_name, group=group_name) |
|
366 | 366 | raise formencode.Invalid(msg, value, state, |
|
367 | 367 | error_dict=dict(repo_name=msg) |
|
368 | 368 | ) |
|
369 | 369 | elif RepoGroup.get_by_group_name(repo_name_full): |
|
370 | 370 | msg = M(self, 'same_group_exists', state, |
|
371 | 371 | repo=repo_name) |
|
372 | 372 | raise formencode.Invalid(msg, value, state, |
|
373 | 373 | error_dict=dict(repo_name=msg) |
|
374 | 374 | ) |
|
375 | 375 | |
|
376 | 376 | elif Repository.get_by_repo_name(repo_name_full): |
|
377 | 377 | msg = M(self, 'repository_exists', state, |
|
378 | 378 | repo=repo_name) |
|
379 | 379 | raise formencode.Invalid(msg, value, state, |
|
380 | 380 | error_dict=dict(repo_name=msg) |
|
381 | 381 | ) |
|
382 | 382 | return value |
|
383 | 383 | return _validator |
|
384 | 384 | |
|
385 | 385 | |
|
386 | 386 | def ValidForkName(*args, **kwargs): |
|
387 | 387 | return ValidRepoName(*args, **kwargs) |
|
388 | 388 | |
|
389 | 389 | |
|
390 | 390 | def SlugifyName(): |
|
391 | 391 | class _validator(formencode.validators.FancyValidator): |
|
392 | 392 | |
|
393 | 393 | def _to_python(self, value, state): |
|
394 | 394 | return repo_name_slug(value) |
|
395 | 395 | |
|
396 | 396 | def validate_python(self, value, state): |
|
397 | 397 | pass |
|
398 | 398 | |
|
399 | 399 | return _validator |
|
400 | 400 | |
|
401 | 401 | |
|
402 | 402 | def ValidCloneUri(): |
|
403 | 403 | from rhodecode.lib.utils import make_ui |
|
404 | 404 | |
|
405 | 405 | def url_handler(repo_type, url, ui=None): |
|
406 | 406 | if repo_type == 'hg': |
|
407 | 407 | from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository |
|
408 | 408 | from mercurial.httppeer import httppeer |
|
409 | 409 | if url.startswith('http'): |
|
410 | 410 | ## initially check if it's at least the proper URL |
|
411 | 411 | ## or does it pass basic auth |
|
412 | 412 | MercurialRepository._check_url(url) |
|
413 | 413 | httppeer(ui, url)._capabilities() |
|
414 | 414 | elif url.startswith('svn+http'): |
|
415 | 415 | from hgsubversion.svnrepo import svnremoterepo |
|
416 | 416 | svnremoterepo(ui, url).capabilities |
|
417 | 417 | elif url.startswith('git+http'): |
|
418 | 418 | raise NotImplementedError() |
|
419 | 419 | |
|
420 | 420 | elif repo_type == 'git': |
|
421 | 421 | from rhodecode.lib.vcs.backends.git.repository import GitRepository |
|
422 | 422 | if url.startswith('http'): |
|
423 | 423 | ## initially check if it's at least the proper URL |
|
424 | 424 | ## or does it pass basic auth |
|
425 | 425 | GitRepository._check_url(url) |
|
426 | 426 | elif url.startswith('svn+http'): |
|
427 | 427 | raise NotImplementedError() |
|
428 | 428 | elif url.startswith('hg+http'): |
|
429 | 429 | raise NotImplementedError() |
|
430 | 430 | |
|
431 | 431 | class _validator(formencode.validators.FancyValidator): |
|
432 | 432 | messages = { |
|
433 | 433 | 'clone_uri': _(u'invalid clone url'), |
|
434 | 434 | 'invalid_clone_uri': _(u'Invalid clone url, provide a ' |
|
435 | 435 | 'valid clone http(s)/svn+http(s) url') |
|
436 | 436 | } |
|
437 | 437 | |
|
438 | 438 | def validate_python(self, value, state): |
|
439 | 439 | repo_type = value.get('repo_type') |
|
440 | 440 | url = value.get('clone_uri') |
|
441 | 441 | |
|
442 | 442 | if not url: |
|
443 | 443 | pass |
|
444 | 444 | else: |
|
445 | 445 | try: |
|
446 | 446 | url_handler(repo_type, url, make_ui('db', clear_session=False)) |
|
447 | 447 | except Exception: |
|
448 | 448 | log.exception('Url validation failed') |
|
449 | 449 | msg = M(self, 'clone_uri') |
|
450 | 450 | raise formencode.Invalid(msg, value, state, |
|
451 | 451 | error_dict=dict(clone_uri=msg) |
|
452 | 452 | ) |
|
453 | 453 | return _validator |
|
454 | 454 | |
|
455 | 455 | |
|
456 | 456 | def ValidForkType(old_data={}): |
|
457 | 457 | class _validator(formencode.validators.FancyValidator): |
|
458 | 458 | messages = { |
|
459 | 459 | 'invalid_fork_type': _(u'Fork have to be the same type as parent') |
|
460 | 460 | } |
|
461 | 461 | |
|
462 | 462 | def validate_python(self, value, state): |
|
463 | 463 | if old_data['repo_type'] != value: |
|
464 | 464 | msg = M(self, 'invalid_fork_type', state) |
|
465 | 465 | raise formencode.Invalid(msg, value, state, |
|
466 | 466 | error_dict=dict(repo_type=msg) |
|
467 | 467 | ) |
|
468 | 468 | return _validator |
|
469 | 469 | |
|
470 | 470 | |
|
471 | 471 | def CanWriteGroup(): |
|
472 | 472 | class _validator(formencode.validators.FancyValidator): |
|
473 | 473 | messages = { |
|
474 | 474 | 'permission_denied': _(u"You don't have permissions " |
|
475 | 475 | "to create repository in this group"), |
|
476 | 476 | 'permission_denied_root': _(u"no permission to create repository " |
|
477 | 477 | "in root location") |
|
478 | 478 | } |
|
479 | 479 | |
|
480 | 480 | def _to_python(self, value, state): |
|
481 | 481 | #root location |
|
482 | 482 | if value in [-1, "-1"]: |
|
483 | 483 | return None |
|
484 | 484 | return value |
|
485 | 485 | |
|
486 | 486 | def validate_python(self, value, state): |
|
487 | 487 | gr = RepoGroup.get(value) |
|
488 | 488 | gr_name = gr.group_name if gr else None # None means ROOT location |
|
489 | 489 | val = HasReposGroupPermissionAny('group.write', 'group.admin') |
|
490 | 490 | can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository') |
|
491 | 491 | forbidden = not val(gr_name, 'can write into group validator') |
|
492 | 492 | #parent group need to be existing |
|
493 | 493 | if gr and forbidden: |
|
494 | 494 | msg = M(self, 'permission_denied', state) |
|
495 | 495 | raise formencode.Invalid(msg, value, state, |
|
496 | 496 | error_dict=dict(repo_type=msg) |
|
497 | 497 | ) |
|
498 | 498 | ## check if we can write to root location ! |
|
499 | 499 | elif gr is None and can_create_repos() is False: |
|
500 | 500 | msg = M(self, 'permission_denied_root', state) |
|
501 | 501 | raise formencode.Invalid(msg, value, state, |
|
502 | 502 | error_dict=dict(repo_type=msg) |
|
503 | 503 | ) |
|
504 | 504 | |
|
505 | 505 | return _validator |
|
506 | 506 | |
|
507 | 507 | |
|
508 | 508 | def CanCreateGroup(can_create_in_root=False): |
|
509 | 509 | class _validator(formencode.validators.FancyValidator): |
|
510 | 510 | messages = { |
|
511 | 511 | 'permission_denied': _(u"You don't have permissions " |
|
512 | 512 | "to create a group in this location") |
|
513 | 513 | } |
|
514 | 514 | |
|
515 | 515 | def to_python(self, value, state): |
|
516 | 516 | #root location |
|
517 | 517 | if value in [-1, "-1"]: |
|
518 | 518 | return None |
|
519 | 519 | return value |
|
520 | 520 | |
|
521 | 521 | def validate_python(self, value, state): |
|
522 | 522 | gr = RepoGroup.get(value) |
|
523 | 523 | gr_name = gr.group_name if gr else None # None means ROOT location |
|
524 | 524 | |
|
525 | 525 | if can_create_in_root and gr is None: |
|
526 | 526 | #we can create in root, we're fine no validations required |
|
527 | 527 | return |
|
528 | 528 | |
|
529 | 529 | forbidden_in_root = gr is None and can_create_in_root is False |
|
530 | 530 | val = HasReposGroupPermissionAny('group.admin') |
|
531 | 531 | forbidden = not val(gr_name, 'can create group validator') |
|
532 | 532 | if forbidden_in_root or forbidden: |
|
533 | 533 | msg = M(self, 'permission_denied', state) |
|
534 | 534 | raise formencode.Invalid(msg, value, state, |
|
535 | 535 | error_dict=dict(group_parent_id=msg) |
|
536 | 536 | ) |
|
537 | 537 | |
|
538 | 538 | return _validator |
|
539 | 539 | |
|
540 | 540 | |
|
541 | 541 | def ValidPerms(type_='repo'): |
|
542 | 542 | if type_ == 'group': |
|
543 | 543 | EMPTY_PERM = 'group.none' |
|
544 | 544 | elif type_ == 'repo': |
|
545 | 545 | EMPTY_PERM = 'repository.none' |
|
546 | 546 | |
|
547 | 547 | class _validator(formencode.validators.FancyValidator): |
|
548 | 548 | messages = { |
|
549 | 549 | 'perm_new_member_name': |
|
550 |
_(u'This username or user |
|
|
550 | _(u'This username or user group name is not valid') | |
|
551 | 551 | } |
|
552 | 552 | |
|
553 | 553 | def to_python(self, value, state): |
|
554 | 554 | perms_update = OrderedSet() |
|
555 | 555 | perms_new = OrderedSet() |
|
556 | 556 | # build a list of permission to update and new permission to create |
|
557 | 557 | |
|
558 | 558 | #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using |
|
559 | 559 | new_perms_group = defaultdict(dict) |
|
560 | 560 | for k, v in value.copy().iteritems(): |
|
561 | 561 | if k.startswith('perm_new_member'): |
|
562 | 562 | del value[k] |
|
563 | 563 | _type, part = k.split('perm_new_member_') |
|
564 | 564 | args = part.split('_') |
|
565 | 565 | if len(args) == 1: |
|
566 | 566 | new_perms_group[args[0]]['perm'] = v |
|
567 | 567 | elif len(args) == 2: |
|
568 | 568 | _key, pos = args |
|
569 | 569 | new_perms_group[pos][_key] = v |
|
570 | 570 | |
|
571 | 571 | # fill new permissions in order of how they were added |
|
572 | 572 | for k in sorted(map(int, new_perms_group.keys())): |
|
573 | 573 | perm_dict = new_perms_group[str(k)] |
|
574 | 574 | new_member = perm_dict.get('name') |
|
575 | 575 | new_perm = perm_dict.get('perm') |
|
576 | 576 | new_type = perm_dict.get('type') |
|
577 | 577 | if new_member and new_perm and new_type: |
|
578 | 578 | perms_new.add((new_member, new_perm, new_type)) |
|
579 | 579 | |
|
580 | 580 | for k, v in value.iteritems(): |
|
581 | 581 | if k.startswith('u_perm_') or k.startswith('g_perm_'): |
|
582 | 582 | member = k[7:] |
|
583 | 583 | t = {'u': 'user', |
|
584 | 584 | 'g': 'users_group' |
|
585 | 585 | }[k[0]] |
|
586 | 586 | if member == 'default': |
|
587 | 587 | if value.get('repo_private'): |
|
588 | 588 | # set none for default when updating to |
|
589 | 589 | # private repo |
|
590 | 590 | v = EMPTY_PERM |
|
591 | 591 | perms_update.add((member, v, t)) |
|
592 | 592 | #always set NONE when private flag is set |
|
593 | 593 | if value.get('repo_private'): |
|
594 | 594 | perms_update.add(('default', EMPTY_PERM, 'user')) |
|
595 | 595 | |
|
596 | 596 | value['perms_updates'] = list(perms_update) |
|
597 | 597 | value['perms_new'] = list(perms_new) |
|
598 | 598 | |
|
599 | 599 | # update permissions |
|
600 | 600 | for k, v, t in perms_new: |
|
601 | 601 | try: |
|
602 | 602 | if t is 'user': |
|
603 | 603 | self.user_db = User.query()\ |
|
604 | 604 | .filter(User.active == True)\ |
|
605 | 605 | .filter(User.username == k).one() |
|
606 | 606 | if t is 'users_group': |
|
607 | 607 | self.user_db = UsersGroup.query()\ |
|
608 | 608 | .filter(UsersGroup.users_group_active == True)\ |
|
609 | 609 | .filter(UsersGroup.users_group_name == k).one() |
|
610 | 610 | |
|
611 | 611 | except Exception: |
|
612 | 612 | log.exception('Updated permission failed') |
|
613 | 613 | msg = M(self, 'perm_new_member_type', state) |
|
614 | 614 | raise formencode.Invalid(msg, value, state, |
|
615 | 615 | error_dict=dict(perm_new_member_name=msg) |
|
616 | 616 | ) |
|
617 | 617 | return value |
|
618 | 618 | return _validator |
|
619 | 619 | |
|
620 | 620 | |
|
621 | 621 | def ValidSettings(): |
|
622 | 622 | class _validator(formencode.validators.FancyValidator): |
|
623 | 623 | def _to_python(self, value, state): |
|
624 | 624 | # settings form for users that are not admin |
|
625 | 625 | # can't edit certain parameters, it's extra backup if they mangle |
|
626 | 626 | # with forms |
|
627 | 627 | |
|
628 | 628 | forbidden_params = [ |
|
629 | 629 | 'user', 'repo_type', 'repo_enable_locking', |
|
630 | 630 | 'repo_enable_downloads', 'repo_enable_statistics' |
|
631 | 631 | ] |
|
632 | 632 | |
|
633 | 633 | for param in forbidden_params: |
|
634 | 634 | if param in value: |
|
635 | 635 | del value[param] |
|
636 | 636 | return value |
|
637 | 637 | |
|
638 | 638 | def validate_python(self, value, state): |
|
639 | 639 | pass |
|
640 | 640 | return _validator |
|
641 | 641 | |
|
642 | 642 | |
|
643 | 643 | def ValidPath(): |
|
644 | 644 | class _validator(formencode.validators.FancyValidator): |
|
645 | 645 | messages = { |
|
646 | 646 | 'invalid_path': _(u'This is not a valid path') |
|
647 | 647 | } |
|
648 | 648 | |
|
649 | 649 | def validate_python(self, value, state): |
|
650 | 650 | if not os.path.isdir(value): |
|
651 | 651 | msg = M(self, 'invalid_path', state) |
|
652 | 652 | raise formencode.Invalid(msg, value, state, |
|
653 | 653 | error_dict=dict(paths_root_path=msg) |
|
654 | 654 | ) |
|
655 | 655 | return _validator |
|
656 | 656 | |
|
657 | 657 | |
|
658 | 658 | def UniqSystemEmail(old_data={}): |
|
659 | 659 | class _validator(formencode.validators.FancyValidator): |
|
660 | 660 | messages = { |
|
661 | 661 | 'email_taken': _(u'This e-mail address is already taken') |
|
662 | 662 | } |
|
663 | 663 | |
|
664 | 664 | def _to_python(self, value, state): |
|
665 | 665 | return value.lower() |
|
666 | 666 | |
|
667 | 667 | def validate_python(self, value, state): |
|
668 | 668 | if (old_data.get('email') or '').lower() != value: |
|
669 | 669 | user = User.get_by_email(value, case_insensitive=True) |
|
670 | 670 | if user: |
|
671 | 671 | msg = M(self, 'email_taken', state) |
|
672 | 672 | raise formencode.Invalid(msg, value, state, |
|
673 | 673 | error_dict=dict(email=msg) |
|
674 | 674 | ) |
|
675 | 675 | return _validator |
|
676 | 676 | |
|
677 | 677 | |
|
678 | 678 | def ValidSystemEmail(): |
|
679 | 679 | class _validator(formencode.validators.FancyValidator): |
|
680 | 680 | messages = { |
|
681 | 681 | 'non_existing_email': _(u'e-mail "%(email)s" does not exist.') |
|
682 | 682 | } |
|
683 | 683 | |
|
684 | 684 | def _to_python(self, value, state): |
|
685 | 685 | return value.lower() |
|
686 | 686 | |
|
687 | 687 | def validate_python(self, value, state): |
|
688 | 688 | user = User.get_by_email(value, case_insensitive=True) |
|
689 | 689 | if user is None: |
|
690 | 690 | msg = M(self, 'non_existing_email', state, email=value) |
|
691 | 691 | raise formencode.Invalid(msg, value, state, |
|
692 | 692 | error_dict=dict(email=msg) |
|
693 | 693 | ) |
|
694 | 694 | |
|
695 | 695 | return _validator |
|
696 | 696 | |
|
697 | 697 | |
|
698 | 698 | def LdapLibValidator(): |
|
699 | 699 | class _validator(formencode.validators.FancyValidator): |
|
700 | 700 | messages = { |
|
701 | 701 | |
|
702 | 702 | } |
|
703 | 703 | |
|
704 | 704 | def validate_python(self, value, state): |
|
705 | 705 | try: |
|
706 | 706 | import ldap |
|
707 | 707 | ldap # pyflakes silence ! |
|
708 | 708 | except ImportError: |
|
709 | 709 | raise LdapImportError() |
|
710 | 710 | |
|
711 | 711 | return _validator |
|
712 | 712 | |
|
713 | 713 | |
|
714 | 714 | def AttrLoginValidator(): |
|
715 | 715 | class _validator(formencode.validators.FancyValidator): |
|
716 | 716 | messages = { |
|
717 | 717 | 'invalid_cn': |
|
718 | 718 | _(u'The LDAP Login attribute of the CN must be specified - ' |
|
719 | 719 | 'this is the name of the attribute that is equivalent ' |
|
720 | 720 | 'to "username"') |
|
721 | 721 | } |
|
722 | 722 | |
|
723 | 723 | def validate_python(self, value, state): |
|
724 | 724 | if not value or not isinstance(value, (str, unicode)): |
|
725 | 725 | msg = M(self, 'invalid_cn', state) |
|
726 | 726 | raise formencode.Invalid(msg, value, state, |
|
727 | 727 | error_dict=dict(ldap_attr_login=msg) |
|
728 | 728 | ) |
|
729 | 729 | |
|
730 | 730 | return _validator |
|
731 | 731 | |
|
732 | 732 | |
|
733 | 733 | def NotReviewedRevisions(repo_id): |
|
734 | 734 | class _validator(formencode.validators.FancyValidator): |
|
735 | 735 | messages = { |
|
736 | 736 | 'rev_already_reviewed': |
|
737 | 737 | _(u'Revisions %(revs)s are already part of pull request ' |
|
738 | 738 | 'or have set status') |
|
739 | 739 | } |
|
740 | 740 | |
|
741 | 741 | def validate_python(self, value, state): |
|
742 | 742 | # check revisions if they are not reviewed, or a part of another |
|
743 | 743 | # pull request |
|
744 | 744 | statuses = ChangesetStatus.query()\ |
|
745 | 745 | .filter(ChangesetStatus.revision.in_(value))\ |
|
746 | 746 | .filter(ChangesetStatus.repo_id == repo_id)\ |
|
747 | 747 | .all() |
|
748 | 748 | |
|
749 | 749 | errors = [] |
|
750 | 750 | for cs in statuses: |
|
751 | 751 | if cs.pull_request_id: |
|
752 | 752 | errors.append(['pull_req', cs.revision[:12]]) |
|
753 | 753 | elif cs.status: |
|
754 | 754 | errors.append(['status', cs.revision[:12]]) |
|
755 | 755 | |
|
756 | 756 | if errors: |
|
757 | 757 | revs = ','.join([x[1] for x in errors]) |
|
758 | 758 | msg = M(self, 'rev_already_reviewed', state, revs=revs) |
|
759 | 759 | raise formencode.Invalid(msg, value, state, |
|
760 | 760 | error_dict=dict(revisions=revs) |
|
761 | 761 | ) |
|
762 | 762 | |
|
763 | 763 | return _validator |
|
764 | 764 | |
|
765 | 765 | |
|
766 | 766 | def ValidIp(): |
|
767 | 767 | class _validator(CIDR): |
|
768 | 768 | messages = dict( |
|
769 | 769 | badFormat=_('Please enter a valid IPv4 or IpV6 address'), |
|
770 | 770 | illegalBits=_('The network size (bits) must be within the range' |
|
771 | 771 | ' of 0-32 (not %(bits)r)')) |
|
772 | 772 | |
|
773 | 773 | def to_python(self, value, state): |
|
774 | 774 | v = super(_validator, self).to_python(value, state) |
|
775 | 775 | v = v.strip() |
|
776 | 776 | net = ipaddr.IPNetwork(address=v) |
|
777 | 777 | if isinstance(net, ipaddr.IPv4Network): |
|
778 | 778 | #if IPv4 doesn't end with a mask, add /32 |
|
779 | 779 | if '/' not in value: |
|
780 | 780 | v += '/32' |
|
781 | 781 | if isinstance(net, ipaddr.IPv6Network): |
|
782 | 782 | #if IPv6 doesn't end with a mask, add /128 |
|
783 | 783 | if '/' not in value: |
|
784 | 784 | v += '/128' |
|
785 | 785 | return v |
|
786 | 786 | |
|
787 | 787 | def validate_python(self, value, state): |
|
788 | 788 | try: |
|
789 | 789 | addr = value.strip() |
|
790 | 790 | #this raises an ValueError if address is not IpV4 or IpV6 |
|
791 | 791 | ipaddr.IPNetwork(address=addr) |
|
792 | 792 | except ValueError: |
|
793 | 793 | raise formencode.Invalid(self.message('badFormat', state), |
|
794 | 794 | value, state) |
|
795 | 795 | |
|
796 | 796 | return _validator |
|
797 | 797 | |
|
798 | 798 | |
|
799 | 799 | def FieldKey(): |
|
800 | 800 | class _validator(formencode.validators.FancyValidator): |
|
801 | 801 | messages = dict( |
|
802 | 802 | badFormat=_('Key name can only consist of letters, ' |
|
803 | 803 | 'underscore, dash or numbers'),) |
|
804 | 804 | |
|
805 | 805 | def validate_python(self, value, state): |
|
806 | 806 | if not re.match('[a-zA-Z0-9_-]+$', value): |
|
807 | 807 | raise formencode.Invalid(self.message('badFormat', state), |
|
808 | 808 | value, state) |
|
809 | 809 | return _validator |
@@ -1,78 +1,78 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.html"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 | 5 | ${_('Repository groups administration')} - ${c.rhodecode_name} |
|
6 | 6 | </%def> |
|
7 | 7 | |
|
8 | 8 | |
|
9 | 9 | <%def name="breadcrumbs_links()"> |
|
10 | 10 | ${h.link_to(_('Admin'),h.url('admin_home'))} |
|
11 | 11 | » |
|
12 |
${_('repositor |
|
|
12 | ${_('repository groups')} | |
|
13 | 13 | </%def> |
|
14 | 14 | <%def name="page_nav()"> |
|
15 | 15 | ${self.menu('admin')} |
|
16 | 16 | </%def> |
|
17 | 17 | <%def name="main()"> |
|
18 | 18 | <div class="box"> |
|
19 | 19 | <!-- box / title --> |
|
20 | 20 | <div class="title"> |
|
21 | 21 | ${self.breadcrumbs()} |
|
22 | 22 | <ul class="links"> |
|
23 | 23 | <li> |
|
24 | 24 | %if h.HasPermissionAny('hg.admin')(): |
|
25 | 25 | <span>${h.link_to(_(u'Add group'),h.url('new_repos_group'))}</span> |
|
26 | 26 | %endif |
|
27 | 27 | </li> |
|
28 | 28 | </ul> |
|
29 | 29 | </div> |
|
30 | 30 | <!-- end box / title --> |
|
31 | 31 | <div class="table"> |
|
32 | 32 | % if c.groups: |
|
33 | 33 | <table class="table_disp"> |
|
34 | 34 | |
|
35 | 35 | <thead> |
|
36 | 36 | <tr> |
|
37 | 37 | <th class="left"><a href="#">${_('Group name')}</a></th> |
|
38 | 38 | <th class="left"><a href="#">${_('Description')}</a></th> |
|
39 | 39 | <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th> |
|
40 | 40 | <th class="left" colspan="2">${_('action')}</th> |
|
41 | 41 | </tr> |
|
42 | 42 | </thead> |
|
43 | 43 | |
|
44 | 44 | ## REPO GROUPS |
|
45 | 45 | |
|
46 | 46 | % for gr in c.groups: |
|
47 | 47 | <% gr_cn = gr.repositories.count() %> |
|
48 | 48 | <tr> |
|
49 | 49 | <td> |
|
50 | 50 | <div style="white-space: nowrap"> |
|
51 | 51 | <img class="icon" alt="${_('Repository group')}" src="${h.url('/images/icons/database_link.png')}"/> |
|
52 | 52 | ${h.link_to(h.literal(' » '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))} |
|
53 | 53 | </div> |
|
54 | 54 | </td> |
|
55 | 55 | <td>${gr.group_description}</td> |
|
56 | 56 | <td><b>${gr_cn}</b></td> |
|
57 | 57 | <td> |
|
58 | 58 | <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('edit')}"> |
|
59 | 59 | ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")} |
|
60 | 60 | </a> |
|
61 | 61 | </td> |
|
62 | 62 | <td> |
|
63 | 63 | ${h.form(url('repos_group', group_name=gr.group_name),method='delete')} |
|
64 | 64 | ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")} |
|
65 | 65 | ${h.end_form()} |
|
66 | 66 | </td> |
|
67 | 67 | </tr> |
|
68 | 68 | % endfor |
|
69 | 69 | |
|
70 | 70 | </table> |
|
71 | 71 | % else: |
|
72 | 72 | ${_('There are no repository groups yet')} |
|
73 | 73 | % endif |
|
74 | 74 | |
|
75 | 75 | </div> |
|
76 | 76 | </div> |
|
77 | 77 | |
|
78 | 78 | </%def> |
@@ -1,228 +1,228 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.html"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 |
${_('Edit user |
|
|
5 | ${_('Edit user group')} ${c.users_group.users_group_name} - ${c.rhodecode_name} | |
|
6 | 6 | </%def> |
|
7 | 7 | |
|
8 | 8 | <%def name="breadcrumbs_links()"> |
|
9 | 9 | ${h.link_to(_('Admin'),h.url('admin_home'))} |
|
10 | 10 | » |
|
11 | 11 | ${h.link_to(_('UsersGroups'),h.url('users_groups'))} |
|
12 | 12 | » |
|
13 | 13 | ${_('edit')} "${c.users_group.users_group_name}" |
|
14 | 14 | </%def> |
|
15 | 15 | |
|
16 | 16 | <%def name="page_nav()"> |
|
17 | 17 | ${self.menu('admin')} |
|
18 | 18 | </%def> |
|
19 | 19 | |
|
20 | 20 | <%def name="main()"> |
|
21 | 21 | <div class="box box-left"> |
|
22 | 22 | <!-- box / title --> |
|
23 | 23 | <div class="title"> |
|
24 | 24 | ${self.breadcrumbs()} |
|
25 | 25 | </div> |
|
26 | 26 | <!-- end box / title --> |
|
27 | 27 | ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')} |
|
28 | 28 | <div class="form"> |
|
29 | 29 | <!-- fields --> |
|
30 | 30 | <div class="fields"> |
|
31 | 31 | <div class="field"> |
|
32 | 32 | <div class="label"> |
|
33 | 33 | <label for="users_group_name">${_('Group name')}:</label> |
|
34 | 34 | </div> |
|
35 | 35 | <div class="input"> |
|
36 | 36 | ${h.text('users_group_name',class_='small')} |
|
37 | 37 | </div> |
|
38 | 38 | </div> |
|
39 | 39 | |
|
40 | 40 | <div class="field"> |
|
41 | 41 | <div class="label label-checkbox"> |
|
42 | 42 | <label for="users_group_active">${_('Active')}:</label> |
|
43 | 43 | </div> |
|
44 | 44 | <div class="checkboxes"> |
|
45 | 45 | ${h.checkbox('users_group_active',value=True)} |
|
46 | 46 | </div> |
|
47 | 47 | </div> |
|
48 | 48 | <div class="field"> |
|
49 | 49 | <div class="label"> |
|
50 | 50 | <label for="users_group_active">${_('Members')}:</label> |
|
51 | 51 | </div> |
|
52 | 52 | <div class="select"> |
|
53 | 53 | <table> |
|
54 | 54 | <tr> |
|
55 | 55 | <td> |
|
56 | 56 | <div> |
|
57 | 57 | <div style="float:left"> |
|
58 | 58 | <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div> |
|
59 | 59 | ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")} |
|
60 | 60 | <div id="remove_all_elements" style="cursor:pointer;text-align:center"> |
|
61 | 61 | ${_('Remove all elements')} |
|
62 | 62 | <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/> |
|
63 | 63 | </div> |
|
64 | 64 | </div> |
|
65 | 65 | <div style="float:left;width:20px;padding-top:50px"> |
|
66 | 66 | <img alt="add" id="add_element" |
|
67 | 67 | style="padding:2px;cursor:pointer" |
|
68 | 68 | src="${h.url('/images/icons/arrow_left.png')}"/> |
|
69 | 69 | <br /> |
|
70 | 70 | <img alt="remove" id="remove_element" |
|
71 | 71 | style="padding:2px;cursor:pointer" |
|
72 | 72 | src="${h.url('/images/icons/arrow_right.png')}"/> |
|
73 | 73 | </div> |
|
74 | 74 | <div style="float:left"> |
|
75 | 75 | <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div> |
|
76 | 76 | ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")} |
|
77 | 77 | <div id="add_all_elements" style="cursor:pointer;text-align:center"> |
|
78 | 78 | <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/> |
|
79 | 79 | ${_('Add all elements')} |
|
80 | 80 | </div> |
|
81 | 81 | </div> |
|
82 | 82 | </div> |
|
83 | 83 | </td> |
|
84 | 84 | </tr> |
|
85 | 85 | </table> |
|
86 | 86 | </div> |
|
87 | 87 | |
|
88 | 88 | </div> |
|
89 | 89 | <div class="buttons"> |
|
90 | 90 | ${h.submit('save',_('save'),class_="ui-btn large")} |
|
91 | 91 | </div> |
|
92 | 92 | </div> |
|
93 | 93 | </div> |
|
94 | 94 | ${h.end_form()} |
|
95 | 95 | </div> |
|
96 | 96 | |
|
97 | 97 | <div class="box box-right"> |
|
98 | 98 | <!-- box / title --> |
|
99 | 99 | <div class="title"> |
|
100 | 100 | <h5>${_('Permissions')}</h5> |
|
101 | 101 | </div> |
|
102 | 102 | ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')} |
|
103 | 103 | <div class="form"> |
|
104 | 104 | <!-- fields --> |
|
105 | 105 | <div class="fields"> |
|
106 | 106 | <div class="field"> |
|
107 | 107 | <div class="label label-checkbox"> |
|
108 | 108 | <label for="inherit_permissions">${_('Inherit default permissions')}:</label> |
|
109 | 109 | </div> |
|
110 | 110 | <div class="checkboxes"> |
|
111 | 111 | ${h.checkbox('inherit_default_permissions',value=True)} |
|
112 | 112 | </div> |
|
113 | 113 | <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. ' |
|
114 | 114 | 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span> |
|
115 | 115 | </div> |
|
116 | 116 | <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" > |
|
117 | 117 | <div class="field"> |
|
118 | 118 | <div class="label label-checkbox"> |
|
119 | 119 | <label for="create_repo_perm">${_('Create repositories')}:</label> |
|
120 | 120 | </div> |
|
121 | 121 | <div class="checkboxes"> |
|
122 | 122 | ${h.checkbox('create_repo_perm',value=True)} |
|
123 | 123 | </div> |
|
124 | 124 | </div> |
|
125 | 125 | <div class="field"> |
|
126 | 126 | <div class="label label-checkbox"> |
|
127 | 127 | <label for="fork_repo_perm">${_('Fork repositories')}:</label> |
|
128 | 128 | </div> |
|
129 | 129 | <div class="checkboxes"> |
|
130 | 130 | ${h.checkbox('fork_repo_perm',value=True)} |
|
131 | 131 | </div> |
|
132 | 132 | </div> |
|
133 | 133 | </div> |
|
134 | 134 | <div class="buttons"> |
|
135 | 135 | ${h.submit('save',_('Save'),class_="ui-btn large")} |
|
136 | 136 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} |
|
137 | 137 | </div> |
|
138 | 138 | </div> |
|
139 | 139 | </div> |
|
140 | 140 | ${h.end_form()} |
|
141 | 141 | </div> |
|
142 | 142 | |
|
143 | 143 | <div class="box box-right"> |
|
144 | 144 | <!-- box / title --> |
|
145 | 145 | <div class="title"> |
|
146 | 146 | <h5>${_('Group members')}</h5> |
|
147 | 147 | </div> |
|
148 | 148 | |
|
149 | 149 | <div class="group_members_wrap"> |
|
150 | 150 | % if c.group_members_obj: |
|
151 | 151 | <ul class="group_members"> |
|
152 | 152 | %for user in c.group_members_obj: |
|
153 | 153 | <li> |
|
154 | 154 | <div class="group_member"> |
|
155 | 155 | <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div> |
|
156 | 156 | <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div> |
|
157 | 157 | <div>${user.full_name}</div> |
|
158 | 158 | </div> |
|
159 | 159 | </li> |
|
160 | 160 | %endfor |
|
161 | 161 | </ul> |
|
162 | 162 | %else: |
|
163 | 163 | <span class="empty_data">${_('No members yet')}</span> |
|
164 | 164 | %endif |
|
165 | 165 | </div> |
|
166 | 166 | </div> |
|
167 | 167 | |
|
168 | 168 | <div class="box box-left"> |
|
169 | 169 | <!-- box / title --> |
|
170 | 170 | <div class="title"> |
|
171 | 171 | <h5>${_('Permissions defined for this group')}</h5> |
|
172 | 172 | </div> |
|
173 | 173 | ## permissions overview |
|
174 | 174 | <div id="perms" class="table"> |
|
175 | 175 | %for section in sorted(c.users_group.permissions.keys()): |
|
176 | 176 | <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div> |
|
177 | 177 | %if not c.users_group.permissions: |
|
178 | 178 | <span class="empty_data">${_('No permissions set yet')}</span> |
|
179 | 179 | %else: |
|
180 | 180 | <div id='tbl_list_wrap_${section}' class="yui-skin-sam"> |
|
181 | 181 | <table id="tbl_list_repository"> |
|
182 | 182 | <thead> |
|
183 | 183 | <tr> |
|
184 | 184 | <th class="left">${_('Name')}</th> |
|
185 | 185 | <th class="left">${_('Permission')}</th> |
|
186 | 186 | <th class="left">${_('Edit Permission')}</th> |
|
187 | 187 | </thead> |
|
188 | 188 | <tbody> |
|
189 | 189 | %for k in c.users_group.permissions[section]: |
|
190 | 190 | <% |
|
191 | 191 | section_perm = c.users_group.permissions[section].get(k) |
|
192 | 192 | _perm = section_perm.split('.')[-1] |
|
193 | 193 | %> |
|
194 | 194 | <tr> |
|
195 | 195 | <td> |
|
196 | 196 | %if section == 'repositories': |
|
197 | 197 | <a href="${h.url('summary_home',repo_name=k)}">${k}</a> |
|
198 | 198 | %elif section == 'repositories_groups': |
|
199 | 199 | <a href="${h.url('repos_group_home',group_name=k)}">${k}</a> |
|
200 | 200 | %endif |
|
201 | 201 | </td> |
|
202 | 202 | <td> |
|
203 | 203 | <span class="perm_tag ${_perm}">${section_perm}</span> |
|
204 | 204 | </td> |
|
205 | 205 | <td> |
|
206 | 206 | %if section == 'repositories': |
|
207 | 207 | <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a> |
|
208 | 208 | %elif section == 'repositories_groups': |
|
209 | 209 | <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a> |
|
210 | 210 | %else: |
|
211 | 211 | -- |
|
212 | 212 | %endif |
|
213 | 213 | </td> |
|
214 | 214 | </tr> |
|
215 | 215 | %endfor |
|
216 | 216 | </tbody> |
|
217 | 217 | </table> |
|
218 | 218 | </div> |
|
219 | 219 | %endif |
|
220 | 220 | %endfor |
|
221 | 221 | </div> |
|
222 | 222 | </div> |
|
223 | 223 | |
|
224 | 224 | |
|
225 | 225 | <script type="text/javascript"> |
|
226 | 226 | MultiSelectWidget('users_group_members','available_members','edit_users_group'); |
|
227 | 227 | </script> |
|
228 | 228 | </%def> |
@@ -1,1294 +1,1294 b'' | |||
|
1 | 1 | from __future__ import with_statement |
|
2 | 2 | import random |
|
3 | 3 | import mock |
|
4 | 4 | |
|
5 | 5 | from rhodecode.tests import * |
|
6 | 6 | from rhodecode.lib.compat import json |
|
7 | 7 | from rhodecode.lib.auth import AuthUser |
|
8 | 8 | from rhodecode.model.user import UserModel |
|
9 | 9 | from rhodecode.model.users_group import UsersGroupModel |
|
10 | 10 | from rhodecode.model.repo import RepoModel |
|
11 | 11 | from rhodecode.model.meta import Session |
|
12 | 12 | from rhodecode.model.scm import ScmModel |
|
13 | 13 | from rhodecode.model.db import Repository |
|
14 | 14 | |
|
15 | 15 | API_URL = '/_admin/api' |
|
16 | 16 | |
|
17 | 17 | |
|
18 | 18 | def _build_data(apikey, method, **kw): |
|
19 | 19 | """ |
|
20 | 20 | Builds API data with given random ID |
|
21 | 21 | |
|
22 | 22 | :param random_id: |
|
23 | 23 | :type random_id: |
|
24 | 24 | """ |
|
25 | 25 | random_id = random.randrange(1, 9999) |
|
26 | 26 | return random_id, json.dumps({ |
|
27 | 27 | "id": random_id, |
|
28 | 28 | "api_key": apikey, |
|
29 | 29 | "method": method, |
|
30 | 30 | "args": kw |
|
31 | 31 | }) |
|
32 | 32 | |
|
33 | 33 | jsonify = lambda obj: json.loads(json.dumps(obj)) |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | def crash(*args, **kwargs): |
|
37 | 37 | raise Exception('Total Crash !') |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | def api_call(test_obj, params): |
|
41 | 41 | response = test_obj.app.post(API_URL, content_type='application/json', |
|
42 | 42 | params=params) |
|
43 | 43 | return response |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | TEST_USERS_GROUP = 'test_users_group' |
|
47 | 47 | |
|
48 | 48 | |
|
49 | 49 | def make_users_group(name=TEST_USERS_GROUP): |
|
50 | 50 | gr = UsersGroupModel().create(name=name) |
|
51 | 51 | UsersGroupModel().add_user_to_group(users_group=gr, |
|
52 | 52 | user=TEST_USER_ADMIN_LOGIN) |
|
53 | 53 | Session().commit() |
|
54 | 54 | return gr |
|
55 | 55 | |
|
56 | 56 | |
|
57 | 57 | def destroy_users_group(name=TEST_USERS_GROUP): |
|
58 | 58 | UsersGroupModel().delete(users_group=name, force=True) |
|
59 | 59 | Session().commit() |
|
60 | 60 | |
|
61 | 61 | |
|
62 | 62 | def create_repo(repo_name, repo_type, owner=None): |
|
63 | 63 | # create new repo |
|
64 | 64 | form_data = _get_repo_create_params( |
|
65 | 65 | repo_name_full=repo_name, |
|
66 | 66 | repo_description='description %s' % repo_name, |
|
67 | 67 | ) |
|
68 | 68 | cur_user = UserModel().get_by_username(owner or TEST_USER_ADMIN_LOGIN) |
|
69 | 69 | r = RepoModel().create(form_data, cur_user) |
|
70 | 70 | Session().commit() |
|
71 | 71 | return r |
|
72 | 72 | |
|
73 | 73 | |
|
74 | 74 | def create_fork(fork_name, fork_type, fork_of): |
|
75 | 75 | fork = RepoModel(Session())._get_repo(fork_of) |
|
76 | 76 | r = create_repo(fork_name, fork_type) |
|
77 | 77 | r.fork = fork |
|
78 | 78 | Session().add(r) |
|
79 | 79 | Session().commit() |
|
80 | 80 | return r |
|
81 | 81 | |
|
82 | 82 | |
|
83 | 83 | def destroy_repo(repo_name): |
|
84 | 84 | RepoModel().delete(repo_name) |
|
85 | 85 | Session().commit() |
|
86 | 86 | |
|
87 | 87 | |
|
88 | 88 | class BaseTestApi(object): |
|
89 | 89 | REPO = None |
|
90 | 90 | REPO_TYPE = None |
|
91 | 91 | |
|
92 | 92 | @classmethod |
|
93 | 93 | def setUpClass(self): |
|
94 | 94 | self.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
95 | 95 | self.apikey = self.usr.api_key |
|
96 | 96 | self.test_user = UserModel().create_or_update( |
|
97 | 97 | username='test-api', |
|
98 | 98 | password='test', |
|
99 | 99 | email='test@api.rhodecode.org', |
|
100 | 100 | firstname='first', |
|
101 | 101 | lastname='last' |
|
102 | 102 | ) |
|
103 | 103 | Session().commit() |
|
104 | 104 | self.TEST_USER_LOGIN = self.test_user.username |
|
105 | 105 | self.apikey_regular = self.test_user.api_key |
|
106 | 106 | |
|
107 | 107 | @classmethod |
|
108 | 108 | def teardownClass(self): |
|
109 | 109 | pass |
|
110 | 110 | |
|
111 | 111 | def setUp(self): |
|
112 | 112 | self.maxDiff = None |
|
113 | 113 | make_users_group() |
|
114 | 114 | |
|
115 | 115 | def tearDown(self): |
|
116 | 116 | destroy_users_group() |
|
117 | 117 | |
|
118 | 118 | def _compare_ok(self, id_, expected, given): |
|
119 | 119 | expected = jsonify({ |
|
120 | 120 | 'id': id_, |
|
121 | 121 | 'error': None, |
|
122 | 122 | 'result': expected |
|
123 | 123 | }) |
|
124 | 124 | given = json.loads(given) |
|
125 | 125 | self.assertEqual(expected, given) |
|
126 | 126 | |
|
127 | 127 | def _compare_error(self, id_, expected, given): |
|
128 | 128 | expected = jsonify({ |
|
129 | 129 | 'id': id_, |
|
130 | 130 | 'error': expected, |
|
131 | 131 | 'result': None |
|
132 | 132 | }) |
|
133 | 133 | given = json.loads(given) |
|
134 | 134 | self.assertEqual(expected, given) |
|
135 | 135 | |
|
136 | 136 | # def test_Optional(self): |
|
137 | 137 | # from rhodecode.controllers.api.api import Optional |
|
138 | 138 | # option1 = Optional(None) |
|
139 | 139 | # self.assertEqual('<Optional:%s>' % None, repr(option1)) |
|
140 | 140 | # |
|
141 | 141 | # self.assertEqual(1, Optional.extract(Optional(1))) |
|
142 | 142 | # self.assertEqual('trololo', Optional.extract('trololo')) |
|
143 | 143 | |
|
144 | 144 | def test_api_wrong_key(self): |
|
145 | 145 | id_, params = _build_data('trololo', 'get_user') |
|
146 | 146 | response = api_call(self, params) |
|
147 | 147 | |
|
148 | 148 | expected = 'Invalid API KEY' |
|
149 | 149 | self._compare_error(id_, expected, given=response.body) |
|
150 | 150 | |
|
151 | 151 | def test_api_missing_non_optional_param(self): |
|
152 | 152 | id_, params = _build_data(self.apikey, 'get_repo') |
|
153 | 153 | response = api_call(self, params) |
|
154 | 154 | |
|
155 | 155 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
156 | 156 | self._compare_error(id_, expected, given=response.body) |
|
157 | 157 | |
|
158 | 158 | def test_api_missing_non_optional_param_args_null(self): |
|
159 | 159 | id_, params = _build_data(self.apikey, 'get_repo') |
|
160 | 160 | params = params.replace('"args": {}', '"args": null') |
|
161 | 161 | response = api_call(self, params) |
|
162 | 162 | |
|
163 | 163 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
164 | 164 | self._compare_error(id_, expected, given=response.body) |
|
165 | 165 | |
|
166 | 166 | def test_api_missing_non_optional_param_args_bad(self): |
|
167 | 167 | id_, params = _build_data(self.apikey, 'get_repo') |
|
168 | 168 | params = params.replace('"args": {}', '"args": 1') |
|
169 | 169 | response = api_call(self, params) |
|
170 | 170 | |
|
171 | 171 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
172 | 172 | self._compare_error(id_, expected, given=response.body) |
|
173 | 173 | |
|
174 | 174 | def test_api_args_is_null(self): |
|
175 | 175 | id_, params = _build_data(self.apikey, 'get_users',) |
|
176 | 176 | params = params.replace('"args": {}', '"args": null') |
|
177 | 177 | response = api_call(self, params) |
|
178 | 178 | self.assertEqual(response.status, '200 OK') |
|
179 | 179 | |
|
180 | 180 | def test_api_args_is_bad(self): |
|
181 | 181 | id_, params = _build_data(self.apikey, 'get_users',) |
|
182 | 182 | params = params.replace('"args": {}', '"args": 1') |
|
183 | 183 | response = api_call(self, params) |
|
184 | 184 | self.assertEqual(response.status, '200 OK') |
|
185 | 185 | |
|
186 | 186 | def test_api_get_users(self): |
|
187 | 187 | id_, params = _build_data(self.apikey, 'get_users',) |
|
188 | 188 | response = api_call(self, params) |
|
189 | 189 | ret_all = [] |
|
190 | 190 | for usr in UserModel().get_all(): |
|
191 | 191 | ret = usr.get_api_data() |
|
192 | 192 | ret_all.append(jsonify(ret)) |
|
193 | 193 | expected = ret_all |
|
194 | 194 | self._compare_ok(id_, expected, given=response.body) |
|
195 | 195 | |
|
196 | 196 | def test_api_get_user(self): |
|
197 | 197 | id_, params = _build_data(self.apikey, 'get_user', |
|
198 | 198 | userid=TEST_USER_ADMIN_LOGIN) |
|
199 | 199 | response = api_call(self, params) |
|
200 | 200 | |
|
201 | 201 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
202 | 202 | ret = usr.get_api_data() |
|
203 | 203 | ret['permissions'] = AuthUser(usr.user_id).permissions |
|
204 | 204 | |
|
205 | 205 | expected = ret |
|
206 | 206 | self._compare_ok(id_, expected, given=response.body) |
|
207 | 207 | |
|
208 | 208 | def test_api_get_user_that_does_not_exist(self): |
|
209 | 209 | id_, params = _build_data(self.apikey, 'get_user', |
|
210 | 210 | userid='trololo') |
|
211 | 211 | response = api_call(self, params) |
|
212 | 212 | |
|
213 | 213 | expected = "user `%s` does not exist" % 'trololo' |
|
214 | 214 | self._compare_error(id_, expected, given=response.body) |
|
215 | 215 | |
|
216 | 216 | def test_api_get_user_without_giving_userid(self): |
|
217 | 217 | id_, params = _build_data(self.apikey, 'get_user') |
|
218 | 218 | response = api_call(self, params) |
|
219 | 219 | |
|
220 | 220 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
221 | 221 | ret = usr.get_api_data() |
|
222 | 222 | ret['permissions'] = AuthUser(usr.user_id).permissions |
|
223 | 223 | |
|
224 | 224 | expected = ret |
|
225 | 225 | self._compare_ok(id_, expected, given=response.body) |
|
226 | 226 | |
|
227 | 227 | def test_api_get_user_without_giving_userid_non_admin(self): |
|
228 | 228 | id_, params = _build_data(self.apikey_regular, 'get_user') |
|
229 | 229 | response = api_call(self, params) |
|
230 | 230 | |
|
231 | 231 | usr = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
232 | 232 | ret = usr.get_api_data() |
|
233 | 233 | ret['permissions'] = AuthUser(usr.user_id).permissions |
|
234 | 234 | |
|
235 | 235 | expected = ret |
|
236 | 236 | self._compare_ok(id_, expected, given=response.body) |
|
237 | 237 | |
|
238 | 238 | def test_api_get_user_with_giving_userid_non_admin(self): |
|
239 | 239 | id_, params = _build_data(self.apikey_regular, 'get_user', |
|
240 | 240 | userid=self.TEST_USER_LOGIN) |
|
241 | 241 | response = api_call(self, params) |
|
242 | 242 | |
|
243 | 243 | expected = 'userid is not the same as your user' |
|
244 | 244 | self._compare_error(id_, expected, given=response.body) |
|
245 | 245 | |
|
246 | 246 | def test_api_pull(self): |
|
247 | 247 | #TODO: issues with rhodecode_extras here.. not sure why ! |
|
248 | 248 | pass |
|
249 | 249 | |
|
250 | 250 | # repo_name = 'test_pull' |
|
251 | 251 | # r = create_repo(repo_name, self.REPO_TYPE) |
|
252 | 252 | # r.clone_uri = TEST_self.REPO |
|
253 | 253 | # Session.add(r) |
|
254 | 254 | # Session.commit() |
|
255 | 255 | # |
|
256 | 256 | # id_, params = _build_data(self.apikey, 'pull', |
|
257 | 257 | # repoid=repo_name,) |
|
258 | 258 | # response = self.app.post(API_URL, content_type='application/json', |
|
259 | 259 | # params=params) |
|
260 | 260 | # |
|
261 | 261 | # expected = 'Pulled from `%s`' % repo_name |
|
262 | 262 | # self._compare_ok(id_, expected, given=response.body) |
|
263 | 263 | # |
|
264 | 264 | # destroy_repo(repo_name) |
|
265 | 265 | |
|
266 | 266 | def test_api_pull_error(self): |
|
267 | 267 | id_, params = _build_data(self.apikey, 'pull', |
|
268 | 268 | repoid=self.REPO,) |
|
269 | 269 | response = api_call(self, params) |
|
270 | 270 | |
|
271 | 271 | expected = 'Unable to pull changes from `%s`' % self.REPO |
|
272 | 272 | self._compare_error(id_, expected, given=response.body) |
|
273 | 273 | |
|
274 | 274 | def test_api_rescan_repos(self): |
|
275 | 275 | id_, params = _build_data(self.apikey, 'rescan_repos') |
|
276 | 276 | response = api_call(self, params) |
|
277 | 277 | |
|
278 | 278 | expected = {'added': [], 'removed': []} |
|
279 | 279 | self._compare_ok(id_, expected, given=response.body) |
|
280 | 280 | |
|
281 | 281 | @mock.patch.object(ScmModel, 'repo_scan', crash) |
|
282 | 282 | def test_api_rescann_error(self): |
|
283 | 283 | id_, params = _build_data(self.apikey, 'rescan_repos',) |
|
284 | 284 | response = api_call(self, params) |
|
285 | 285 | |
|
286 | 286 | expected = 'Error occurred during rescan repositories action' |
|
287 | 287 | self._compare_error(id_, expected, given=response.body) |
|
288 | 288 | |
|
289 | 289 | def test_api_invalidate_cache(self): |
|
290 | 290 | id_, params = _build_data(self.apikey, 'invalidate_cache', |
|
291 | 291 | repoid=self.REPO) |
|
292 | 292 | response = api_call(self, params) |
|
293 | 293 | |
|
294 | 294 | expected = ("Cache for repository `%s` was invalidated: " |
|
295 | 295 | "invalidated cache keys: %s" % (self.REPO, |
|
296 | 296 | [unicode(self.REPO)])) |
|
297 | 297 | self._compare_ok(id_, expected, given=response.body) |
|
298 | 298 | |
|
299 | 299 | @mock.patch.object(ScmModel, 'mark_for_invalidation', crash) |
|
300 | 300 | def test_api_invalidate_cache_error(self): |
|
301 | 301 | id_, params = _build_data(self.apikey, 'invalidate_cache', |
|
302 | 302 | repoid=self.REPO) |
|
303 | 303 | response = api_call(self, params) |
|
304 | 304 | |
|
305 | 305 | expected = 'Error occurred during cache invalidation action' |
|
306 | 306 | self._compare_error(id_, expected, given=response.body) |
|
307 | 307 | |
|
308 | 308 | def test_api_lock_repo_lock_aquire(self): |
|
309 | 309 | id_, params = _build_data(self.apikey, 'lock', |
|
310 | 310 | userid=TEST_USER_ADMIN_LOGIN, |
|
311 | 311 | repoid=self.REPO, |
|
312 | 312 | locked=True) |
|
313 | 313 | response = api_call(self, params) |
|
314 | 314 | expected = ('User `%s` set lock state for repo `%s` to `%s`' |
|
315 | 315 | % (TEST_USER_ADMIN_LOGIN, self.REPO, True)) |
|
316 | 316 | self._compare_ok(id_, expected, given=response.body) |
|
317 | 317 | |
|
318 | 318 | def test_api_lock_repo_lock_aquire_by_non_admin(self): |
|
319 | 319 | repo_name = 'api_delete_me' |
|
320 | 320 | create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN) |
|
321 | 321 | try: |
|
322 | 322 | id_, params = _build_data(self.apikey_regular, 'lock', |
|
323 | 323 | repoid=repo_name, |
|
324 | 324 | locked=True) |
|
325 | 325 | response = api_call(self, params) |
|
326 | 326 | expected = ('User `%s` set lock state for repo `%s` to `%s`' |
|
327 | 327 | % (self.TEST_USER_LOGIN, repo_name, True)) |
|
328 | 328 | self._compare_ok(id_, expected, given=response.body) |
|
329 | 329 | finally: |
|
330 | 330 | destroy_repo(repo_name) |
|
331 | 331 | |
|
332 | 332 | def test_api_lock_repo_lock_aquire_non_admin_with_userid(self): |
|
333 | 333 | repo_name = 'api_delete_me' |
|
334 | 334 | create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN) |
|
335 | 335 | try: |
|
336 | 336 | id_, params = _build_data(self.apikey_regular, 'lock', |
|
337 | 337 | userid=TEST_USER_ADMIN_LOGIN, |
|
338 | 338 | repoid=repo_name, |
|
339 | 339 | locked=True) |
|
340 | 340 | response = api_call(self, params) |
|
341 | 341 | expected = 'userid is not the same as your user' |
|
342 | 342 | self._compare_error(id_, expected, given=response.body) |
|
343 | 343 | finally: |
|
344 | 344 | destroy_repo(repo_name) |
|
345 | 345 | |
|
346 | 346 | def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self): |
|
347 | 347 | id_, params = _build_data(self.apikey_regular, 'lock', |
|
348 | 348 | repoid=self.REPO, |
|
349 | 349 | locked=True) |
|
350 | 350 | response = api_call(self, params) |
|
351 | 351 | expected = 'repository `%s` does not exist' % (self.REPO) |
|
352 | 352 | self._compare_error(id_, expected, given=response.body) |
|
353 | 353 | |
|
354 | 354 | def test_api_lock_repo_lock_release(self): |
|
355 | 355 | id_, params = _build_data(self.apikey, 'lock', |
|
356 | 356 | userid=TEST_USER_ADMIN_LOGIN, |
|
357 | 357 | repoid=self.REPO, |
|
358 | 358 | locked=False) |
|
359 | 359 | response = api_call(self, params) |
|
360 | 360 | expected = ('User `%s` set lock state for repo `%s` to `%s`' |
|
361 | 361 | % (TEST_USER_ADMIN_LOGIN, self.REPO, False)) |
|
362 | 362 | self._compare_ok(id_, expected, given=response.body) |
|
363 | 363 | |
|
364 | 364 | def test_api_lock_repo_lock_aquire_optional_userid(self): |
|
365 | 365 | id_, params = _build_data(self.apikey, 'lock', |
|
366 | 366 | repoid=self.REPO, |
|
367 | 367 | locked=True) |
|
368 | 368 | response = api_call(self, params) |
|
369 | 369 | expected = ('User `%s` set lock state for repo `%s` to `%s`' |
|
370 | 370 | % (TEST_USER_ADMIN_LOGIN, self.REPO, True)) |
|
371 | 371 | self._compare_ok(id_, expected, given=response.body) |
|
372 | 372 | |
|
373 | 373 | @mock.patch.object(Repository, 'lock', crash) |
|
374 | 374 | def test_api_lock_error(self): |
|
375 | 375 | id_, params = _build_data(self.apikey, 'lock', |
|
376 | 376 | userid=TEST_USER_ADMIN_LOGIN, |
|
377 | 377 | repoid=self.REPO, |
|
378 | 378 | locked=True) |
|
379 | 379 | response = api_call(self, params) |
|
380 | 380 | |
|
381 | 381 | expected = 'Error occurred locking repository `%s`' % self.REPO |
|
382 | 382 | self._compare_error(id_, expected, given=response.body) |
|
383 | 383 | |
|
384 | 384 | def test_api_create_existing_user(self): |
|
385 | 385 | id_, params = _build_data(self.apikey, 'create_user', |
|
386 | 386 | username=TEST_USER_ADMIN_LOGIN, |
|
387 | 387 | email='test@foo.com', |
|
388 | 388 | password='trololo') |
|
389 | 389 | response = api_call(self, params) |
|
390 | 390 | |
|
391 | 391 | expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN |
|
392 | 392 | self._compare_error(id_, expected, given=response.body) |
|
393 | 393 | |
|
394 | 394 | def test_api_create_user_with_existing_email(self): |
|
395 | 395 | id_, params = _build_data(self.apikey, 'create_user', |
|
396 | 396 | username=TEST_USER_ADMIN_LOGIN + 'new', |
|
397 | 397 | email=TEST_USER_REGULAR_EMAIL, |
|
398 | 398 | password='trololo') |
|
399 | 399 | response = api_call(self, params) |
|
400 | 400 | |
|
401 | 401 | expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL |
|
402 | 402 | self._compare_error(id_, expected, given=response.body) |
|
403 | 403 | |
|
404 | 404 | def test_api_create_user(self): |
|
405 | 405 | username = 'test_new_api_user' |
|
406 | 406 | email = username + "@foo.com" |
|
407 | 407 | |
|
408 | 408 | id_, params = _build_data(self.apikey, 'create_user', |
|
409 | 409 | username=username, |
|
410 | 410 | email=email, |
|
411 | 411 | password='trololo') |
|
412 | 412 | response = api_call(self, params) |
|
413 | 413 | |
|
414 | 414 | usr = UserModel().get_by_username(username) |
|
415 | 415 | ret = dict( |
|
416 | 416 | msg='created new user `%s`' % username, |
|
417 | 417 | user=jsonify(usr.get_api_data()) |
|
418 | 418 | ) |
|
419 | 419 | |
|
420 | 420 | expected = ret |
|
421 | 421 | self._compare_ok(id_, expected, given=response.body) |
|
422 | 422 | |
|
423 | 423 | UserModel().delete(usr.user_id) |
|
424 | 424 | Session().commit() |
|
425 | 425 | |
|
426 | 426 | @mock.patch.object(UserModel, 'create_or_update', crash) |
|
427 | 427 | def test_api_create_user_when_exception_happened(self): |
|
428 | 428 | |
|
429 | 429 | username = 'test_new_api_user' |
|
430 | 430 | email = username + "@foo.com" |
|
431 | 431 | |
|
432 | 432 | id_, params = _build_data(self.apikey, 'create_user', |
|
433 | 433 | username=username, |
|
434 | 434 | email=email, |
|
435 | 435 | password='trololo') |
|
436 | 436 | response = api_call(self, params) |
|
437 | 437 | expected = 'failed to create user `%s`' % username |
|
438 | 438 | self._compare_error(id_, expected, given=response.body) |
|
439 | 439 | |
|
440 | 440 | def test_api_delete_user(self): |
|
441 | 441 | usr = UserModel().create_or_update(username=u'test_user', |
|
442 | 442 | password=u'qweqwe', |
|
443 | 443 | email=u'u232@rhodecode.org', |
|
444 | 444 | firstname=u'u1', lastname=u'u1') |
|
445 | 445 | Session().commit() |
|
446 | 446 | username = usr.username |
|
447 | 447 | email = usr.email |
|
448 | 448 | usr_id = usr.user_id |
|
449 | 449 | ## DELETE THIS USER NOW |
|
450 | 450 | |
|
451 | 451 | id_, params = _build_data(self.apikey, 'delete_user', |
|
452 | 452 | userid=username,) |
|
453 | 453 | response = api_call(self, params) |
|
454 | 454 | |
|
455 | 455 | ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username), |
|
456 | 456 | 'user': None} |
|
457 | 457 | expected = ret |
|
458 | 458 | self._compare_ok(id_, expected, given=response.body) |
|
459 | 459 | |
|
460 | 460 | @mock.patch.object(UserModel, 'delete', crash) |
|
461 | 461 | def test_api_delete_user_when_exception_happened(self): |
|
462 | 462 | usr = UserModel().create_or_update(username=u'test_user', |
|
463 | 463 | password=u'qweqwe', |
|
464 | 464 | email=u'u232@rhodecode.org', |
|
465 | 465 | firstname=u'u1', lastname=u'u1') |
|
466 | 466 | Session().commit() |
|
467 | 467 | username = usr.username |
|
468 | 468 | |
|
469 | 469 | id_, params = _build_data(self.apikey, 'delete_user', |
|
470 | 470 | userid=username,) |
|
471 | 471 | response = api_call(self, params) |
|
472 | 472 | ret = 'failed to delete ID:%s %s' % (usr.user_id, |
|
473 | 473 | usr.username) |
|
474 | 474 | expected = ret |
|
475 | 475 | self._compare_error(id_, expected, given=response.body) |
|
476 | 476 | |
|
477 | 477 | @parameterized.expand([('firstname', 'new_username'), |
|
478 | 478 | ('lastname', 'new_username'), |
|
479 | 479 | ('email', 'new_username'), |
|
480 | 480 | ('admin', True), |
|
481 | 481 | ('admin', False), |
|
482 | 482 | ('ldap_dn', 'test'), |
|
483 | 483 | ('ldap_dn', None), |
|
484 | 484 | ('active', False), |
|
485 | 485 | ('active', True), |
|
486 | 486 | ('password', 'newpass') |
|
487 | 487 | ]) |
|
488 | 488 | def test_api_update_user(self, name, expected): |
|
489 | 489 | usr = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
490 | 490 | kw = {name: expected, |
|
491 | 491 | 'userid': usr.user_id} |
|
492 | 492 | id_, params = _build_data(self.apikey, 'update_user', **kw) |
|
493 | 493 | response = api_call(self, params) |
|
494 | 494 | |
|
495 | 495 | ret = { |
|
496 | 496 | 'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN), |
|
497 | 497 | 'user': jsonify(UserModel()\ |
|
498 | 498 | .get_by_username(self.TEST_USER_LOGIN)\ |
|
499 | 499 | .get_api_data()) |
|
500 | 500 | } |
|
501 | 501 | |
|
502 | 502 | expected = ret |
|
503 | 503 | self._compare_ok(id_, expected, given=response.body) |
|
504 | 504 | |
|
505 | 505 | def test_api_update_user_no_changed_params(self): |
|
506 | 506 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
507 | 507 | ret = jsonify(usr.get_api_data()) |
|
508 | 508 | id_, params = _build_data(self.apikey, 'update_user', |
|
509 | 509 | userid=TEST_USER_ADMIN_LOGIN) |
|
510 | 510 | |
|
511 | 511 | response = api_call(self, params) |
|
512 | 512 | ret = { |
|
513 | 513 | 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN), |
|
514 | 514 | 'user': ret |
|
515 | 515 | } |
|
516 | 516 | expected = ret |
|
517 | 517 | self._compare_ok(id_, expected, given=response.body) |
|
518 | 518 | |
|
519 | 519 | def test_api_update_user_by_user_id(self): |
|
520 | 520 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
521 | 521 | ret = jsonify(usr.get_api_data()) |
|
522 | 522 | id_, params = _build_data(self.apikey, 'update_user', |
|
523 | 523 | userid=usr.user_id) |
|
524 | 524 | |
|
525 | 525 | response = api_call(self, params) |
|
526 | 526 | ret = { |
|
527 | 527 | 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN), |
|
528 | 528 | 'user': ret |
|
529 | 529 | } |
|
530 | 530 | expected = ret |
|
531 | 531 | self._compare_ok(id_, expected, given=response.body) |
|
532 | 532 | |
|
533 | 533 | @mock.patch.object(UserModel, 'update_user', crash) |
|
534 | 534 | def test_api_update_user_when_exception_happens(self): |
|
535 | 535 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
536 | 536 | ret = jsonify(usr.get_api_data()) |
|
537 | 537 | id_, params = _build_data(self.apikey, 'update_user', |
|
538 | 538 | userid=usr.user_id) |
|
539 | 539 | |
|
540 | 540 | response = api_call(self, params) |
|
541 | 541 | ret = 'failed to update user `%s`' % usr.user_id |
|
542 | 542 | |
|
543 | 543 | expected = ret |
|
544 | 544 | self._compare_error(id_, expected, given=response.body) |
|
545 | 545 | |
|
546 | 546 | def test_api_get_repo(self): |
|
547 | 547 | new_group = 'some_new_group' |
|
548 | 548 | make_users_group(new_group) |
|
549 | 549 | RepoModel().grant_users_group_permission(repo=self.REPO, |
|
550 | 550 | group_name=new_group, |
|
551 | 551 | perm='repository.read') |
|
552 | 552 | Session().commit() |
|
553 | 553 | id_, params = _build_data(self.apikey, 'get_repo', |
|
554 | 554 | repoid=self.REPO) |
|
555 | 555 | response = api_call(self, params) |
|
556 | 556 | |
|
557 | 557 | repo = RepoModel().get_by_repo_name(self.REPO) |
|
558 | 558 | ret = repo.get_api_data() |
|
559 | 559 | |
|
560 | 560 | members = [] |
|
561 | 561 | followers = [] |
|
562 | 562 | for user in repo.repo_to_perm: |
|
563 | 563 | perm = user.permission.permission_name |
|
564 | 564 | user = user.user |
|
565 | 565 | user_data = user.get_api_data() |
|
566 | 566 | user_data['type'] = "user" |
|
567 | 567 | user_data['permission'] = perm |
|
568 | 568 | members.append(user_data) |
|
569 | 569 | |
|
570 | 570 | for users_group in repo.users_group_to_perm: |
|
571 | 571 | perm = users_group.permission.permission_name |
|
572 | 572 | users_group = users_group.users_group |
|
573 | 573 | users_group_data = users_group.get_api_data() |
|
574 | 574 | users_group_data['type'] = "users_group" |
|
575 | 575 | users_group_data['permission'] = perm |
|
576 | 576 | members.append(users_group_data) |
|
577 | 577 | |
|
578 | 578 | for user in repo.followers: |
|
579 | 579 | followers.append(user.user.get_api_data()) |
|
580 | 580 | |
|
581 | 581 | ret['members'] = members |
|
582 | 582 | ret['followers'] = followers |
|
583 | 583 | |
|
584 | 584 | expected = ret |
|
585 | 585 | self._compare_ok(id_, expected, given=response.body) |
|
586 | 586 | destroy_users_group(new_group) |
|
587 | 587 | |
|
588 | 588 | def test_api_get_repo_by_non_admin(self): |
|
589 | 589 | id_, params = _build_data(self.apikey, 'get_repo', |
|
590 | 590 | repoid=self.REPO) |
|
591 | 591 | response = api_call(self, params) |
|
592 | 592 | |
|
593 | 593 | repo = RepoModel().get_by_repo_name(self.REPO) |
|
594 | 594 | ret = repo.get_api_data() |
|
595 | 595 | |
|
596 | 596 | members = [] |
|
597 | 597 | followers = [] |
|
598 | 598 | for user in repo.repo_to_perm: |
|
599 | 599 | perm = user.permission.permission_name |
|
600 | 600 | user = user.user |
|
601 | 601 | user_data = user.get_api_data() |
|
602 | 602 | user_data['type'] = "user" |
|
603 | 603 | user_data['permission'] = perm |
|
604 | 604 | members.append(user_data) |
|
605 | 605 | |
|
606 | 606 | for users_group in repo.users_group_to_perm: |
|
607 | 607 | perm = users_group.permission.permission_name |
|
608 | 608 | users_group = users_group.users_group |
|
609 | 609 | users_group_data = users_group.get_api_data() |
|
610 | 610 | users_group_data['type'] = "users_group" |
|
611 | 611 | users_group_data['permission'] = perm |
|
612 | 612 | members.append(users_group_data) |
|
613 | 613 | |
|
614 | 614 | for user in repo.followers: |
|
615 | 615 | followers.append(user.user.get_api_data()) |
|
616 | 616 | |
|
617 | 617 | ret['members'] = members |
|
618 | 618 | ret['followers'] = followers |
|
619 | 619 | |
|
620 | 620 | expected = ret |
|
621 | 621 | self._compare_ok(id_, expected, given=response.body) |
|
622 | 622 | |
|
623 | 623 | def test_api_get_repo_by_non_admin_no_permission_to_repo(self): |
|
624 | 624 | RepoModel().grant_user_permission(repo=self.REPO, |
|
625 | 625 | user=self.TEST_USER_LOGIN, |
|
626 | 626 | perm='repository.none') |
|
627 | 627 | |
|
628 | 628 | id_, params = _build_data(self.apikey_regular, 'get_repo', |
|
629 | 629 | repoid=self.REPO) |
|
630 | 630 | response = api_call(self, params) |
|
631 | 631 | |
|
632 | 632 | expected = 'repository `%s` does not exist' % (self.REPO) |
|
633 | 633 | self._compare_error(id_, expected, given=response.body) |
|
634 | 634 | |
|
635 | 635 | def test_api_get_repo_that_doesn_not_exist(self): |
|
636 | 636 | id_, params = _build_data(self.apikey, 'get_repo', |
|
637 | 637 | repoid='no-such-repo') |
|
638 | 638 | response = api_call(self, params) |
|
639 | 639 | |
|
640 | 640 | ret = 'repository `%s` does not exist' % 'no-such-repo' |
|
641 | 641 | expected = ret |
|
642 | 642 | self._compare_error(id_, expected, given=response.body) |
|
643 | 643 | |
|
644 | 644 | def test_api_get_repos(self): |
|
645 | 645 | id_, params = _build_data(self.apikey, 'get_repos') |
|
646 | 646 | response = api_call(self, params) |
|
647 | 647 | |
|
648 | 648 | result = [] |
|
649 | 649 | for repo in RepoModel().get_all(): |
|
650 | 650 | result.append(repo.get_api_data()) |
|
651 | 651 | ret = jsonify(result) |
|
652 | 652 | |
|
653 | 653 | expected = ret |
|
654 | 654 | self._compare_ok(id_, expected, given=response.body) |
|
655 | 655 | |
|
656 | 656 | def test_api_get_repos_non_admin(self): |
|
657 | 657 | id_, params = _build_data(self.apikey_regular, 'get_repos') |
|
658 | 658 | response = api_call(self, params) |
|
659 | 659 | |
|
660 | 660 | result = [] |
|
661 | 661 | for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN): |
|
662 | 662 | result.append(repo.get_api_data()) |
|
663 | 663 | ret = jsonify(result) |
|
664 | 664 | |
|
665 | 665 | expected = ret |
|
666 | 666 | self._compare_ok(id_, expected, given=response.body) |
|
667 | 667 | |
|
668 | 668 | @parameterized.expand([('all', 'all'), |
|
669 | 669 | ('dirs', 'dirs'), |
|
670 | 670 | ('files', 'files'), ]) |
|
671 | 671 | def test_api_get_repo_nodes(self, name, ret_type): |
|
672 | 672 | rev = 'tip' |
|
673 | 673 | path = '/' |
|
674 | 674 | id_, params = _build_data(self.apikey, 'get_repo_nodes', |
|
675 | 675 | repoid=self.REPO, revision=rev, |
|
676 | 676 | root_path=path, |
|
677 | 677 | ret_type=ret_type) |
|
678 | 678 | response = api_call(self, params) |
|
679 | 679 | |
|
680 | 680 | # we don't the actual return types here since it's tested somewhere |
|
681 | 681 | # else |
|
682 | 682 | expected = json.loads(response.body)['result'] |
|
683 | 683 | self._compare_ok(id_, expected, given=response.body) |
|
684 | 684 | |
|
685 | 685 | def test_api_get_repo_nodes_bad_revisions(self): |
|
686 | 686 | rev = 'i-dont-exist' |
|
687 | 687 | path = '/' |
|
688 | 688 | id_, params = _build_data(self.apikey, 'get_repo_nodes', |
|
689 | 689 | repoid=self.REPO, revision=rev, |
|
690 | 690 | root_path=path,) |
|
691 | 691 | response = api_call(self, params) |
|
692 | 692 | |
|
693 | 693 | expected = 'failed to get repo: `%s` nodes' % self.REPO |
|
694 | 694 | self._compare_error(id_, expected, given=response.body) |
|
695 | 695 | |
|
696 | 696 | def test_api_get_repo_nodes_bad_path(self): |
|
697 | 697 | rev = 'tip' |
|
698 | 698 | path = '/idontexits' |
|
699 | 699 | id_, params = _build_data(self.apikey, 'get_repo_nodes', |
|
700 | 700 | repoid=self.REPO, revision=rev, |
|
701 | 701 | root_path=path,) |
|
702 | 702 | response = api_call(self, params) |
|
703 | 703 | |
|
704 | 704 | expected = 'failed to get repo: `%s` nodes' % self.REPO |
|
705 | 705 | self._compare_error(id_, expected, given=response.body) |
|
706 | 706 | |
|
707 | 707 | def test_api_get_repo_nodes_bad_ret_type(self): |
|
708 | 708 | rev = 'tip' |
|
709 | 709 | path = '/' |
|
710 | 710 | ret_type = 'error' |
|
711 | 711 | id_, params = _build_data(self.apikey, 'get_repo_nodes', |
|
712 | 712 | repoid=self.REPO, revision=rev, |
|
713 | 713 | root_path=path, |
|
714 | 714 | ret_type=ret_type) |
|
715 | 715 | response = api_call(self, params) |
|
716 | 716 | |
|
717 | 717 | expected = 'ret_type must be one of %s' % (['files', 'dirs', 'all']) |
|
718 | 718 | self._compare_error(id_, expected, given=response.body) |
|
719 | 719 | |
|
720 | 720 | def test_api_create_repo(self): |
|
721 | 721 | repo_name = 'api-repo' |
|
722 | 722 | id_, params = _build_data(self.apikey, 'create_repo', |
|
723 | 723 | repo_name=repo_name, |
|
724 | 724 | owner=TEST_USER_ADMIN_LOGIN, |
|
725 | 725 | repo_type='hg', |
|
726 | 726 | ) |
|
727 | 727 | response = api_call(self, params) |
|
728 | 728 | |
|
729 | 729 | repo = RepoModel().get_by_repo_name(repo_name) |
|
730 | 730 | ret = { |
|
731 | 731 | 'msg': 'Created new repository `%s`' % repo_name, |
|
732 | 732 | 'repo': jsonify(repo.get_api_data()) |
|
733 | 733 | } |
|
734 | 734 | expected = ret |
|
735 | 735 | self._compare_ok(id_, expected, given=response.body) |
|
736 | 736 | destroy_repo(repo_name) |
|
737 | 737 | |
|
738 | 738 | def test_api_create_repo_unknown_owner(self): |
|
739 | 739 | repo_name = 'api-repo' |
|
740 | 740 | owner = 'i-dont-exist' |
|
741 | 741 | id_, params = _build_data(self.apikey, 'create_repo', |
|
742 | 742 | repo_name=repo_name, |
|
743 | 743 | owner=owner, |
|
744 | 744 | repo_type='hg', |
|
745 | 745 | ) |
|
746 | 746 | response = api_call(self, params) |
|
747 | 747 | expected = 'user `%s` does not exist' % owner |
|
748 | 748 | self._compare_error(id_, expected, given=response.body) |
|
749 | 749 | |
|
750 | 750 | def test_api_create_repo_dont_specify_owner(self): |
|
751 | 751 | repo_name = 'api-repo' |
|
752 | 752 | owner = 'i-dont-exist' |
|
753 | 753 | id_, params = _build_data(self.apikey, 'create_repo', |
|
754 | 754 | repo_name=repo_name, |
|
755 | 755 | repo_type='hg', |
|
756 | 756 | ) |
|
757 | 757 | response = api_call(self, params) |
|
758 | 758 | |
|
759 | 759 | repo = RepoModel().get_by_repo_name(repo_name) |
|
760 | 760 | ret = { |
|
761 | 761 | 'msg': 'Created new repository `%s`' % repo_name, |
|
762 | 762 | 'repo': jsonify(repo.get_api_data()) |
|
763 | 763 | } |
|
764 | 764 | expected = ret |
|
765 | 765 | self._compare_ok(id_, expected, given=response.body) |
|
766 | 766 | destroy_repo(repo_name) |
|
767 | 767 | |
|
768 | 768 | def test_api_create_repo_by_non_admin(self): |
|
769 | 769 | repo_name = 'api-repo' |
|
770 | 770 | owner = 'i-dont-exist' |
|
771 | 771 | id_, params = _build_data(self.apikey_regular, 'create_repo', |
|
772 | 772 | repo_name=repo_name, |
|
773 | 773 | repo_type='hg', |
|
774 | 774 | ) |
|
775 | 775 | response = api_call(self, params) |
|
776 | 776 | |
|
777 | 777 | repo = RepoModel().get_by_repo_name(repo_name) |
|
778 | 778 | ret = { |
|
779 | 779 | 'msg': 'Created new repository `%s`' % repo_name, |
|
780 | 780 | 'repo': jsonify(repo.get_api_data()) |
|
781 | 781 | } |
|
782 | 782 | expected = ret |
|
783 | 783 | self._compare_ok(id_, expected, given=response.body) |
|
784 | 784 | destroy_repo(repo_name) |
|
785 | 785 | |
|
786 | 786 | def test_api_create_repo_by_non_admin_specify_owner(self): |
|
787 | 787 | repo_name = 'api-repo' |
|
788 | 788 | owner = 'i-dont-exist' |
|
789 | 789 | id_, params = _build_data(self.apikey_regular, 'create_repo', |
|
790 | 790 | repo_name=repo_name, |
|
791 | 791 | repo_type='hg', |
|
792 | 792 | owner=owner |
|
793 | 793 | ) |
|
794 | 794 | response = api_call(self, params) |
|
795 | 795 | |
|
796 | 796 | expected = 'Only RhodeCode admin can specify `owner` param' |
|
797 | 797 | self._compare_error(id_, expected, given=response.body) |
|
798 | 798 | destroy_repo(repo_name) |
|
799 | 799 | |
|
800 | 800 | def test_api_create_repo_exists(self): |
|
801 | 801 | repo_name = self.REPO |
|
802 | 802 | id_, params = _build_data(self.apikey, 'create_repo', |
|
803 | 803 | repo_name=repo_name, |
|
804 | 804 | owner=TEST_USER_ADMIN_LOGIN, |
|
805 | 805 | repo_type='hg', |
|
806 | 806 | ) |
|
807 | 807 | response = api_call(self, params) |
|
808 | 808 | expected = "repo `%s` already exist" % repo_name |
|
809 | 809 | self._compare_error(id_, expected, given=response.body) |
|
810 | 810 | |
|
811 | 811 | @mock.patch.object(RepoModel, 'create_repo', crash) |
|
812 | 812 | def test_api_create_repo_exception_occurred(self): |
|
813 | 813 | repo_name = 'api-repo' |
|
814 | 814 | id_, params = _build_data(self.apikey, 'create_repo', |
|
815 | 815 | repo_name=repo_name, |
|
816 | 816 | owner=TEST_USER_ADMIN_LOGIN, |
|
817 | 817 | repo_type='hg', |
|
818 | 818 | ) |
|
819 | 819 | response = api_call(self, params) |
|
820 | 820 | expected = 'failed to create repository `%s`' % repo_name |
|
821 | 821 | self._compare_error(id_, expected, given=response.body) |
|
822 | 822 | |
|
823 | 823 | def test_api_delete_repo(self): |
|
824 | 824 | repo_name = 'api_delete_me' |
|
825 | 825 | create_repo(repo_name, self.REPO_TYPE) |
|
826 | 826 | |
|
827 | 827 | id_, params = _build_data(self.apikey, 'delete_repo', |
|
828 | 828 | repoid=repo_name,) |
|
829 | 829 | response = api_call(self, params) |
|
830 | 830 | |
|
831 | 831 | ret = { |
|
832 | 832 | 'msg': 'Deleted repository `%s`' % repo_name, |
|
833 | 833 | 'success': True |
|
834 | 834 | } |
|
835 | 835 | expected = ret |
|
836 | 836 | self._compare_ok(id_, expected, given=response.body) |
|
837 | 837 | |
|
838 | 838 | def test_api_delete_repo_by_non_admin(self): |
|
839 | 839 | repo_name = 'api_delete_me' |
|
840 | 840 | create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN) |
|
841 | 841 | try: |
|
842 | 842 | id_, params = _build_data(self.apikey_regular, 'delete_repo', |
|
843 | 843 | repoid=repo_name,) |
|
844 | 844 | response = api_call(self, params) |
|
845 | 845 | |
|
846 | 846 | ret = { |
|
847 | 847 | 'msg': 'Deleted repository `%s`' % repo_name, |
|
848 | 848 | 'success': True |
|
849 | 849 | } |
|
850 | 850 | expected = ret |
|
851 | 851 | self._compare_ok(id_, expected, given=response.body) |
|
852 | 852 | finally: |
|
853 | 853 | destroy_repo(repo_name) |
|
854 | 854 | |
|
855 | 855 | def test_api_delete_repo_by_non_admin_no_permission(self): |
|
856 | 856 | repo_name = 'api_delete_me' |
|
857 | 857 | create_repo(repo_name, self.REPO_TYPE) |
|
858 | 858 | try: |
|
859 | 859 | id_, params = _build_data(self.apikey_regular, 'delete_repo', |
|
860 | 860 | repoid=repo_name,) |
|
861 | 861 | response = api_call(self, params) |
|
862 | 862 | expected = 'repository `%s` does not exist' % (repo_name) |
|
863 | 863 | self._compare_error(id_, expected, given=response.body) |
|
864 | 864 | finally: |
|
865 | 865 | destroy_repo(repo_name) |
|
866 | 866 | |
|
867 | 867 | def test_api_delete_repo_exception_occurred(self): |
|
868 | 868 | repo_name = 'api_delete_me' |
|
869 | 869 | create_repo(repo_name, self.REPO_TYPE) |
|
870 | 870 | try: |
|
871 | 871 | with mock.patch.object(RepoModel, 'delete', crash): |
|
872 | 872 | id_, params = _build_data(self.apikey, 'delete_repo', |
|
873 | 873 | repoid=repo_name,) |
|
874 | 874 | response = api_call(self, params) |
|
875 | 875 | |
|
876 | 876 | expected = 'failed to delete repository `%s`' % repo_name |
|
877 | 877 | self._compare_error(id_, expected, given=response.body) |
|
878 | 878 | finally: |
|
879 | 879 | destroy_repo(repo_name) |
|
880 | 880 | |
|
881 | 881 | def test_api_fork_repo(self): |
|
882 | 882 | fork_name = 'api-repo-fork' |
|
883 | 883 | id_, params = _build_data(self.apikey, 'fork_repo', |
|
884 | 884 | repoid=self.REPO, |
|
885 | 885 | fork_name=fork_name, |
|
886 | 886 | owner=TEST_USER_ADMIN_LOGIN, |
|
887 | 887 | ) |
|
888 | 888 | response = api_call(self, params) |
|
889 | 889 | |
|
890 | 890 | ret = { |
|
891 | 891 | 'msg': 'Created fork of `%s` as `%s`' % (self.REPO, |
|
892 | 892 | fork_name), |
|
893 | 893 | 'success': True |
|
894 | 894 | } |
|
895 | 895 | expected = ret |
|
896 | 896 | self._compare_ok(id_, expected, given=response.body) |
|
897 | 897 | destroy_repo(fork_name) |
|
898 | 898 | |
|
899 | 899 | def test_api_fork_repo_non_admin(self): |
|
900 | 900 | fork_name = 'api-repo-fork' |
|
901 | 901 | id_, params = _build_data(self.apikey_regular, 'fork_repo', |
|
902 | 902 | repoid=self.REPO, |
|
903 | 903 | fork_name=fork_name, |
|
904 | 904 | ) |
|
905 | 905 | response = api_call(self, params) |
|
906 | 906 | |
|
907 | 907 | ret = { |
|
908 | 908 | 'msg': 'Created fork of `%s` as `%s`' % (self.REPO, |
|
909 | 909 | fork_name), |
|
910 | 910 | 'success': True |
|
911 | 911 | } |
|
912 | 912 | expected = ret |
|
913 | 913 | self._compare_ok(id_, expected, given=response.body) |
|
914 | 914 | destroy_repo(fork_name) |
|
915 | 915 | |
|
916 | 916 | def test_api_fork_repo_non_admin_specify_owner(self): |
|
917 | 917 | fork_name = 'api-repo-fork' |
|
918 | 918 | id_, params = _build_data(self.apikey_regular, 'fork_repo', |
|
919 | 919 | repoid=self.REPO, |
|
920 | 920 | fork_name=fork_name, |
|
921 | 921 | owner=TEST_USER_ADMIN_LOGIN, |
|
922 | 922 | ) |
|
923 | 923 | response = api_call(self, params) |
|
924 | 924 | expected = 'Only RhodeCode admin can specify `owner` param' |
|
925 | 925 | self._compare_error(id_, expected, given=response.body) |
|
926 | 926 | destroy_repo(fork_name) |
|
927 | 927 | |
|
928 | 928 | def test_api_fork_repo_non_admin_no_permission_to_fork(self): |
|
929 | 929 | RepoModel().grant_user_permission(repo=self.REPO, |
|
930 | 930 | user=self.TEST_USER_LOGIN, |
|
931 | 931 | perm='repository.none') |
|
932 | 932 | fork_name = 'api-repo-fork' |
|
933 | 933 | id_, params = _build_data(self.apikey_regular, 'fork_repo', |
|
934 | 934 | repoid=self.REPO, |
|
935 | 935 | fork_name=fork_name, |
|
936 | 936 | ) |
|
937 | 937 | response = api_call(self, params) |
|
938 | 938 | expected = 'repository `%s` does not exist' % (self.REPO) |
|
939 | 939 | self._compare_error(id_, expected, given=response.body) |
|
940 | 940 | destroy_repo(fork_name) |
|
941 | 941 | |
|
942 | 942 | def test_api_fork_repo_unknown_owner(self): |
|
943 | 943 | fork_name = 'api-repo-fork' |
|
944 | 944 | owner = 'i-dont-exist' |
|
945 | 945 | id_, params = _build_data(self.apikey, 'fork_repo', |
|
946 | 946 | repoid=self.REPO, |
|
947 | 947 | fork_name=fork_name, |
|
948 | 948 | owner=owner, |
|
949 | 949 | ) |
|
950 | 950 | response = api_call(self, params) |
|
951 | 951 | expected = 'user `%s` does not exist' % owner |
|
952 | 952 | self._compare_error(id_, expected, given=response.body) |
|
953 | 953 | |
|
954 | 954 | def test_api_fork_repo_fork_exists(self): |
|
955 | 955 | fork_name = 'api-repo-fork' |
|
956 | 956 | create_fork(fork_name, self.REPO_TYPE, self.REPO) |
|
957 | 957 | |
|
958 | 958 | try: |
|
959 | 959 | fork_name = 'api-repo-fork' |
|
960 | 960 | |
|
961 | 961 | id_, params = _build_data(self.apikey, 'fork_repo', |
|
962 | 962 | repoid=self.REPO, |
|
963 | 963 | fork_name=fork_name, |
|
964 | 964 | owner=TEST_USER_ADMIN_LOGIN, |
|
965 | 965 | ) |
|
966 | 966 | response = api_call(self, params) |
|
967 | 967 | |
|
968 | 968 | expected = "fork `%s` already exist" % fork_name |
|
969 | 969 | self._compare_error(id_, expected, given=response.body) |
|
970 | 970 | finally: |
|
971 | 971 | destroy_repo(fork_name) |
|
972 | 972 | |
|
973 | 973 | def test_api_fork_repo_repo_exists(self): |
|
974 | 974 | fork_name = self.REPO |
|
975 | 975 | |
|
976 | 976 | id_, params = _build_data(self.apikey, 'fork_repo', |
|
977 | 977 | repoid=self.REPO, |
|
978 | 978 | fork_name=fork_name, |
|
979 | 979 | owner=TEST_USER_ADMIN_LOGIN, |
|
980 | 980 | ) |
|
981 | 981 | response = api_call(self, params) |
|
982 | 982 | |
|
983 | 983 | expected = "repo `%s` already exist" % fork_name |
|
984 | 984 | self._compare_error(id_, expected, given=response.body) |
|
985 | 985 | |
|
986 | 986 | @mock.patch.object(RepoModel, 'create_fork', crash) |
|
987 | 987 | def test_api_fork_repo_exception_occurred(self): |
|
988 | 988 | fork_name = 'api-repo-fork' |
|
989 | 989 | id_, params = _build_data(self.apikey, 'fork_repo', |
|
990 | 990 | repoid=self.REPO, |
|
991 | 991 | fork_name=fork_name, |
|
992 | 992 | owner=TEST_USER_ADMIN_LOGIN, |
|
993 | 993 | ) |
|
994 | 994 | response = api_call(self, params) |
|
995 | 995 | |
|
996 | 996 | expected = 'failed to fork repository `%s` as `%s`' % (self.REPO, |
|
997 | 997 | fork_name) |
|
998 | 998 | self._compare_error(id_, expected, given=response.body) |
|
999 | 999 | |
|
1000 | 1000 | def test_api_get_users_group(self): |
|
1001 | 1001 | id_, params = _build_data(self.apikey, 'get_users_group', |
|
1002 | 1002 | usersgroupid=TEST_USERS_GROUP) |
|
1003 | 1003 | response = api_call(self, params) |
|
1004 | 1004 | |
|
1005 | 1005 | users_group = UsersGroupModel().get_group(TEST_USERS_GROUP) |
|
1006 | 1006 | members = [] |
|
1007 | 1007 | for user in users_group.members: |
|
1008 | 1008 | user = user.user |
|
1009 | 1009 | members.append(user.get_api_data()) |
|
1010 | 1010 | |
|
1011 | 1011 | ret = users_group.get_api_data() |
|
1012 | 1012 | ret['members'] = members |
|
1013 | 1013 | expected = ret |
|
1014 | 1014 | self._compare_ok(id_, expected, given=response.body) |
|
1015 | 1015 | |
|
1016 | 1016 | def test_api_get_users_groups(self): |
|
1017 | 1017 | |
|
1018 | 1018 | make_users_group('test_users_group2') |
|
1019 | 1019 | |
|
1020 | 1020 | id_, params = _build_data(self.apikey, 'get_users_groups',) |
|
1021 | 1021 | response = api_call(self, params) |
|
1022 | 1022 | |
|
1023 | 1023 | expected = [] |
|
1024 | 1024 | for gr_name in [TEST_USERS_GROUP, 'test_users_group2']: |
|
1025 | 1025 | users_group = UsersGroupModel().get_group(gr_name) |
|
1026 | 1026 | ret = users_group.get_api_data() |
|
1027 | 1027 | expected.append(ret) |
|
1028 | 1028 | self._compare_ok(id_, expected, given=response.body) |
|
1029 | 1029 | |
|
1030 | 1030 | UsersGroupModel().delete(users_group='test_users_group2') |
|
1031 | 1031 | Session().commit() |
|
1032 | 1032 | |
|
1033 | 1033 | def test_api_create_users_group(self): |
|
1034 | 1034 | group_name = 'some_new_group' |
|
1035 | 1035 | id_, params = _build_data(self.apikey, 'create_users_group', |
|
1036 | 1036 | group_name=group_name) |
|
1037 | 1037 | response = api_call(self, params) |
|
1038 | 1038 | |
|
1039 | 1039 | ret = { |
|
1040 |
'msg': 'created new user |
|
|
1040 | 'msg': 'created new user group `%s`' % group_name, | |
|
1041 | 1041 | 'users_group': jsonify(UsersGroupModel()\ |
|
1042 | 1042 | .get_by_name(group_name)\ |
|
1043 | 1043 | .get_api_data()) |
|
1044 | 1044 | } |
|
1045 | 1045 | expected = ret |
|
1046 | 1046 | self._compare_ok(id_, expected, given=response.body) |
|
1047 | 1047 | |
|
1048 | 1048 | destroy_users_group(group_name) |
|
1049 | 1049 | |
|
1050 | 1050 | def test_api_get_users_group_that_exist(self): |
|
1051 | 1051 | id_, params = _build_data(self.apikey, 'create_users_group', |
|
1052 | 1052 | group_name=TEST_USERS_GROUP) |
|
1053 | 1053 | response = api_call(self, params) |
|
1054 | 1054 | |
|
1055 |
expected = "user |
|
|
1055 | expected = "user group `%s` already exist" % TEST_USERS_GROUP | |
|
1056 | 1056 | self._compare_error(id_, expected, given=response.body) |
|
1057 | 1057 | |
|
1058 | 1058 | @mock.patch.object(UsersGroupModel, 'create', crash) |
|
1059 | 1059 | def test_api_get_users_group_exception_occurred(self): |
|
1060 | 1060 | group_name = 'exception_happens' |
|
1061 | 1061 | id_, params = _build_data(self.apikey, 'create_users_group', |
|
1062 | 1062 | group_name=group_name) |
|
1063 | 1063 | response = api_call(self, params) |
|
1064 | 1064 | |
|
1065 | 1065 | expected = 'failed to create group `%s`' % group_name |
|
1066 | 1066 | self._compare_error(id_, expected, given=response.body) |
|
1067 | 1067 | |
|
1068 | 1068 | def test_api_add_user_to_users_group(self): |
|
1069 | 1069 | gr_name = 'test_group' |
|
1070 | 1070 | UsersGroupModel().create(gr_name) |
|
1071 | 1071 | Session().commit() |
|
1072 | 1072 | id_, params = _build_data(self.apikey, 'add_user_to_users_group', |
|
1073 | 1073 | usersgroupid=gr_name, |
|
1074 | 1074 | userid=TEST_USER_ADMIN_LOGIN) |
|
1075 | 1075 | response = api_call(self, params) |
|
1076 | 1076 | |
|
1077 | 1077 | expected = { |
|
1078 |
'msg': 'added member `%s` to user |
|
|
1078 | 'msg': 'added member `%s` to user group `%s`' % ( | |
|
1079 | 1079 | TEST_USER_ADMIN_LOGIN, gr_name |
|
1080 | 1080 | ), |
|
1081 | 1081 | 'success': True} |
|
1082 | 1082 | self._compare_ok(id_, expected, given=response.body) |
|
1083 | 1083 | |
|
1084 | 1084 | UsersGroupModel().delete(users_group=gr_name) |
|
1085 | 1085 | Session().commit() |
|
1086 | 1086 | |
|
1087 | 1087 | def test_api_add_user_to_users_group_that_doesnt_exist(self): |
|
1088 | 1088 | id_, params = _build_data(self.apikey, 'add_user_to_users_group', |
|
1089 | 1089 | usersgroupid='false-group', |
|
1090 | 1090 | userid=TEST_USER_ADMIN_LOGIN) |
|
1091 | 1091 | response = api_call(self, params) |
|
1092 | 1092 | |
|
1093 |
expected = 'user |
|
|
1093 | expected = 'user group `%s` does not exist' % 'false-group' | |
|
1094 | 1094 | self._compare_error(id_, expected, given=response.body) |
|
1095 | 1095 | |
|
1096 | 1096 | @mock.patch.object(UsersGroupModel, 'add_user_to_group', crash) |
|
1097 | 1097 | def test_api_add_user_to_users_group_exception_occurred(self): |
|
1098 | 1098 | gr_name = 'test_group' |
|
1099 | 1099 | UsersGroupModel().create(gr_name) |
|
1100 | 1100 | Session().commit() |
|
1101 | 1101 | id_, params = _build_data(self.apikey, 'add_user_to_users_group', |
|
1102 | 1102 | usersgroupid=gr_name, |
|
1103 | 1103 | userid=TEST_USER_ADMIN_LOGIN) |
|
1104 | 1104 | response = api_call(self, params) |
|
1105 | 1105 | |
|
1106 |
expected = 'failed to add member to user |
|
|
1106 | expected = 'failed to add member to user group `%s`' % gr_name | |
|
1107 | 1107 | self._compare_error(id_, expected, given=response.body) |
|
1108 | 1108 | |
|
1109 | 1109 | UsersGroupModel().delete(users_group=gr_name) |
|
1110 | 1110 | Session().commit() |
|
1111 | 1111 | |
|
1112 | 1112 | def test_api_remove_user_from_users_group(self): |
|
1113 | 1113 | gr_name = 'test_group_3' |
|
1114 | 1114 | gr = UsersGroupModel().create(gr_name) |
|
1115 | 1115 | UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN) |
|
1116 | 1116 | Session().commit() |
|
1117 | 1117 | id_, params = _build_data(self.apikey, 'remove_user_from_users_group', |
|
1118 | 1118 | usersgroupid=gr_name, |
|
1119 | 1119 | userid=TEST_USER_ADMIN_LOGIN) |
|
1120 | 1120 | response = api_call(self, params) |
|
1121 | 1121 | |
|
1122 | 1122 | expected = { |
|
1123 |
'msg': 'removed member `%s` from user |
|
|
1123 | 'msg': 'removed member `%s` from user group `%s`' % ( | |
|
1124 | 1124 | TEST_USER_ADMIN_LOGIN, gr_name |
|
1125 | 1125 | ), |
|
1126 | 1126 | 'success': True} |
|
1127 | 1127 | self._compare_ok(id_, expected, given=response.body) |
|
1128 | 1128 | |
|
1129 | 1129 | UsersGroupModel().delete(users_group=gr_name) |
|
1130 | 1130 | Session().commit() |
|
1131 | 1131 | |
|
1132 | 1132 | @mock.patch.object(UsersGroupModel, 'remove_user_from_group', crash) |
|
1133 | 1133 | def test_api_remove_user_from_users_group_exception_occurred(self): |
|
1134 | 1134 | gr_name = 'test_group_3' |
|
1135 | 1135 | gr = UsersGroupModel().create(gr_name) |
|
1136 | 1136 | UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN) |
|
1137 | 1137 | Session().commit() |
|
1138 | 1138 | id_, params = _build_data(self.apikey, 'remove_user_from_users_group', |
|
1139 | 1139 | usersgroupid=gr_name, |
|
1140 | 1140 | userid=TEST_USER_ADMIN_LOGIN) |
|
1141 | 1141 | response = api_call(self, params) |
|
1142 | 1142 | |
|
1143 |
expected = 'failed to remove member from user |
|
|
1143 | expected = 'failed to remove member from user group `%s`' % gr_name | |
|
1144 | 1144 | self._compare_error(id_, expected, given=response.body) |
|
1145 | 1145 | |
|
1146 | 1146 | UsersGroupModel().delete(users_group=gr_name) |
|
1147 | 1147 | Session().commit() |
|
1148 | 1148 | |
|
1149 | 1149 | @parameterized.expand([('none', 'repository.none'), |
|
1150 | 1150 | ('read', 'repository.read'), |
|
1151 | 1151 | ('write', 'repository.write'), |
|
1152 | 1152 | ('admin', 'repository.admin')]) |
|
1153 | 1153 | def test_api_grant_user_permission(self, name, perm): |
|
1154 | 1154 | id_, params = _build_data(self.apikey, 'grant_user_permission', |
|
1155 | 1155 | repoid=self.REPO, |
|
1156 | 1156 | userid=TEST_USER_ADMIN_LOGIN, |
|
1157 | 1157 | perm=perm) |
|
1158 | 1158 | response = api_call(self, params) |
|
1159 | 1159 | |
|
1160 | 1160 | ret = { |
|
1161 | 1161 | 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % ( |
|
1162 | 1162 | perm, TEST_USER_ADMIN_LOGIN, self.REPO |
|
1163 | 1163 | ), |
|
1164 | 1164 | 'success': True |
|
1165 | 1165 | } |
|
1166 | 1166 | expected = ret |
|
1167 | 1167 | self._compare_ok(id_, expected, given=response.body) |
|
1168 | 1168 | |
|
1169 | 1169 | def test_api_grant_user_permission_wrong_permission(self): |
|
1170 | 1170 | perm = 'haha.no.permission' |
|
1171 | 1171 | id_, params = _build_data(self.apikey, 'grant_user_permission', |
|
1172 | 1172 | repoid=self.REPO, |
|
1173 | 1173 | userid=TEST_USER_ADMIN_LOGIN, |
|
1174 | 1174 | perm=perm) |
|
1175 | 1175 | response = api_call(self, params) |
|
1176 | 1176 | |
|
1177 | 1177 | expected = 'permission `%s` does not exist' % perm |
|
1178 | 1178 | self._compare_error(id_, expected, given=response.body) |
|
1179 | 1179 | |
|
1180 | 1180 | @mock.patch.object(RepoModel, 'grant_user_permission', crash) |
|
1181 | 1181 | def test_api_grant_user_permission_exception_when_adding(self): |
|
1182 | 1182 | perm = 'repository.read' |
|
1183 | 1183 | id_, params = _build_data(self.apikey, 'grant_user_permission', |
|
1184 | 1184 | repoid=self.REPO, |
|
1185 | 1185 | userid=TEST_USER_ADMIN_LOGIN, |
|
1186 | 1186 | perm=perm) |
|
1187 | 1187 | response = api_call(self, params) |
|
1188 | 1188 | |
|
1189 | 1189 | expected = 'failed to edit permission for user: `%s` in repo: `%s`' % ( |
|
1190 | 1190 | TEST_USER_ADMIN_LOGIN, self.REPO |
|
1191 | 1191 | ) |
|
1192 | 1192 | self._compare_error(id_, expected, given=response.body) |
|
1193 | 1193 | |
|
1194 | 1194 | def test_api_revoke_user_permission(self): |
|
1195 | 1195 | id_, params = _build_data(self.apikey, 'revoke_user_permission', |
|
1196 | 1196 | repoid=self.REPO, |
|
1197 | 1197 | userid=TEST_USER_ADMIN_LOGIN,) |
|
1198 | 1198 | response = api_call(self, params) |
|
1199 | 1199 | |
|
1200 | 1200 | expected = { |
|
1201 | 1201 | 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % ( |
|
1202 | 1202 | TEST_USER_ADMIN_LOGIN, self.REPO |
|
1203 | 1203 | ), |
|
1204 | 1204 | 'success': True |
|
1205 | 1205 | } |
|
1206 | 1206 | self._compare_ok(id_, expected, given=response.body) |
|
1207 | 1207 | |
|
1208 | 1208 | @mock.patch.object(RepoModel, 'revoke_user_permission', crash) |
|
1209 | 1209 | def test_api_revoke_user_permission_exception_when_adding(self): |
|
1210 | 1210 | id_, params = _build_data(self.apikey, 'revoke_user_permission', |
|
1211 | 1211 | repoid=self.REPO, |
|
1212 | 1212 | userid=TEST_USER_ADMIN_LOGIN,) |
|
1213 | 1213 | response = api_call(self, params) |
|
1214 | 1214 | |
|
1215 | 1215 | expected = 'failed to edit permission for user: `%s` in repo: `%s`' % ( |
|
1216 | 1216 | TEST_USER_ADMIN_LOGIN, self.REPO |
|
1217 | 1217 | ) |
|
1218 | 1218 | self._compare_error(id_, expected, given=response.body) |
|
1219 | 1219 | |
|
1220 | 1220 | @parameterized.expand([('none', 'repository.none'), |
|
1221 | 1221 | ('read', 'repository.read'), |
|
1222 | 1222 | ('write', 'repository.write'), |
|
1223 | 1223 | ('admin', 'repository.admin')]) |
|
1224 | 1224 | def test_api_grant_users_group_permission(self, name, perm): |
|
1225 | 1225 | id_, params = _build_data(self.apikey, 'grant_users_group_permission', |
|
1226 | 1226 | repoid=self.REPO, |
|
1227 | 1227 | usersgroupid=TEST_USERS_GROUP, |
|
1228 | 1228 | perm=perm) |
|
1229 | 1229 | response = api_call(self, params) |
|
1230 | 1230 | |
|
1231 | 1231 | ret = { |
|
1232 |
'msg': 'Granted perm: `%s` for user |
|
|
1232 | 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % ( | |
|
1233 | 1233 | perm, TEST_USERS_GROUP, self.REPO |
|
1234 | 1234 | ), |
|
1235 | 1235 | 'success': True |
|
1236 | 1236 | } |
|
1237 | 1237 | expected = ret |
|
1238 | 1238 | self._compare_ok(id_, expected, given=response.body) |
|
1239 | 1239 | |
|
1240 | 1240 | def test_api_grant_users_group_permission_wrong_permission(self): |
|
1241 | 1241 | perm = 'haha.no.permission' |
|
1242 | 1242 | id_, params = _build_data(self.apikey, 'grant_users_group_permission', |
|
1243 | 1243 | repoid=self.REPO, |
|
1244 | 1244 | usersgroupid=TEST_USERS_GROUP, |
|
1245 | 1245 | perm=perm) |
|
1246 | 1246 | response = api_call(self, params) |
|
1247 | 1247 | |
|
1248 | 1248 | expected = 'permission `%s` does not exist' % perm |
|
1249 | 1249 | self._compare_error(id_, expected, given=response.body) |
|
1250 | 1250 | |
|
1251 | 1251 | @mock.patch.object(RepoModel, 'grant_users_group_permission', crash) |
|
1252 | 1252 | def test_api_grant_users_group_permission_exception_when_adding(self): |
|
1253 | 1253 | perm = 'repository.read' |
|
1254 | 1254 | id_, params = _build_data(self.apikey, 'grant_users_group_permission', |
|
1255 | 1255 | repoid=self.REPO, |
|
1256 | 1256 | usersgroupid=TEST_USERS_GROUP, |
|
1257 | 1257 | perm=perm) |
|
1258 | 1258 | response = api_call(self, params) |
|
1259 | 1259 | |
|
1260 |
expected = 'failed to edit permission for user |
|
|
1260 | expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % ( | |
|
1261 | 1261 | TEST_USERS_GROUP, self.REPO |
|
1262 | 1262 | ) |
|
1263 | 1263 | self._compare_error(id_, expected, given=response.body) |
|
1264 | 1264 | |
|
1265 | 1265 | def test_api_revoke_users_group_permission(self): |
|
1266 | 1266 | RepoModel().grant_users_group_permission(repo=self.REPO, |
|
1267 | 1267 | group_name=TEST_USERS_GROUP, |
|
1268 | 1268 | perm='repository.read') |
|
1269 | 1269 | Session().commit() |
|
1270 | 1270 | id_, params = _build_data(self.apikey, 'revoke_users_group_permission', |
|
1271 | 1271 | repoid=self.REPO, |
|
1272 | 1272 | usersgroupid=TEST_USERS_GROUP,) |
|
1273 | 1273 | response = api_call(self, params) |
|
1274 | 1274 | |
|
1275 | 1275 | expected = { |
|
1276 |
'msg': 'Revoked perm for user |
|
|
1276 | 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % ( | |
|
1277 | 1277 | TEST_USERS_GROUP, self.REPO |
|
1278 | 1278 | ), |
|
1279 | 1279 | 'success': True |
|
1280 | 1280 | } |
|
1281 | 1281 | self._compare_ok(id_, expected, given=response.body) |
|
1282 | 1282 | |
|
1283 | 1283 | @mock.patch.object(RepoModel, 'revoke_users_group_permission', crash) |
|
1284 | 1284 | def test_api_revoke_users_group_permission_exception_when_adding(self): |
|
1285 | 1285 | |
|
1286 | 1286 | id_, params = _build_data(self.apikey, 'revoke_users_group_permission', |
|
1287 | 1287 | repoid=self.REPO, |
|
1288 | 1288 | usersgroupid=TEST_USERS_GROUP,) |
|
1289 | 1289 | response = api_call(self, params) |
|
1290 | 1290 | |
|
1291 |
expected = 'failed to edit permission for user |
|
|
1291 | expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % ( | |
|
1292 | 1292 | TEST_USERS_GROUP, self.REPO |
|
1293 | 1293 | ) |
|
1294 | 1294 | self._compare_error(id_, expected, given=response.body) |
@@ -1,224 +1,224 b'' | |||
|
1 | 1 | from rhodecode.tests import * |
|
2 | 2 | from rhodecode.model.db import UsersGroup, UsersGroupToPerm, Permission |
|
3 | 3 | |
|
4 | 4 | TEST_USERS_GROUP = 'admins_test' |
|
5 | 5 | |
|
6 | 6 | |
|
7 | 7 | class TestAdminUsersGroupsController(TestController): |
|
8 | 8 | |
|
9 | 9 | def test_index(self): |
|
10 | 10 | response = self.app.get(url('users_groups')) |
|
11 | 11 | # Test response... |
|
12 | 12 | |
|
13 | 13 | def test_index_as_xml(self): |
|
14 | 14 | response = self.app.get(url('formatted_users_groups', format='xml')) |
|
15 | 15 | |
|
16 | 16 | def test_create(self): |
|
17 | 17 | self.log_user() |
|
18 | 18 | users_group_name = TEST_USERS_GROUP |
|
19 | 19 | response = self.app.post(url('users_groups'), |
|
20 | 20 | {'users_group_name': users_group_name, |
|
21 | 21 | 'active':True}) |
|
22 | 22 | response.follow() |
|
23 | 23 | |
|
24 | 24 | self.checkSessionFlash(response, |
|
25 |
'created user |
|
|
25 | 'created user group %s' % TEST_USERS_GROUP) | |
|
26 | 26 | |
|
27 | 27 | def test_new(self): |
|
28 | 28 | response = self.app.get(url('new_users_group')) |
|
29 | 29 | |
|
30 | 30 | def test_new_as_xml(self): |
|
31 | 31 | response = self.app.get(url('formatted_new_users_group', format='xml')) |
|
32 | 32 | |
|
33 | 33 | def test_update(self): |
|
34 | 34 | response = self.app.put(url('users_group', id=1)) |
|
35 | 35 | |
|
36 | 36 | def test_update_browser_fakeout(self): |
|
37 | 37 | response = self.app.post(url('users_group', id=1), |
|
38 | 38 | params=dict(_method='put')) |
|
39 | 39 | |
|
40 | 40 | def test_delete(self): |
|
41 | 41 | self.log_user() |
|
42 | 42 | users_group_name = TEST_USERS_GROUP + 'another' |
|
43 | 43 | response = self.app.post(url('users_groups'), |
|
44 | 44 | {'users_group_name':users_group_name, |
|
45 | 45 | 'active':True}) |
|
46 | 46 | response.follow() |
|
47 | 47 | |
|
48 | 48 | self.checkSessionFlash(response, |
|
49 |
'created user |
|
|
49 | 'created user group %s' % users_group_name) | |
|
50 | 50 | |
|
51 | 51 | gr = self.Session.query(UsersGroup)\ |
|
52 | 52 | .filter(UsersGroup.users_group_name == |
|
53 | 53 | users_group_name).one() |
|
54 | 54 | |
|
55 | 55 | response = self.app.delete(url('users_group', id=gr.users_group_id)) |
|
56 | 56 | |
|
57 | 57 | gr = self.Session.query(UsersGroup)\ |
|
58 | 58 | .filter(UsersGroup.users_group_name == |
|
59 | 59 | users_group_name).scalar() |
|
60 | 60 | |
|
61 | 61 | self.assertEqual(gr, None) |
|
62 | 62 | |
|
63 | 63 | def test_enable_repository_read_on_group(self): |
|
64 | 64 | self.log_user() |
|
65 | 65 | users_group_name = TEST_USERS_GROUP + 'another2' |
|
66 | 66 | response = self.app.post(url('users_groups'), |
|
67 | 67 | {'users_group_name': users_group_name, |
|
68 | 68 | 'active': True}) |
|
69 | 69 | response.follow() |
|
70 | 70 | |
|
71 | 71 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
72 | 72 | self.checkSessionFlash(response, |
|
73 |
'created user |
|
|
73 | 'created user group %s' % users_group_name) | |
|
74 | 74 | ## ENABLE REPO CREATE ON A GROUP |
|
75 | 75 | response = self.app.put(url('users_group_perm', id=ug.users_group_id), |
|
76 | 76 | {'create_repo_perm': True}) |
|
77 | 77 | |
|
78 | 78 | response.follow() |
|
79 | 79 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
80 | 80 | p = Permission.get_by_key('hg.create.repository') |
|
81 | 81 | p2 = Permission.get_by_key('hg.fork.none') |
|
82 | 82 | # check if user has this perms, they should be here since |
|
83 | 83 | # defaults are on |
|
84 | 84 | perms = UsersGroupToPerm.query()\ |
|
85 | 85 | .filter(UsersGroupToPerm.users_group == ug).all() |
|
86 | 86 | |
|
87 | 87 | self.assertEqual( |
|
88 | 88 | [[x.users_group_id, x.permission_id, ] for x in perms], |
|
89 | 89 | [[ug.users_group_id, p.permission_id], |
|
90 | 90 | [ug.users_group_id, p2.permission_id]] |
|
91 | 91 | ) |
|
92 | 92 | |
|
93 | 93 | ## DISABLE REPO CREATE ON A GROUP |
|
94 | 94 | response = self.app.put(url('users_group_perm', id=ug.users_group_id), |
|
95 | 95 | {}) |
|
96 | 96 | |
|
97 | 97 | response.follow() |
|
98 | 98 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
99 | 99 | p = Permission.get_by_key('hg.create.none') |
|
100 | 100 | p2 = Permission.get_by_key('hg.fork.none') |
|
101 | 101 | # check if user has this perms, they should be here since |
|
102 | 102 | # defaults are on |
|
103 | 103 | perms = UsersGroupToPerm.query()\ |
|
104 | 104 | .filter(UsersGroupToPerm.users_group == ug).all() |
|
105 | 105 | |
|
106 | 106 | self.assertEqual( |
|
107 | 107 | sorted([[x.users_group_id, x.permission_id, ] for x in perms]), |
|
108 | 108 | sorted([[ug.users_group_id, p.permission_id], |
|
109 | 109 | [ug.users_group_id, p2.permission_id]]) |
|
110 | 110 | ) |
|
111 | 111 | |
|
112 | 112 | # DELETE ! |
|
113 | 113 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
114 | 114 | ugid = ug.users_group_id |
|
115 | 115 | response = self.app.delete(url('users_group', id=ug.users_group_id)) |
|
116 | 116 | response = response.follow() |
|
117 | 117 | gr = self.Session.query(UsersGroup)\ |
|
118 | 118 | .filter(UsersGroup.users_group_name == |
|
119 | 119 | users_group_name).scalar() |
|
120 | 120 | |
|
121 | 121 | self.assertEqual(gr, None) |
|
122 | 122 | p = Permission.get_by_key('hg.create.repository') |
|
123 | 123 | perms = UsersGroupToPerm.query()\ |
|
124 | 124 | .filter(UsersGroupToPerm.users_group_id == ugid).all() |
|
125 | 125 | perms = [[x.users_group_id, |
|
126 | 126 | x.permission_id, ] for x in perms] |
|
127 | 127 | self.assertEqual( |
|
128 | 128 | perms, |
|
129 | 129 | [] |
|
130 | 130 | ) |
|
131 | 131 | |
|
132 | 132 | def test_enable_repository_fork_on_group(self): |
|
133 | 133 | self.log_user() |
|
134 | 134 | users_group_name = TEST_USERS_GROUP + 'another2' |
|
135 | 135 | response = self.app.post(url('users_groups'), |
|
136 | 136 | {'users_group_name': users_group_name, |
|
137 | 137 | 'active': True}) |
|
138 | 138 | response.follow() |
|
139 | 139 | |
|
140 | 140 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
141 | 141 | self.checkSessionFlash(response, |
|
142 |
'created user |
|
|
142 | 'created user group %s' % users_group_name) | |
|
143 | 143 | ## ENABLE REPO CREATE ON A GROUP |
|
144 | 144 | response = self.app.put(url('users_group_perm', id=ug.users_group_id), |
|
145 | 145 | {'fork_repo_perm': True}) |
|
146 | 146 | |
|
147 | 147 | response.follow() |
|
148 | 148 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
149 | 149 | p = Permission.get_by_key('hg.create.none') |
|
150 | 150 | p2 = Permission.get_by_key('hg.fork.repository') |
|
151 | 151 | # check if user has this perms, they should be here since |
|
152 | 152 | # defaults are on |
|
153 | 153 | perms = UsersGroupToPerm.query()\ |
|
154 | 154 | .filter(UsersGroupToPerm.users_group == ug).all() |
|
155 | 155 | |
|
156 | 156 | self.assertEqual( |
|
157 | 157 | [[x.users_group_id, x.permission_id, ] for x in perms], |
|
158 | 158 | [[ug.users_group_id, p.permission_id], |
|
159 | 159 | [ug.users_group_id, p2.permission_id]] |
|
160 | 160 | ) |
|
161 | 161 | |
|
162 | 162 | ## DISABLE REPO CREATE ON A GROUP |
|
163 | 163 | response = self.app.put(url('users_group_perm', id=ug.users_group_id), |
|
164 | 164 | {}) |
|
165 | 165 | |
|
166 | 166 | response.follow() |
|
167 | 167 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
168 | 168 | p = Permission.get_by_key('hg.create.none') |
|
169 | 169 | p2 = Permission.get_by_key('hg.fork.none') |
|
170 | 170 | # check if user has this perms, they should be here since |
|
171 | 171 | # defaults are on |
|
172 | 172 | perms = UsersGroupToPerm.query()\ |
|
173 | 173 | .filter(UsersGroupToPerm.users_group == ug).all() |
|
174 | 174 | |
|
175 | 175 | self.assertEqual( |
|
176 | 176 | [[x.users_group_id, x.permission_id, ] for x in perms], |
|
177 | 177 | [[ug.users_group_id, p.permission_id], |
|
178 | 178 | [ug.users_group_id, p2.permission_id]] |
|
179 | 179 | ) |
|
180 | 180 | |
|
181 | 181 | # DELETE ! |
|
182 | 182 | ug = UsersGroup.get_by_group_name(users_group_name) |
|
183 | 183 | ugid = ug.users_group_id |
|
184 | 184 | response = self.app.delete(url('users_group', id=ug.users_group_id)) |
|
185 | 185 | response = response.follow() |
|
186 | 186 | gr = self.Session.query(UsersGroup)\ |
|
187 | 187 | .filter(UsersGroup.users_group_name == |
|
188 | 188 | users_group_name).scalar() |
|
189 | 189 | |
|
190 | 190 | self.assertEqual(gr, None) |
|
191 | 191 | p = Permission.get_by_key('hg.fork.repository') |
|
192 | 192 | perms = UsersGroupToPerm.query()\ |
|
193 | 193 | .filter(UsersGroupToPerm.users_group_id == ugid).all() |
|
194 | 194 | perms = [[x.users_group_id, |
|
195 | 195 | x.permission_id, ] for x in perms] |
|
196 | 196 | self.assertEqual( |
|
197 | 197 | perms, |
|
198 | 198 | [] |
|
199 | 199 | ) |
|
200 | 200 | |
|
201 | 201 | def test_delete_browser_fakeout(self): |
|
202 | 202 | response = self.app.post(url('users_group', id=1), |
|
203 | 203 | params=dict(_method='delete')) |
|
204 | 204 | |
|
205 | 205 | def test_show(self): |
|
206 | 206 | response = self.app.get(url('users_group', id=1)) |
|
207 | 207 | |
|
208 | 208 | def test_show_as_xml(self): |
|
209 | 209 | response = self.app.get(url('formatted_users_group', id=1, format='xml')) |
|
210 | 210 | |
|
211 | 211 | def test_edit(self): |
|
212 | 212 | response = self.app.get(url('edit_users_group', id=1)) |
|
213 | 213 | |
|
214 | 214 | def test_edit_as_xml(self): |
|
215 | 215 | response = self.app.get(url('formatted_edit_users_group', id=1, format='xml')) |
|
216 | 216 | |
|
217 | 217 | def test_assign_members(self): |
|
218 | 218 | pass |
|
219 | 219 | |
|
220 | 220 | def test_add_create_permission(self): |
|
221 | 221 | pass |
|
222 | 222 | |
|
223 | 223 | def test_revoke_members(self): |
|
224 | 224 | pass |
@@ -1,472 +1,472 b'' | |||
|
1 | 1 | import os |
|
2 | 2 | import unittest |
|
3 | 3 | from rhodecode.tests import * |
|
4 | 4 | from rhodecode.tests.models.common import _make_group |
|
5 | 5 | from rhodecode.model.repos_group import ReposGroupModel |
|
6 | 6 | from rhodecode.model.repo import RepoModel |
|
7 | 7 | from rhodecode.model.db import RepoGroup, User, UsersGroupRepoGroupToPerm |
|
8 | 8 | from rhodecode.model.user import UserModel |
|
9 | 9 | |
|
10 | 10 | from rhodecode.model.meta import Session |
|
11 | 11 | from rhodecode.model.users_group import UsersGroupModel |
|
12 | 12 | from rhodecode.lib.auth import AuthUser |
|
13 | 13 | from rhodecode.tests.api.api_base import create_repo |
|
14 | 14 | |
|
15 | 15 | |
|
16 | 16 | class TestPermissions(unittest.TestCase): |
|
17 | 17 | def __init__(self, methodName='runTest'): |
|
18 | 18 | super(TestPermissions, self).__init__(methodName=methodName) |
|
19 | 19 | |
|
20 | 20 | def setUp(self): |
|
21 | 21 | self.u1 = UserModel().create_or_update( |
|
22 | 22 | username=u'u1', password=u'qweqwe', |
|
23 | 23 | email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1' |
|
24 | 24 | ) |
|
25 | 25 | self.u2 = UserModel().create_or_update( |
|
26 | 26 | username=u'u2', password=u'qweqwe', |
|
27 | 27 | email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2' |
|
28 | 28 | ) |
|
29 | 29 | self.u3 = UserModel().create_or_update( |
|
30 | 30 | username=u'u3', password=u'qweqwe', |
|
31 | 31 | email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3' |
|
32 | 32 | ) |
|
33 | 33 | self.anon = User.get_by_username('default') |
|
34 | 34 | self.a1 = UserModel().create_or_update( |
|
35 | 35 | username=u'a1', password=u'qweqwe', |
|
36 | 36 | email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1', admin=True |
|
37 | 37 | ) |
|
38 | 38 | Session().commit() |
|
39 | 39 | |
|
40 | 40 | def tearDown(self): |
|
41 | 41 | if hasattr(self, 'test_repo'): |
|
42 | 42 | RepoModel().delete(repo=self.test_repo) |
|
43 | 43 | |
|
44 | 44 | UserModel().delete(self.u1) |
|
45 | 45 | UserModel().delete(self.u2) |
|
46 | 46 | UserModel().delete(self.u3) |
|
47 | 47 | UserModel().delete(self.a1) |
|
48 | 48 | if hasattr(self, 'g1'): |
|
49 | 49 | ReposGroupModel().delete(self.g1.group_id) |
|
50 | 50 | if hasattr(self, 'g2'): |
|
51 | 51 | ReposGroupModel().delete(self.g2.group_id) |
|
52 | 52 | |
|
53 | 53 | if hasattr(self, 'ug1'): |
|
54 | 54 | UsersGroupModel().delete(self.ug1, force=True) |
|
55 | 55 | |
|
56 | 56 | Session().commit() |
|
57 | 57 | |
|
58 | 58 | def test_default_perms_set(self): |
|
59 | 59 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
60 | 60 | perms = { |
|
61 | 61 | 'repositories_groups': {}, |
|
62 | 62 | 'global': set([u'hg.create.repository', u'repository.read', |
|
63 | 63 | u'hg.register.manual_activate']), |
|
64 | 64 | 'repositories': {u'vcs_test_hg': u'repository.read'} |
|
65 | 65 | } |
|
66 | 66 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
67 | 67 | perms['repositories'][HG_REPO]) |
|
68 | 68 | new_perm = 'repository.write' |
|
69 | 69 | RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, |
|
70 | 70 | perm=new_perm) |
|
71 | 71 | Session().commit() |
|
72 | 72 | |
|
73 | 73 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
74 | 74 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
75 | 75 | new_perm) |
|
76 | 76 | |
|
77 | 77 | def test_default_admin_perms_set(self): |
|
78 | 78 | a1_auth = AuthUser(user_id=self.a1.user_id) |
|
79 | 79 | perms = { |
|
80 | 80 | 'repositories_groups': {}, |
|
81 | 81 | 'global': set([u'hg.admin']), |
|
82 | 82 | 'repositories': {u'vcs_test_hg': u'repository.admin'} |
|
83 | 83 | } |
|
84 | 84 | self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], |
|
85 | 85 | perms['repositories'][HG_REPO]) |
|
86 | 86 | new_perm = 'repository.write' |
|
87 | 87 | RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, |
|
88 | 88 | perm=new_perm) |
|
89 | 89 | Session().commit() |
|
90 | 90 | # cannot really downgrade admins permissions !? they still get's set as |
|
91 | 91 | # admin ! |
|
92 | 92 | u1_auth = AuthUser(user_id=self.a1.user_id) |
|
93 | 93 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
94 | 94 | perms['repositories'][HG_REPO]) |
|
95 | 95 | |
|
96 | 96 | def test_default_group_perms(self): |
|
97 | 97 | self.g1 = _make_group('test1', skip_if_exists=True) |
|
98 | 98 | self.g2 = _make_group('test2', skip_if_exists=True) |
|
99 | 99 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
100 | 100 | perms = { |
|
101 | 101 | 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'}, |
|
102 | 102 | 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']), |
|
103 | 103 | 'repositories': {u'vcs_test_hg': u'repository.read'} |
|
104 | 104 | } |
|
105 | 105 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
106 | 106 | perms['repositories'][HG_REPO]) |
|
107 | 107 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
108 | 108 | perms['repositories_groups']) |
|
109 | 109 | |
|
110 | 110 | def test_default_admin_group_perms(self): |
|
111 | 111 | self.g1 = _make_group('test1', skip_if_exists=True) |
|
112 | 112 | self.g2 = _make_group('test2', skip_if_exists=True) |
|
113 | 113 | a1_auth = AuthUser(user_id=self.a1.user_id) |
|
114 | 114 | perms = { |
|
115 | 115 | 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'}, |
|
116 | 116 | 'global': set(['hg.admin']), |
|
117 | 117 | 'repositories': {u'vcs_test_hg': 'repository.admin'} |
|
118 | 118 | } |
|
119 | 119 | |
|
120 | 120 | self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], |
|
121 | 121 | perms['repositories'][HG_REPO]) |
|
122 | 122 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
123 | 123 | perms['repositories_groups']) |
|
124 | 124 | |
|
125 | 125 | def test_propagated_permission_from_users_group_by_explicit_perms_exist(self): |
|
126 | 126 | # make group |
|
127 | 127 | self.ug1 = UsersGroupModel().create('G1') |
|
128 | 128 | # add user to group |
|
129 | 129 | |
|
130 | 130 | UsersGroupModel().add_user_to_group(self.ug1, self.u1) |
|
131 | 131 | |
|
132 | 132 | # set permission to lower |
|
133 | 133 | new_perm = 'repository.none' |
|
134 | 134 | RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm) |
|
135 | 135 | Session().commit() |
|
136 | 136 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
137 | 137 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
138 | 138 | new_perm) |
|
139 | 139 | |
|
140 | 140 | # grant perm for group this should not override permission from user |
|
141 | 141 | # since it has explicitly set |
|
142 | 142 | new_perm_gr = 'repository.write' |
|
143 | 143 | RepoModel().grant_users_group_permission(repo=HG_REPO, |
|
144 | 144 | group_name=self.ug1, |
|
145 | 145 | perm=new_perm_gr) |
|
146 | 146 | # check perms |
|
147 | 147 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
148 | 148 | perms = { |
|
149 | 149 | 'repositories_groups': {}, |
|
150 | 150 | 'global': set([u'hg.create.repository', u'repository.read', |
|
151 | 151 | u'hg.register.manual_activate']), |
|
152 | 152 | 'repositories': {u'vcs_test_hg': u'repository.read'} |
|
153 | 153 | } |
|
154 | 154 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
155 | 155 | new_perm) |
|
156 | 156 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
157 | 157 | perms['repositories_groups']) |
|
158 | 158 | |
|
159 | 159 | def test_propagated_permission_from_users_group(self): |
|
160 | 160 | # make group |
|
161 | 161 | self.ug1 = UsersGroupModel().create('G1') |
|
162 | 162 | # add user to group |
|
163 | 163 | |
|
164 | 164 | UsersGroupModel().add_user_to_group(self.ug1, self.u3) |
|
165 | 165 | |
|
166 | 166 | # grant perm for group this should override default permission from user |
|
167 | 167 | new_perm_gr = 'repository.write' |
|
168 | 168 | RepoModel().grant_users_group_permission(repo=HG_REPO, |
|
169 | 169 | group_name=self.ug1, |
|
170 | 170 | perm=new_perm_gr) |
|
171 | 171 | # check perms |
|
172 | 172 | u3_auth = AuthUser(user_id=self.u3.user_id) |
|
173 | 173 | perms = { |
|
174 | 174 | 'repositories_groups': {}, |
|
175 | 175 | 'global': set([u'hg.create.repository', u'repository.read', |
|
176 | 176 | u'hg.register.manual_activate']), |
|
177 | 177 | 'repositories': {u'vcs_test_hg': u'repository.read'} |
|
178 | 178 | } |
|
179 | 179 | self.assertEqual(u3_auth.permissions['repositories'][HG_REPO], |
|
180 | 180 | new_perm_gr) |
|
181 | 181 | self.assertEqual(u3_auth.permissions['repositories_groups'], |
|
182 | 182 | perms['repositories_groups']) |
|
183 | 183 | |
|
184 | 184 | def test_propagated_permission_from_users_group_lower_weight(self): |
|
185 | 185 | # make group |
|
186 | 186 | self.ug1 = UsersGroupModel().create('G1') |
|
187 | 187 | # add user to group |
|
188 | 188 | UsersGroupModel().add_user_to_group(self.ug1, self.u1) |
|
189 | 189 | |
|
190 | 190 | # set permission to lower |
|
191 | 191 | new_perm_h = 'repository.write' |
|
192 | 192 | RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, |
|
193 | 193 | perm=new_perm_h) |
|
194 | 194 | Session().commit() |
|
195 | 195 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
196 | 196 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
197 | 197 | new_perm_h) |
|
198 | 198 | |
|
199 | 199 | # grant perm for group this should NOT override permission from user |
|
200 | 200 | # since it's lower than granted |
|
201 | 201 | new_perm_l = 'repository.read' |
|
202 | 202 | RepoModel().grant_users_group_permission(repo=HG_REPO, |
|
203 | 203 | group_name=self.ug1, |
|
204 | 204 | perm=new_perm_l) |
|
205 | 205 | # check perms |
|
206 | 206 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
207 | 207 | perms = { |
|
208 | 208 | 'repositories_groups': {}, |
|
209 | 209 | 'global': set([u'hg.create.repository', u'repository.read', |
|
210 | 210 | u'hg.register.manual_activate']), |
|
211 | 211 | 'repositories': {u'vcs_test_hg': u'repository.write'} |
|
212 | 212 | } |
|
213 | 213 | self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], |
|
214 | 214 | new_perm_h) |
|
215 | 215 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
216 | 216 | perms['repositories_groups']) |
|
217 | 217 | |
|
218 | 218 | def test_repo_in_group_permissions(self): |
|
219 | 219 | self.g1 = _make_group('group1', skip_if_exists=True) |
|
220 | 220 | self.g2 = _make_group('group2', skip_if_exists=True) |
|
221 | 221 | Session().commit() |
|
222 | 222 | # both perms should be read ! |
|
223 | 223 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
224 | 224 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
225 | 225 | {u'group1': u'group.read', u'group2': u'group.read'}) |
|
226 | 226 | |
|
227 | 227 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
228 | 228 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
229 | 229 | {u'group1': u'group.read', u'group2': u'group.read'}) |
|
230 | 230 | |
|
231 | 231 | #Change perms to none for both groups |
|
232 | 232 | ReposGroupModel().grant_user_permission(repos_group=self.g1, |
|
233 | 233 | user=self.anon, |
|
234 | 234 | perm='group.none') |
|
235 | 235 | ReposGroupModel().grant_user_permission(repos_group=self.g2, |
|
236 | 236 | user=self.anon, |
|
237 | 237 | perm='group.none') |
|
238 | 238 | |
|
239 | 239 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
240 | 240 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
241 | 241 | {u'group1': u'group.none', u'group2': u'group.none'}) |
|
242 | 242 | |
|
243 | 243 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
244 | 244 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
245 | 245 | {u'group1': u'group.none', u'group2': u'group.none'}) |
|
246 | 246 | |
|
247 | 247 | # add repo to group |
|
248 | 248 | name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm']) |
|
249 | 249 | self.test_repo = RepoModel().create_repo( |
|
250 | 250 | repo_name=name, |
|
251 | 251 | repo_type='hg', |
|
252 | 252 | description='', |
|
253 | 253 | repos_group=self.g1, |
|
254 | 254 | owner=self.u1, |
|
255 | 255 | ) |
|
256 | 256 | Session().commit() |
|
257 | 257 | |
|
258 | 258 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
259 | 259 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
260 | 260 | {u'group1': u'group.none', u'group2': u'group.none'}) |
|
261 | 261 | |
|
262 | 262 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
263 | 263 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
264 | 264 | {u'group1': u'group.none', u'group2': u'group.none'}) |
|
265 | 265 | |
|
266 | 266 | #grant permission for u2 ! |
|
267 | 267 | ReposGroupModel().grant_user_permission(repos_group=self.g1, |
|
268 | 268 | user=self.u2, |
|
269 | 269 | perm='group.read') |
|
270 | 270 | ReposGroupModel().grant_user_permission(repos_group=self.g2, |
|
271 | 271 | user=self.u2, |
|
272 | 272 | perm='group.read') |
|
273 | 273 | Session().commit() |
|
274 | 274 | self.assertNotEqual(self.u1, self.u2) |
|
275 | 275 | #u1 and anon should have not change perms while u2 should ! |
|
276 | 276 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
277 | 277 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
278 | 278 | {u'group1': u'group.none', u'group2': u'group.none'}) |
|
279 | 279 | |
|
280 | 280 | u2_auth = AuthUser(user_id=self.u2.user_id) |
|
281 | 281 | self.assertEqual(u2_auth.permissions['repositories_groups'], |
|
282 | 282 | {u'group1': u'group.read', u'group2': u'group.read'}) |
|
283 | 283 | |
|
284 | 284 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
285 | 285 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
286 | 286 | {u'group1': u'group.none', u'group2': u'group.none'}) |
|
287 | 287 | |
|
288 | 288 | def test_repo_group_user_as_user_group_member(self): |
|
289 | 289 | # create Group1 |
|
290 | 290 | self.g1 = _make_group('group1', skip_if_exists=True) |
|
291 | 291 | Session().commit() |
|
292 | 292 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
293 | 293 | |
|
294 | 294 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
295 | 295 | {u'group1': u'group.read'}) |
|
296 | 296 | |
|
297 | 297 | # set default permission to none |
|
298 | 298 | ReposGroupModel().grant_user_permission(repos_group=self.g1, |
|
299 | 299 | user=self.anon, |
|
300 | 300 | perm='group.none') |
|
301 | 301 | # make group |
|
302 | 302 | self.ug1 = UsersGroupModel().create('G1') |
|
303 | 303 | # add user to group |
|
304 | 304 | UsersGroupModel().add_user_to_group(self.ug1, self.u1) |
|
305 | 305 | Session().commit() |
|
306 | 306 | |
|
307 | 307 | # check if user is in the group |
|
308 | 308 | membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members] |
|
309 | 309 | self.assertEqual(membrs, [self.u1.user_id]) |
|
310 | 310 | # add some user to that group |
|
311 | 311 | |
|
312 | 312 | # check his permissions |
|
313 | 313 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
314 | 314 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
315 | 315 | {u'group1': u'group.none'}) |
|
316 | 316 | |
|
317 | 317 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
318 | 318 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
319 | 319 | {u'group1': u'group.none'}) |
|
320 | 320 | |
|
321 | 321 | # grant ug1 read permissions for |
|
322 | 322 | ReposGroupModel().grant_users_group_permission(repos_group=self.g1, |
|
323 | 323 | group_name=self.ug1, |
|
324 | 324 | perm='group.read') |
|
325 | 325 | Session().commit() |
|
326 | 326 | # check if the |
|
327 | 327 | obj = Session().query(UsersGroupRepoGroupToPerm)\ |
|
328 | 328 | .filter(UsersGroupRepoGroupToPerm.group == self.g1)\ |
|
329 | 329 | .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\ |
|
330 | 330 | .scalar() |
|
331 | 331 | self.assertEqual(obj.permission.permission_name, 'group.read') |
|
332 | 332 | |
|
333 | 333 | a1_auth = AuthUser(user_id=self.anon.user_id) |
|
334 | 334 | |
|
335 | 335 | self.assertEqual(a1_auth.permissions['repositories_groups'], |
|
336 | 336 | {u'group1': u'group.none'}) |
|
337 | 337 | |
|
338 | 338 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
339 | 339 | self.assertEqual(u1_auth.permissions['repositories_groups'], |
|
340 | 340 | {u'group1': u'group.read'}) |
|
341 | 341 | |
|
342 | 342 | def test_inherited_permissions_from_default_on_user_enabled(self): |
|
343 | 343 | user_model = UserModel() |
|
344 | 344 | # enable fork and create on default user |
|
345 | 345 | usr = 'default' |
|
346 | 346 | user_model.revoke_perm(usr, 'hg.create.none') |
|
347 | 347 | user_model.grant_perm(usr, 'hg.create.repository') |
|
348 | 348 | user_model.revoke_perm(usr, 'hg.fork.none') |
|
349 | 349 | user_model.grant_perm(usr, 'hg.fork.repository') |
|
350 | 350 | # make sure inherit flag is turned on |
|
351 | 351 | self.u1.inherit_default_permissions = True |
|
352 | 352 | Session().commit() |
|
353 | 353 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
354 | 354 | # this user will have inherited permissions from default user |
|
355 | 355 | self.assertEqual(u1_auth.permissions['global'], |
|
356 | 356 | set(['hg.create.repository', 'hg.fork.repository', |
|
357 | 357 | 'hg.register.manual_activate', |
|
358 | 358 | 'repository.read', 'group.read'])) |
|
359 | 359 | |
|
360 | 360 | def test_inherited_permissions_from_default_on_user_disabled(self): |
|
361 | 361 | user_model = UserModel() |
|
362 | 362 | # disable fork and create on default user |
|
363 | 363 | usr = 'default' |
|
364 | 364 | user_model.revoke_perm(usr, 'hg.create.repository') |
|
365 | 365 | user_model.grant_perm(usr, 'hg.create.none') |
|
366 | 366 | user_model.revoke_perm(usr, 'hg.fork.repository') |
|
367 | 367 | user_model.grant_perm(usr, 'hg.fork.none') |
|
368 | 368 | # make sure inherit flag is turned on |
|
369 | 369 | self.u1.inherit_default_permissions = True |
|
370 | 370 | Session().commit() |
|
371 | 371 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
372 | 372 | # this user will have inherited permissions from default user |
|
373 | 373 | self.assertEqual(u1_auth.permissions['global'], |
|
374 | 374 | set(['hg.create.none', 'hg.fork.none', |
|
375 | 375 | 'hg.register.manual_activate', |
|
376 | 376 | 'repository.read', 'group.read'])) |
|
377 | 377 | |
|
378 | 378 | def test_non_inherited_permissions_from_default_on_user_enabled(self): |
|
379 | 379 | user_model = UserModel() |
|
380 | 380 | # enable fork and create on default user |
|
381 | 381 | usr = 'default' |
|
382 | 382 | user_model.revoke_perm(usr, 'hg.create.none') |
|
383 | 383 | user_model.grant_perm(usr, 'hg.create.repository') |
|
384 | 384 | user_model.revoke_perm(usr, 'hg.fork.none') |
|
385 | 385 | user_model.grant_perm(usr, 'hg.fork.repository') |
|
386 | 386 | |
|
387 | 387 | #disable global perms on specific user |
|
388 | 388 | user_model.revoke_perm(self.u1, 'hg.create.repository') |
|
389 | 389 | user_model.grant_perm(self.u1, 'hg.create.none') |
|
390 | 390 | user_model.revoke_perm(self.u1, 'hg.fork.repository') |
|
391 | 391 | user_model.grant_perm(self.u1, 'hg.fork.none') |
|
392 | 392 | |
|
393 | 393 | # make sure inherit flag is turned off |
|
394 | 394 | self.u1.inherit_default_permissions = False |
|
395 | 395 | Session().commit() |
|
396 | 396 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
397 | 397 | # this user will have non inherited permissions from he's |
|
398 | 398 | # explicitly set permissions |
|
399 | 399 | self.assertEqual(u1_auth.permissions['global'], |
|
400 | 400 | set(['hg.create.none', 'hg.fork.none', |
|
401 | 401 | 'hg.register.manual_activate', |
|
402 | 402 | 'repository.read', 'group.read'])) |
|
403 | 403 | |
|
404 | 404 | def test_non_inherited_permissions_from_default_on_user_disabled(self): |
|
405 | 405 | user_model = UserModel() |
|
406 | 406 | # disable fork and create on default user |
|
407 | 407 | usr = 'default' |
|
408 | 408 | user_model.revoke_perm(usr, 'hg.create.repository') |
|
409 | 409 | user_model.grant_perm(usr, 'hg.create.none') |
|
410 | 410 | user_model.revoke_perm(usr, 'hg.fork.repository') |
|
411 | 411 | user_model.grant_perm(usr, 'hg.fork.none') |
|
412 | 412 | |
|
413 | 413 | #enable global perms on specific user |
|
414 | 414 | user_model.revoke_perm(self.u1, 'hg.create.none') |
|
415 | 415 | user_model.grant_perm(self.u1, 'hg.create.repository') |
|
416 | 416 | user_model.revoke_perm(self.u1, 'hg.fork.none') |
|
417 | 417 | user_model.grant_perm(self.u1, 'hg.fork.repository') |
|
418 | 418 | |
|
419 | 419 | # make sure inherit flag is turned off |
|
420 | 420 | self.u1.inherit_default_permissions = False |
|
421 | 421 | Session().commit() |
|
422 | 422 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
423 | 423 | # this user will have non inherited permissions from he's |
|
424 | 424 | # explicitly set permissions |
|
425 | 425 | self.assertEqual(u1_auth.permissions['global'], |
|
426 | 426 | set(['hg.create.repository', 'hg.fork.repository', |
|
427 | 427 | 'hg.register.manual_activate', |
|
428 | 428 | 'repository.read', 'group.read'])) |
|
429 | 429 | |
|
430 | 430 | def test_owner_permissions_doesnot_get_overwritten_by_group(self): |
|
431 | 431 | #create repo as USER, |
|
432 | 432 | self.test_repo = repo = RepoModel().create_repo(repo_name='myownrepo', |
|
433 | 433 | repo_type='hg', |
|
434 | 434 | description='desc', |
|
435 | 435 | owner=self.u1) |
|
436 | 436 | |
|
437 | 437 | Session().commit() |
|
438 | 438 | #he has permissions of admin as owner |
|
439 | 439 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
440 | 440 | self.assertEqual(u1_auth.permissions['repositories']['myownrepo'], |
|
441 | 441 | 'repository.admin') |
|
442 |
#set his permission as user |
|
|
442 | #set his permission as user group, he should still be admin | |
|
443 | 443 | self.ug1 = UsersGroupModel().create('G1') |
|
444 | 444 | # add user to group |
|
445 | 445 | UsersGroupModel().add_user_to_group(self.ug1, self.u1) |
|
446 | 446 | RepoModel().grant_users_group_permission(repo, group_name=self.ug1, |
|
447 | 447 | perm='repository.none') |
|
448 | 448 | |
|
449 | 449 | Session().commit() |
|
450 | 450 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
451 | 451 | self.assertEqual(u1_auth.permissions['repositories']['myownrepo'], |
|
452 | 452 | 'repository.admin') |
|
453 | 453 | |
|
454 | 454 | def test_owner_permissions_doesnot_get_overwritten_by_others(self): |
|
455 | 455 | #create repo as USER, |
|
456 | 456 | self.test_repo = repo = RepoModel().create_repo(repo_name='myownrepo', |
|
457 | 457 | repo_type='hg', |
|
458 | 458 | description='desc', |
|
459 | 459 | owner=self.u1) |
|
460 | 460 | |
|
461 | 461 | Session().commit() |
|
462 | 462 | #he has permissions of admin as owner |
|
463 | 463 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
464 | 464 | self.assertEqual(u1_auth.permissions['repositories']['myownrepo'], |
|
465 | 465 | 'repository.admin') |
|
466 | 466 | #set his permission as user, he should still be admin |
|
467 | 467 | RepoModel().grant_user_permission(repo, user=self.u1, |
|
468 | 468 | perm='repository.none') |
|
469 | 469 | Session().commit() |
|
470 | 470 | u1_auth = AuthUser(user_id=self.u1.user_id) |
|
471 | 471 | self.assertEqual(u1_auth.permissions['repositories']['myownrepo'], |
|
472 | 472 | 'repository.admin') |
@@ -1,124 +1,124 b'' | |||
|
1 | 1 | import unittest |
|
2 | 2 | from rhodecode.tests import * |
|
3 | 3 | |
|
4 | 4 | from rhodecode.model.db import User, UsersGroup, UsersGroupMember, UserEmailMap,\ |
|
5 | 5 | Permission |
|
6 | 6 | from rhodecode.model.user import UserModel |
|
7 | 7 | |
|
8 | 8 | from rhodecode.model.meta import Session |
|
9 | 9 | from rhodecode.model.users_group import UsersGroupModel |
|
10 | 10 | |
|
11 | 11 | |
|
12 | 12 | class TestUser(unittest.TestCase): |
|
13 | 13 | def __init__(self, methodName='runTest'): |
|
14 | 14 | Session.remove() |
|
15 | 15 | super(TestUser, self).__init__(methodName=methodName) |
|
16 | 16 | |
|
17 | 17 | def test_create_and_remove(self): |
|
18 | 18 | usr = UserModel().create_or_update(username=u'test_user', |
|
19 | 19 | password=u'qweqwe', |
|
20 | 20 | email=u'u232@rhodecode.org', |
|
21 | 21 | firstname=u'u1', lastname=u'u1') |
|
22 | 22 | Session().commit() |
|
23 | 23 | self.assertEqual(User.get_by_username(u'test_user'), usr) |
|
24 | 24 | |
|
25 |
# make user |
|
|
25 | # make user group | |
|
26 | 26 | users_group = UsersGroupModel().create('some_example_group') |
|
27 | 27 | Session().commit() |
|
28 | 28 | |
|
29 | 29 | UsersGroupModel().add_user_to_group(users_group, usr) |
|
30 | 30 | Session().commit() |
|
31 | 31 | |
|
32 | 32 | self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group) |
|
33 | 33 | self.assertEqual(UsersGroupMember.query().count(), 1) |
|
34 | 34 | UserModel().delete(usr.user_id) |
|
35 | 35 | Session().commit() |
|
36 | 36 | |
|
37 | 37 | self.assertEqual(UsersGroupMember.query().all(), []) |
|
38 | 38 | |
|
39 | 39 | def test_additonal_email_as_main(self): |
|
40 | 40 | usr = UserModel().create_or_update(username=u'test_user', |
|
41 | 41 | password=u'qweqwe', |
|
42 | 42 | email=u'main_email@rhodecode.org', |
|
43 | 43 | firstname=u'u1', lastname=u'u1') |
|
44 | 44 | Session().commit() |
|
45 | 45 | |
|
46 | 46 | def do(): |
|
47 | 47 | m = UserEmailMap() |
|
48 | 48 | m.email = u'main_email@rhodecode.org' |
|
49 | 49 | m.user = usr |
|
50 | 50 | Session().add(m) |
|
51 | 51 | Session().commit() |
|
52 | 52 | self.assertRaises(AttributeError, do) |
|
53 | 53 | |
|
54 | 54 | UserModel().delete(usr.user_id) |
|
55 | 55 | Session().commit() |
|
56 | 56 | |
|
57 | 57 | def test_extra_email_map(self): |
|
58 | 58 | usr = UserModel().create_or_update(username=u'test_user', |
|
59 | 59 | password=u'qweqwe', |
|
60 | 60 | email=u'main_email@rhodecode.org', |
|
61 | 61 | firstname=u'u1', lastname=u'u1') |
|
62 | 62 | Session().commit() |
|
63 | 63 | |
|
64 | 64 | m = UserEmailMap() |
|
65 | 65 | m.email = u'main_email2@rhodecode.org' |
|
66 | 66 | m.user = usr |
|
67 | 67 | Session().add(m) |
|
68 | 68 | Session().commit() |
|
69 | 69 | |
|
70 | 70 | u = User.get_by_email(email='main_email@rhodecode.org') |
|
71 | 71 | self.assertEqual(usr.user_id, u.user_id) |
|
72 | 72 | self.assertEqual(usr.username, u.username) |
|
73 | 73 | |
|
74 | 74 | u = User.get_by_email(email='main_email2@rhodecode.org') |
|
75 | 75 | self.assertEqual(usr.user_id, u.user_id) |
|
76 | 76 | self.assertEqual(usr.username, u.username) |
|
77 | 77 | u = User.get_by_email(email='main_email3@rhodecode.org') |
|
78 | 78 | self.assertEqual(None, u) |
|
79 | 79 | |
|
80 | 80 | UserModel().delete(usr.user_id) |
|
81 | 81 | Session().commit() |
|
82 | 82 | |
|
83 | 83 | |
|
84 | 84 | class TestUsers(unittest.TestCase): |
|
85 | 85 | |
|
86 | 86 | def __init__(self, methodName='runTest'): |
|
87 | 87 | super(TestUsers, self).__init__(methodName=methodName) |
|
88 | 88 | |
|
89 | 89 | def setUp(self): |
|
90 | 90 | self.u1 = UserModel().create_or_update(username=u'u1', |
|
91 | 91 | password=u'qweqwe', |
|
92 | 92 | email=u'u1@rhodecode.org', |
|
93 | 93 | firstname=u'u1', lastname=u'u1') |
|
94 | 94 | |
|
95 | 95 | def tearDown(self): |
|
96 | 96 | perm = Permission.query().all() |
|
97 | 97 | for p in perm: |
|
98 | 98 | UserModel().revoke_perm(self.u1, p) |
|
99 | 99 | |
|
100 | 100 | UserModel().delete(self.u1) |
|
101 | 101 | Session().commit() |
|
102 | 102 | |
|
103 | 103 | def test_add_perm(self): |
|
104 | 104 | perm = Permission.query().all()[0] |
|
105 | 105 | UserModel().grant_perm(self.u1, perm) |
|
106 | 106 | Session().commit() |
|
107 | 107 | self.assertEqual(UserModel().has_perm(self.u1, perm), True) |
|
108 | 108 | |
|
109 | 109 | def test_has_perm(self): |
|
110 | 110 | perm = Permission.query().all() |
|
111 | 111 | for p in perm: |
|
112 | 112 | has_p = UserModel().has_perm(self.u1, p) |
|
113 | 113 | self.assertEqual(False, has_p) |
|
114 | 114 | |
|
115 | 115 | def test_revoke_perm(self): |
|
116 | 116 | perm = Permission.query().all()[0] |
|
117 | 117 | UserModel().grant_perm(self.u1, perm) |
|
118 | 118 | Session().commit() |
|
119 | 119 | self.assertEqual(UserModel().has_perm(self.u1, perm), True) |
|
120 | 120 | |
|
121 | 121 | #revoke |
|
122 | 122 | UserModel().revoke_perm(self.u1, perm) |
|
123 | 123 | Session().commit() |
|
124 | 124 | self.assertEqual(UserModel().has_perm(self.u1, perm), False) |
General Comments 0
You need to be logged in to leave comments.
Login now