##// END OF EJS Templates
fixed #397 Private repository groups shows up before login...
marcink -
r2124:273ce1a9 beta
parent child Browse files
Show More
@@ -1,587 +1,588 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7
8 8 1.3.4 (**2012-XX-XX**)
9 9 ----------------------
10 10
11 11 :status: in-progress
12 12 :branch: beta
13 13
14 14 news
15 15 ++++
16 16
17 17 - Whoosh logging is now controlled by the .ini files logging setup
18 18 - added clone-url into edit form on /settings page
19 19 - added help text into repo add/edit forms
20 20 - created rcextensions module with additional mappings (ref #322) and
21 21 post push/pull/create repo hooks callbacks
22 22
23 23 fixes
24 24 +++++
25 25
26 26 - fixed #390 cache invalidation problems on repos inside group
27 27 - fixed #385 clone by ID url was loosing proxy prefix in URL
28 28 - fixed some unicode problems with waitress
29 29 - fixed issue with escaping < and > in changeset commits
30 30 - fixed error occurring during recursive group creation in API
31 31 create_repo function
32 32 - fixed #393 py2.5 fixes for routes url generator
33 - fixed #397 Private repository groups shows up before login
33 34
34 35 1.3.3 (**2012-03-02**)
35 36 ----------------------
36 37
37 38 news
38 39 ++++
39 40
40 41
41 42 fixes
42 43 +++++
43 44
44 45 - fixed some python2.5 compatibility issues
45 46 - fixed issues with removed repos was accidentally added as groups, after
46 47 full rescan of paths
47 48 - fixes #376 Cannot edit user (using container auth)
48 49 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
49 50 configuration
50 51 - fixed initial sorting of repos inside repo group
51 52 - fixes issue when user tried to resubmit same permission into user/user_groups
52 53 - bumped beaker version that fixes #375 leap error bug
53 54 - fixed raw_changeset for git. It was generated with hg patch headers
54 55 - fixed vcs issue with last_changeset for filenodes
55 56 - fixed missing commit after hook delete
56 57 - fixed #372 issues with git operation detection that caused a security issue
57 58 for git repos
58 59
59 60 1.3.2 (**2012-02-28**)
60 61 ----------------------
61 62
62 63 news
63 64 ++++
64 65
65 66
66 67 fixes
67 68 +++++
68 69
69 70 - fixed git protocol issues with repos-groups
70 71 - fixed git remote repos validator that prevented from cloning remote git repos
71 72 - fixes #370 ending slashes fixes for repo and groups
72 73 - fixes #368 improved git-protocol detection to handle other clients
73 74 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
74 75 Moved To Root
75 76 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
76 77 - fixed #373 missing cascade drop on user_group_to_perm table
77 78
78 79 1.3.1 (**2012-02-27**)
79 80 ----------------------
80 81
81 82 news
82 83 ++++
83 84
84 85
85 86 fixes
86 87 +++++
87 88
88 89 - redirection loop occurs when remember-me wasn't checked during login
89 90 - fixes issues with git blob history generation
90 91 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
91 92
92 93 1.3.0 (**2012-02-26**)
93 94 ----------------------
94 95
95 96 news
96 97 ++++
97 98
98 99 - code review, inspired by github code-comments
99 100 - #215 rst and markdown README files support
100 101 - #252 Container-based and proxy pass-through authentication support
101 102 - #44 branch browser. Filtering of changelog by branches
102 103 - mercurial bookmarks support
103 104 - new hover top menu, optimized to add maximum size for important views
104 105 - configurable clone url template with possibility to specify protocol like
105 106 ssh:// or http:// and also manually alter other parts of clone_url.
106 107 - enabled largefiles extension by default
107 108 - optimized summary file pages and saved a lot of unused space in them
108 109 - #239 option to manually mark repository as fork
109 110 - #320 mapping of commit authors to RhodeCode users
110 111 - #304 hashes are displayed using monospace font
111 112 - diff configuration, toggle white lines and context lines
112 113 - #307 configurable diffs, whitespace toggle, increasing context lines
113 114 - sorting on branches, tags and bookmarks using YUI datatable
114 115 - improved file filter on files page
115 116 - implements #330 api method for listing nodes ar particular revision
116 117 - #73 added linking issues in commit messages to chosen issue tracker url
117 118 based on user defined regular expression
118 119 - added linking of changesets in commit messages
119 120 - new compact changelog with expandable commit messages
120 121 - firstname and lastname are optional in user creation
121 122 - #348 added post-create repository hook
122 123 - #212 global encoding settings is now configurable from .ini files
123 124 - #227 added repository groups permissions
124 125 - markdown gets codehilite extensions
125 126 - new API methods, delete_repositories, grante/revoke permissions for groups
126 127 and repos
127 128
128 129
129 130 fixes
130 131 +++++
131 132
132 133 - rewrote dbsession management for atomic operations, and better error handling
133 134 - fixed sorting of repo tables
134 135 - #326 escape of special html entities in diffs
135 136 - normalized user_name => username in api attributes
136 137 - fixes #298 ldap created users with mixed case emails created conflicts
137 138 on saving a form
138 139 - fixes issue when owner of a repo couldn't revoke permissions for users
139 140 and groups
140 141 - fixes #271 rare JSON serialization problem with statistics
141 142 - fixes #337 missing validation check for conflicting names of a group with a
142 143 repositories group
143 144 - #340 fixed session problem for mysql and celery tasks
144 145 - fixed #331 RhodeCode mangles repository names if the a repository group
145 146 contains the "full path" to the repositories
146 147 - #355 RhodeCode doesn't store encrypted LDAP passwords
147 148
148 149 1.2.5 (**2012-01-28**)
149 150 ----------------------
150 151
151 152 news
152 153 ++++
153 154
154 155 fixes
155 156 +++++
156 157
157 158 - #340 Celery complains about MySQL server gone away, added session cleanup
158 159 for celery tasks
159 160 - #341 "scanning for repositories in None" log message during Rescan was missing
160 161 a parameter
161 162 - fixed creating archives with subrepos. Some hooks were triggered during that
162 163 operation leading to crash.
163 164 - fixed missing email in account page.
164 165 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
165 166 forking on windows impossible
166 167
167 168 1.2.4 (**2012-01-19**)
168 169 ----------------------
169 170
170 171 news
171 172 ++++
172 173
173 174 - RhodeCode is bundled with mercurial series 2.0.X by default, with
174 175 full support to largefiles extension. Enabled by default in new installations
175 176 - #329 Ability to Add/Remove Groups to/from a Repository via AP
176 177 - added requires.txt file with requirements
177 178
178 179 fixes
179 180 +++++
180 181
181 182 - fixes db session issues with celery when emailing admins
182 183 - #331 RhodeCode mangles repository names if the a repository group
183 184 contains the "full path" to the repositories
184 185 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
185 186 - DB session cleanup after hg protocol operations, fixes issues with
186 187 `mysql has gone away` errors
187 188 - #333 doc fixes for get_repo api function
188 189 - #271 rare JSON serialization problem with statistics enabled
189 190 - #337 Fixes issues with validation of repository name conflicting with
190 191 a group name. A proper message is now displayed.
191 192 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
192 193 doesn't work
193 194 - #316 fixes issues with web description in hgrc files
194 195
195 196 1.2.3 (**2011-11-02**)
196 197 ----------------------
197 198
198 199 news
199 200 ++++
200 201
201 202 - added option to manage repos group for non admin users
202 203 - added following API methods for get_users, create_user, get_users_groups,
203 204 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
204 205 get_repo, create_repo, add_user_to_repo
205 206 - implements #237 added password confirmation for my account
206 207 and admin edit user.
207 208 - implements #291 email notification for global events are now sent to all
208 209 administrator users, and global config email.
209 210
210 211 fixes
211 212 +++++
212 213
213 214 - added option for passing auth method for smtp mailer
214 215 - #276 issue with adding a single user with id>10 to usergroups
215 216 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
216 217 - #288 fixes managing of repos in a group for non admin user
217 218
218 219 1.2.2 (**2011-10-17**)
219 220 ----------------------
220 221
221 222 news
222 223 ++++
223 224
224 225 - #226 repo groups are available by path instead of numerical id
225 226
226 227 fixes
227 228 +++++
228 229
229 230 - #259 Groups with the same name but with different parent group
230 231 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
231 232 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
232 233 - #265 ldap save fails sometimes on converting attributes to booleans,
233 234 added getter and setter into model that will prevent from this on db model level
234 235 - fixed problems with timestamps issues #251 and #213
235 236 - fixes #266 RhodeCode allows to create repo with the same name and in
236 237 the same parent as group
237 238 - fixes #245 Rescan of the repositories on Windows
238 239 - fixes #248 cannot edit repos inside a group on windows
239 240 - fixes #219 forking problems on windows
240 241
241 242 1.2.1 (**2011-10-08**)
242 243 ----------------------
243 244
244 245 news
245 246 ++++
246 247
247 248
248 249 fixes
249 250 +++++
250 251
251 252 - fixed problems with basic auth and push problems
252 253 - gui fixes
253 254 - fixed logger
254 255
255 256 1.2.0 (**2011-10-07**)
256 257 ----------------------
257 258
258 259 news
259 260 ++++
260 261
261 262 - implemented #47 repository groups
262 263 - implemented #89 Can setup google analytics code from settings menu
263 264 - implemented #91 added nicer looking archive urls with more download options
264 265 like tags, branches
265 266 - implemented #44 into file browsing, and added follow branch option
266 267 - implemented #84 downloads can be enabled/disabled for each repository
267 268 - anonymous repository can be cloned without having to pass default:default
268 269 into clone url
269 270 - fixed #90 whoosh indexer can index chooses repositories passed in command
270 271 line
271 272 - extended journal with day aggregates and paging
272 273 - implemented #107 source code lines highlight ranges
273 274 - implemented #93 customizable changelog on combined revision ranges -
274 275 equivalent of githubs compare view
275 276 - implemented #108 extended and more powerful LDAP configuration
276 277 - implemented #56 users groups
277 278 - major code rewrites optimized codes for speed and memory usage
278 279 - raw and diff downloads are now in git format
279 280 - setup command checks for write access to given path
280 281 - fixed many issues with international characters and unicode. It uses utf8
281 282 decode with replace to provide less errors even with non utf8 encoded strings
282 283 - #125 added API KEY access to feeds
283 284 - #109 Repository can be created from external Mercurial link (aka. remote
284 285 repository, and manually updated (via pull) from admin panel
285 286 - beta git support - push/pull server + basic view for git repos
286 287 - added followers page and forks page
287 288 - server side file creation (with binary file upload interface)
288 289 and edition with commits powered by codemirror
289 290 - #111 file browser file finder, quick lookup files on whole file tree
290 291 - added quick login sliding menu into main page
291 292 - changelog uses lazy loading of affected files details, in some scenarios
292 293 this can improve speed of changelog page dramatically especially for
293 294 larger repositories.
294 295 - implements #214 added support for downloading subrepos in download menu.
295 296 - Added basic API for direct operations on rhodecode via JSON
296 297 - Implemented advanced hook management
297 298
298 299 fixes
299 300 +++++
300 301
301 302 - fixed file browser bug, when switching into given form revision the url was
302 303 not changing
303 304 - fixed propagation to error controller on simplehg and simplegit middlewares
304 305 - fixed error when trying to make a download on empty repository
305 306 - fixed problem with '[' chars in commit messages in journal
306 307 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
307 308 - journal fork fixes
308 309 - removed issue with space inside renamed repository after deletion
309 310 - fixed strange issue on formencode imports
310 311 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
311 312 - #150 fixes for errors on repositories mapped in db but corrupted in
312 313 filesystem
313 314 - fixed problem with ascendant characters in realm #181
314 315 - fixed problem with sqlite file based database connection pool
315 316 - whoosh indexer and code stats share the same dynamic extensions map
316 317 - fixes #188 - relationship delete of repo_to_perm entry on user removal
317 318 - fixes issue #189 Trending source files shows "show more" when no more exist
318 319 - fixes issue #197 Relative paths for pidlocks
319 320 - fixes issue #198 password will require only 3 chars now for login form
320 321 - fixes issue #199 wrong redirection for non admin users after creating a repository
321 322 - fixes issues #202, bad db constraint made impossible to attach same group
322 323 more than one time. Affects only mysql/postgres
323 324 - fixes #218 os.kill patch for windows was missing sig param
324 325 - improved rendering of dag (they are not trimmed anymore when number of
325 326 heads exceeds 5)
326 327
327 328 1.1.8 (**2011-04-12**)
328 329 ----------------------
329 330
330 331 news
331 332 ++++
332 333
333 334 - improved windows support
334 335
335 336 fixes
336 337 +++++
337 338
338 339 - fixed #140 freeze of python dateutil library, since new version is python2.x
339 340 incompatible
340 341 - setup-app will check for write permission in given path
341 342 - cleaned up license info issue #149
342 343 - fixes for issues #137,#116 and problems with unicode and accented characters.
343 344 - fixes crashes on gravatar, when passed in email as unicode
344 345 - fixed tooltip flickering problems
345 346 - fixed came_from redirection on windows
346 347 - fixed logging modules, and sql formatters
347 348 - windows fixes for os.kill issue #133
348 349 - fixes path splitting for windows issues #148
349 350 - fixed issue #143 wrong import on migration to 1.1.X
350 351 - fixed problems with displaying binary files, thanks to Thomas Waldmann
351 352 - removed name from archive files since it's breaking ui for long repo names
352 353 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
353 354 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
354 355 Thomas Waldmann
355 356 - fixed issue #166 summary pager was skipping 10 revisions on second page
356 357
357 358
358 359 1.1.7 (**2011-03-23**)
359 360 ----------------------
360 361
361 362 news
362 363 ++++
363 364
364 365 fixes
365 366 +++++
366 367
367 368 - fixed (again) #136 installation support for FreeBSD
368 369
369 370
370 371 1.1.6 (**2011-03-21**)
371 372 ----------------------
372 373
373 374 news
374 375 ++++
375 376
376 377 fixes
377 378 +++++
378 379
379 380 - fixed #136 installation support for FreeBSD
380 381 - RhodeCode will check for python version during installation
381 382
382 383 1.1.5 (**2011-03-17**)
383 384 ----------------------
384 385
385 386 news
386 387 ++++
387 388
388 389 - basic windows support, by exchanging pybcrypt into sha256 for windows only
389 390 highly inspired by idea of mantis406
390 391
391 392 fixes
392 393 +++++
393 394
394 395 - fixed sorting by author in main page
395 396 - fixed crashes with diffs on binary files
396 397 - fixed #131 problem with boolean values for LDAP
397 398 - fixed #122 mysql problems thanks to striker69
398 399 - fixed problem with errors on calling raw/raw_files/annotate functions
399 400 with unknown revisions
400 401 - fixed returned rawfiles attachment names with international character
401 402 - cleaned out docs, big thanks to Jason Harris
402 403
403 404 1.1.4 (**2011-02-19**)
404 405 ----------------------
405 406
406 407 news
407 408 ++++
408 409
409 410 fixes
410 411 +++++
411 412
412 413 - fixed formencode import problem on settings page, that caused server crash
413 414 when that page was accessed as first after server start
414 415 - journal fixes
415 416 - fixed option to access repository just by entering http://server/<repo_name>
416 417
417 418 1.1.3 (**2011-02-16**)
418 419 ----------------------
419 420
420 421 news
421 422 ++++
422 423
423 424 - implemented #102 allowing the '.' character in username
424 425 - added option to access repository just by entering http://server/<repo_name>
425 426 - celery task ignores result for better performance
426 427
427 428 fixes
428 429 +++++
429 430
430 431 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
431 432 apollo13 and Johan Walles
432 433 - small fixes in journal
433 434 - fixed problems with getting setting for celery from .ini files
434 435 - registration, password reset and login boxes share the same title as main
435 436 application now
436 437 - fixed #113: to high permissions to fork repository
437 438 - fixed problem with '[' chars in commit messages in journal
438 439 - removed issue with space inside renamed repository after deletion
439 440 - db transaction fixes when filesystem repository creation failed
440 441 - fixed #106 relation issues on databases different than sqlite
441 442 - fixed static files paths links to use of url() method
442 443
443 444 1.1.2 (**2011-01-12**)
444 445 ----------------------
445 446
446 447 news
447 448 ++++
448 449
449 450
450 451 fixes
451 452 +++++
452 453
453 454 - fixes #98 protection against float division of percentage stats
454 455 - fixed graph bug
455 456 - forced webhelpers version since it was making troubles during installation
456 457
457 458 1.1.1 (**2011-01-06**)
458 459 ----------------------
459 460
460 461 news
461 462 ++++
462 463
463 464 - added force https option into ini files for easier https usage (no need to
464 465 set server headers with this options)
465 466 - small css updates
466 467
467 468 fixes
468 469 +++++
469 470
470 471 - fixed #96 redirect loop on files view on repositories without changesets
471 472 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
472 473 and server crashed with errors
473 474 - fixed large tooltips problems on main page
474 475 - fixed #92 whoosh indexer is more error proof
475 476
476 477 1.1.0 (**2010-12-18**)
477 478 ----------------------
478 479
479 480 news
480 481 ++++
481 482
482 483 - rewrite of internals for vcs >=0.1.10
483 484 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
484 485 with older clients
485 486 - anonymous access, authentication via ldap
486 487 - performance upgrade for cached repos list - each repository has its own
487 488 cache that's invalidated when needed.
488 489 - performance upgrades on repositories with large amount of commits (20K+)
489 490 - main page quick filter for filtering repositories
490 491 - user dashboards with ability to follow chosen repositories actions
491 492 - sends email to admin on new user registration
492 493 - added cache/statistics reset options into repository settings
493 494 - more detailed action logger (based on hooks) with pushed changesets lists
494 495 and options to disable those hooks from admin panel
495 496 - introduced new enhanced changelog for merges that shows more accurate results
496 497 - new improved and faster code stats (based on pygments lexers mapping tables,
497 498 showing up to 10 trending sources for each repository. Additionally stats
498 499 can be disabled in repository settings.
499 500 - gui optimizations, fixed application width to 1024px
500 501 - added cut off (for large files/changesets) limit into config files
501 502 - whoosh, celeryd, upgrade moved to paster command
502 503 - other than sqlite database backends can be used
503 504
504 505 fixes
505 506 +++++
506 507
507 508 - fixes #61 forked repo was showing only after cache expired
508 509 - fixes #76 no confirmation on user deletes
509 510 - fixes #66 Name field misspelled
510 511 - fixes #72 block user removal when he owns repositories
511 512 - fixes #69 added password confirmation fields
512 513 - fixes #87 RhodeCode crashes occasionally on updating repository owner
513 514 - fixes #82 broken annotations on files with more than 1 blank line at the end
514 515 - a lot of fixes and tweaks for file browser
515 516 - fixed detached session issues
516 517 - fixed when user had no repos he would see all repos listed in my account
517 518 - fixed ui() instance bug when global hgrc settings was loaded for server
518 519 instance and all hgrc options were merged with our db ui() object
519 520 - numerous small bugfixes
520 521
521 522 (special thanks for TkSoh for detailed feedback)
522 523
523 524
524 525 1.0.2 (**2010-11-12**)
525 526 ----------------------
526 527
527 528 news
528 529 ++++
529 530
530 531 - tested under python2.7
531 532 - bumped sqlalchemy and celery versions
532 533
533 534 fixes
534 535 +++++
535 536
536 537 - fixed #59 missing graph.js
537 538 - fixed repo_size crash when repository had broken symlinks
538 539 - fixed python2.5 crashes.
539 540
540 541
541 542 1.0.1 (**2010-11-10**)
542 543 ----------------------
543 544
544 545 news
545 546 ++++
546 547
547 548 - small css updated
548 549
549 550 fixes
550 551 +++++
551 552
552 553 - fixed #53 python2.5 incompatible enumerate calls
553 554 - fixed #52 disable mercurial extension for web
554 555 - fixed #51 deleting repositories don't delete it's dependent objects
555 556
556 557
557 558 1.0.0 (**2010-11-02**)
558 559 ----------------------
559 560
560 561 - security bugfix simplehg wasn't checking for permissions on commands
561 562 other than pull or push.
562 563 - fixed doubled messages after push or pull in admin journal
563 564 - templating and css corrections, fixed repo switcher on chrome, updated titles
564 565 - admin menu accessible from options menu on repository view
565 566 - permissions cached queries
566 567
567 568 1.0.0rc4 (**2010-10-12**)
568 569 --------------------------
569 570
570 571 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
571 572 - removed cache_manager settings from sqlalchemy meta
572 573 - added sqlalchemy cache settings to ini files
573 574 - validated password length and added second try of failure on paster setup-app
574 575 - fixed setup database destroy prompt even when there was no db
575 576
576 577
577 578 1.0.0rc3 (**2010-10-11**)
578 579 -------------------------
579 580
580 581 - fixed i18n during installation.
581 582
582 583 1.0.0rc2 (**2010-10-11**)
583 584 -------------------------
584 585
585 586 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
586 587 occure. After vcs is fixed it'll be put back again.
587 588 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,561 +1,561 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
29 29 from pylons import url
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
33 33 from rhodecode.lib.caching_query import FromCache
34 34
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
39 39 from rhodecode.lib.exceptions import DefaultUserException, \
40 40 UserOwnsReposException
41 41
42 42 from sqlalchemy.exc import DatabaseError
43 43
44 44 from sqlalchemy.orm import joinedload
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 PERM_WEIGHTS = {
50 50 'repository.none': 0,
51 51 'repository.read': 1,
52 52 'repository.write': 3,
53 53 'repository.admin': 4,
54 54 'group.none': 0,
55 55 'group.read': 1,
56 56 'group.write': 3,
57 57 'group.admin': 4,
58 58 }
59 59
60 60
61 61 class UserModel(BaseModel):
62 62
63 63 def __get_user(self, user):
64 64 return self._get_instance(User, user, callback=User.get_by_username)
65 65
66 66 def __get_perm(self, permission):
67 67 return self._get_instance(Permission, permission,
68 68 callback=Permission.get_by_key)
69 69
70 70 def get(self, user_id, cache=False):
71 71 user = self.sa.query(User)
72 72 if cache:
73 73 user = user.options(FromCache("sql_cache_short",
74 74 "get_user_%s" % user_id))
75 75 return user.get(user_id)
76 76
77 77 def get_user(self, user):
78 78 return self.__get_user(user)
79 79
80 80 def get_by_username(self, username, cache=False, case_insensitive=False):
81 81
82 82 if case_insensitive:
83 83 user = self.sa.query(User).filter(User.username.ilike(username))
84 84 else:
85 85 user = self.sa.query(User)\
86 86 .filter(User.username == username)
87 87 if cache:
88 88 user = user.options(FromCache("sql_cache_short",
89 89 "get_user_%s" % username))
90 90 return user.scalar()
91 91
92 92 def get_by_api_key(self, api_key, cache=False):
93 93 return User.get_by_api_key(api_key, cache)
94 94
95 95 def create(self, form_data):
96 96 try:
97 97 new_user = User()
98 98 for k, v in form_data.items():
99 99 setattr(new_user, k, v)
100 100
101 101 new_user.api_key = generate_api_key(form_data['username'])
102 102 self.sa.add(new_user)
103 103 return new_user
104 104 except:
105 105 log.error(traceback.format_exc())
106 106 raise
107 107
108 108 def create_or_update(self, username, password, email, name, lastname,
109 109 active=True, admin=False, ldap_dn=None):
110 110 """
111 111 Creates a new instance if not found, or updates current one
112 112
113 113 :param username:
114 114 :param password:
115 115 :param email:
116 116 :param active:
117 117 :param name:
118 118 :param lastname:
119 119 :param active:
120 120 :param admin:
121 121 :param ldap_dn:
122 122 """
123 123
124 124 from rhodecode.lib.auth import get_crypt_password
125 125
126 126 log.debug('Checking for %s account in RhodeCode database' % username)
127 127 user = User.get_by_username(username, case_insensitive=True)
128 128 if user is None:
129 129 log.debug('creating new user %s' % username)
130 130 new_user = User()
131 131 else:
132 132 log.debug('updating user %s' % username)
133 133 new_user = user
134 134
135 135 try:
136 136 new_user.username = username
137 137 new_user.admin = admin
138 138 new_user.password = get_crypt_password(password)
139 139 new_user.api_key = generate_api_key(username)
140 140 new_user.email = email
141 141 new_user.active = active
142 142 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
143 143 new_user.name = name
144 144 new_user.lastname = lastname
145 145 self.sa.add(new_user)
146 146 return new_user
147 147 except (DatabaseError,):
148 148 log.error(traceback.format_exc())
149 149 raise
150 150
151 151 def create_for_container_auth(self, username, attrs):
152 152 """
153 153 Creates the given user if it's not already in the database
154 154
155 155 :param username:
156 156 :param attrs:
157 157 """
158 158 if self.get_by_username(username, case_insensitive=True) is None:
159 159
160 160 # autogenerate email for container account without one
161 161 generate_email = lambda usr: '%s@container_auth.account' % usr
162 162
163 163 try:
164 164 new_user = User()
165 165 new_user.username = username
166 166 new_user.password = None
167 167 new_user.api_key = generate_api_key(username)
168 168 new_user.email = attrs['email']
169 169 new_user.active = attrs.get('active', True)
170 170 new_user.name = attrs['name'] or generate_email(username)
171 171 new_user.lastname = attrs['lastname']
172 172
173 173 self.sa.add(new_user)
174 174 return new_user
175 175 except (DatabaseError,):
176 176 log.error(traceback.format_exc())
177 177 self.sa.rollback()
178 178 raise
179 179 log.debug('User %s already exists. Skipping creation of account'
180 180 ' for container auth.', username)
181 181 return None
182 182
183 183 def create_ldap(self, username, password, user_dn, attrs):
184 184 """
185 185 Checks if user is in database, if not creates this user marked
186 186 as ldap user
187 187
188 188 :param username:
189 189 :param password:
190 190 :param user_dn:
191 191 :param attrs:
192 192 """
193 193 from rhodecode.lib.auth import get_crypt_password
194 194 log.debug('Checking for such ldap account in RhodeCode database')
195 195 if self.get_by_username(username, case_insensitive=True) is None:
196 196
197 197 # autogenerate email for ldap account without one
198 198 generate_email = lambda usr: '%s@ldap.account' % usr
199 199
200 200 try:
201 201 new_user = User()
202 202 username = username.lower()
203 203 # add ldap account always lowercase
204 204 new_user.username = username
205 205 new_user.password = get_crypt_password(password)
206 206 new_user.api_key = generate_api_key(username)
207 207 new_user.email = attrs['email'] or generate_email(username)
208 208 new_user.active = attrs.get('active', True)
209 209 new_user.ldap_dn = safe_unicode(user_dn)
210 210 new_user.name = attrs['name']
211 211 new_user.lastname = attrs['lastname']
212 212
213 213 self.sa.add(new_user)
214 214 return new_user
215 215 except (DatabaseError,):
216 216 log.error(traceback.format_exc())
217 217 self.sa.rollback()
218 218 raise
219 219 log.debug('this %s user exists skipping creation of ldap account',
220 220 username)
221 221 return None
222 222
223 223 def create_registration(self, form_data):
224 224 from rhodecode.model.notification import NotificationModel
225 225
226 226 try:
227 227 new_user = User()
228 228 for k, v in form_data.items():
229 229 if k != 'admin':
230 230 setattr(new_user, k, v)
231 231
232 232 self.sa.add(new_user)
233 233 self.sa.flush()
234 234
235 235 # notification to admins
236 236 subject = _('new user registration')
237 237 body = ('New user registration\n'
238 238 '---------------------\n'
239 239 '- Username: %s\n'
240 240 '- Full Name: %s\n'
241 241 '- Email: %s\n')
242 242 body = body % (new_user.username, new_user.full_name,
243 243 new_user.email)
244 244 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
245 245 kw = {'registered_user_url': edit_url}
246 246 NotificationModel().create(created_by=new_user, subject=subject,
247 247 body=body, recipients=None,
248 248 type_=Notification.TYPE_REGISTRATION,
249 249 email_kwargs=kw)
250 250
251 251 except:
252 252 log.error(traceback.format_exc())
253 253 raise
254 254
255 255 def update(self, user_id, form_data):
256 256 try:
257 257 user = self.get(user_id, cache=False)
258 258 if user.username == 'default':
259 259 raise DefaultUserException(
260 260 _("You can't Edit this user since it's"
261 261 " crucial for entire application"))
262 262
263 263 for k, v in form_data.items():
264 264 if k == 'new_password' and v != '':
265 265 user.password = v
266 266 user.api_key = generate_api_key(user.username)
267 267 else:
268 268 setattr(user, k, v)
269 269
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_my_account(self, user_id, form_data):
276 276 try:
277 277 user = self.get(user_id, cache=False)
278 278 if user.username == 'default':
279 279 raise DefaultUserException(
280 280 _("You can't Edit this user since it's"
281 281 " crucial for entire application"))
282 282 for k, v in form_data.items():
283 283 if k == 'new_password' and v != '':
284 284 user.password = v
285 285 user.api_key = generate_api_key(user.username)
286 286 else:
287 287 if k not in ['admin', 'active']:
288 288 setattr(user, k, v)
289 289
290 290 self.sa.add(user)
291 291 except:
292 292 log.error(traceback.format_exc())
293 293 raise
294 294
295 295 def delete(self, user):
296 296 user = self.__get_user(user)
297 297
298 298 try:
299 299 if user.username == 'default':
300 300 raise DefaultUserException(
301 _("You can't remove this user since it's"
302 " crucial for entire application"))
301 _("You can't remove this user since it's"
302 " crucial for entire application")
303 )
303 304 if user.repositories:
304 raise UserOwnsReposException(_('This user still owns %s '
305 'repositories and cannot be '
306 'removed. Switch owners or '
307 'remove those repositories') \
308 % user.repositories)
305 raise UserOwnsReposException(
306 _('user "%s" still owns %s repositories and cannot be '
307 'removed. Switch owners or remove those repositories')
308 % (user.username, user.repositories)
309 )
309 310 self.sa.delete(user)
310 311 except:
311 312 log.error(traceback.format_exc())
312 313 raise
313 314
314 315 def reset_password_link(self, data):
315 316 from rhodecode.lib.celerylib import tasks, run_task
316 317 run_task(tasks.send_password_link, data['email'])
317 318
318 319 def reset_password(self, data):
319 320 from rhodecode.lib.celerylib import tasks, run_task
320 321 run_task(tasks.reset_user_password, data['email'])
321 322
322 323 def fill_data(self, auth_user, user_id=None, api_key=None):
323 324 """
324 325 Fetches auth_user by user_id,or api_key if present.
325 326 Fills auth_user attributes with those taken from database.
326 327 Additionally set's is_authenitated if lookup fails
327 328 present in database
328 329
329 330 :param auth_user: instance of user to set attributes
330 331 :param user_id: user id to fetch by
331 332 :param api_key: api key to fetch by
332 333 """
333 334 if user_id is None and api_key is None:
334 335 raise Exception('You need to pass user_id or api_key')
335 336
336 337 try:
337 338 if api_key:
338 339 dbuser = self.get_by_api_key(api_key)
339 340 else:
340 341 dbuser = self.get(user_id)
341 342
342 343 if dbuser is not None and dbuser.active:
343 344 log.debug('filling %s data' % dbuser)
344 345 for k, v in dbuser.get_dict().items():
345 346 setattr(auth_user, k, v)
346 347 else:
347 348 return False
348 349
349 350 except:
350 351 log.error(traceback.format_exc())
351 352 auth_user.is_authenticated = False
352 353 return False
353 354
354 355 return True
355 356
356 357 def fill_perms(self, user):
357 358 """
358 359 Fills user permission attribute with permissions taken from database
359 360 works for permissions given for repositories, and for permissions that
360 361 are granted to groups
361 362
362 363 :param user: user instance to fill his perms
363 364 """
364 365 RK = 'repositories'
365 366 GK = 'repositories_groups'
366 367 GLOBAL = 'global'
367 368 user.permissions[RK] = {}
368 369 user.permissions[GK] = {}
369 370 user.permissions[GLOBAL] = set()
370 371
371 372 #======================================================================
372 373 # fetch default permissions
373 374 #======================================================================
374 375 default_user = User.get_by_username('default', cache=True)
375 376 default_user_id = default_user.user_id
376 377
377 378 default_repo_perms = Permission.get_default_perms(default_user_id)
378 379 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
379 380
380 381 if user.is_admin:
381 382 #==================================================================
382 383 # admin user have all default rights for repositories
383 384 # and groups set to admin
384 385 #==================================================================
385 386 user.permissions[GLOBAL].add('hg.admin')
386 387
387 388 # repositories
388 389 for perm in default_repo_perms:
389 390 r_k = perm.UserRepoToPerm.repository.repo_name
390 391 p = 'repository.admin'
391 392 user.permissions[RK][r_k] = p
392 393
393 394 # repositories groups
394 395 for perm in default_repo_groups_perms:
395 396 rg_k = perm.UserRepoGroupToPerm.group.group_name
396 397 p = 'group.admin'
397 398 user.permissions[GK][rg_k] = p
398 399
399 400 else:
400 401 #==================================================================
401 402 # set default permissions first for repositories and groups
402 403 #==================================================================
403 404 uid = user.user_id
404 405
405 406 # default global permissions
406 407 default_global_perms = self.sa.query(UserToPerm)\
407 408 .filter(UserToPerm.user_id == default_user_id)
408 409
409 410 for perm in default_global_perms:
410 411 user.permissions[GLOBAL].add(perm.permission.permission_name)
411 412
412 413 # default for repositories
413 414 for perm in default_repo_perms:
414 415 r_k = perm.UserRepoToPerm.repository.repo_name
415 416 if perm.Repository.private and not (perm.Repository.user_id == uid):
416 417 # disable defaults for private repos,
417 418 p = 'repository.none'
418 419 elif perm.Repository.user_id == uid:
419 420 # set admin if owner
420 421 p = 'repository.admin'
421 422 else:
422 423 p = perm.Permission.permission_name
423 424
424 425 user.permissions[RK][r_k] = p
425 426
426 427 # default for repositories groups
427 428 for perm in default_repo_groups_perms:
428 429 rg_k = perm.UserRepoGroupToPerm.group.group_name
429 430 p = perm.Permission.permission_name
430 431 user.permissions[GK][rg_k] = p
431 432
432 433 #==================================================================
433 434 # overwrite default with user permissions if any
434 435 #==================================================================
435 436
436 437 # user global
437 438 user_perms = self.sa.query(UserToPerm)\
438 439 .options(joinedload(UserToPerm.permission))\
439 440 .filter(UserToPerm.user_id == uid).all()
440 441
441 442 for perm in user_perms:
442 443 user.permissions[GLOBAL].add(perm.permission.permission_name)
443 444
444 445 # user repositories
445 446 user_repo_perms = \
446 447 self.sa.query(UserRepoToPerm, Permission, Repository)\
447 448 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
448 449 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
449 450 .filter(UserRepoToPerm.user_id == uid)\
450 451 .all()
451 452
452 453 for perm in user_repo_perms:
453 454 # set admin if owner
454 455 r_k = perm.UserRepoToPerm.repository.repo_name
455 456 if perm.Repository.user_id == uid:
456 457 p = 'repository.admin'
457 458 else:
458 459 p = perm.Permission.permission_name
459 460 user.permissions[RK][r_k] = p
460 461
461 462 #==================================================================
462 463 # check if user is part of groups for this repository and fill in
463 464 # (or replace with higher) permissions
464 465 #==================================================================
465 466
466 467 # users group global
467 468 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
468 469 .options(joinedload(UsersGroupToPerm.permission))\
469 470 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
470 471 UsersGroupMember.users_group_id))\
471 472 .filter(UsersGroupMember.user_id == uid).all()
472 473
473 474 for perm in user_perms_from_users_groups:
474 475 user.permissions[GLOBAL].add(perm.permission.permission_name)
475 476
476 477 # users group repositories
477 478 user_repo_perms_from_users_groups = \
478 479 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
479 480 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
480 481 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
481 482 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
482 483 .filter(UsersGroupMember.user_id == uid)\
483 484 .all()
484 485
485 486 for perm in user_repo_perms_from_users_groups:
486 487 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
487 488 p = perm.Permission.permission_name
488 489 cur_perm = user.permissions[RK][r_k]
489 490 # overwrite permission only if it's greater than permission
490 491 # given from other sources
491 492 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
492 493 user.permissions[RK][r_k] = p
493 494
494 495 #==================================================================
495 496 # get access for this user for repos group and override defaults
496 497 #==================================================================
497 498
498 499 # user repositories groups
499 500 user_repo_groups_perms = \
500 501 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
501 502 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
502 503 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
503 .filter(UserRepoToPerm.user_id == uid)\
504 .filter(UserRepoGroupToPerm.user_id == uid)\
504 505 .all()
505 506
506 507 for perm in user_repo_groups_perms:
507 508 rg_k = perm.UserRepoGroupToPerm.group.group_name
508 509 p = perm.Permission.permission_name
509 510 cur_perm = user.permissions[GK][rg_k]
510 511 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
511 512 user.permissions[GK][rg_k] = p
512
513 513 return user
514 514
515 515 def has_perm(self, user, perm):
516 516 if not isinstance(perm, Permission):
517 517 raise Exception('perm needs to be an instance of Permission class '
518 518 'got %s instead' % type(perm))
519 519
520 520 user = self.__get_user(user)
521 521
522 522 return UserToPerm.query().filter(UserToPerm.user == user)\
523 523 .filter(UserToPerm.permission == perm).scalar() is not None
524 524
525 525 def grant_perm(self, user, perm):
526 526 """
527 527 Grant user global permissions
528 528
529 529 :param user:
530 530 :param perm:
531 531 """
532 532 user = self.__get_user(user)
533 533 perm = self.__get_perm(perm)
534 534 # if this permission is already granted skip it
535 535 _perm = UserToPerm.query()\
536 536 .filter(UserToPerm.user == user)\
537 537 .filter(UserToPerm.permission == perm)\
538 538 .scalar()
539 539 if _perm:
540 540 return
541 541 new = UserToPerm()
542 542 new.user = user
543 543 new.permission = perm
544 544 self.sa.add(new)
545 545
546 546 def revoke_perm(self, user, perm):
547 547 """
548 548 Revoke users global permissions
549 549
550 550 :param user:
551 551 :param perm:
552 552 """
553 553 user = self.__get_user(user)
554 554 perm = self.__get_perm(perm)
555 555
556 556 obj = UserToPerm.query()\
557 557 .filter(UserToPerm.user == user)\
558 558 .filter(UserToPerm.permission == perm)\
559 559 .scalar()
560 560 if obj:
561 561 self.sa.delete(obj)
@@ -1,580 +1,660 b''
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4
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, Notification, UserNotification, \
8 8 UsersGroup, UsersGroupMember, Permission
9 9 from sqlalchemy.exc import IntegrityError
10 10 from rhodecode.model.user import UserModel
11 11
12 12 from rhodecode.model.meta import Session
13 13 from rhodecode.model.notification import NotificationModel
14 14 from rhodecode.model.users_group import UsersGroupModel
15 15 from rhodecode.lib.auth import AuthUser
16 16
17 17
18 18 def _make_group(path, desc='desc', parent_id=None,
19 19 skip_if_exists=False):
20 20
21 21 gr = RepoGroup.get_by_group_name(path)
22 22 if gr and skip_if_exists:
23 23 return gr
24 24
25 25 gr = ReposGroupModel().create(path, desc, parent_id)
26 26 return gr
27 27
28 28
29 29 class TestReposGroups(unittest.TestCase):
30 30
31 31 def setUp(self):
32 32 self.g1 = _make_group('test1', skip_if_exists=True)
33 33 Session.commit()
34 34 self.g2 = _make_group('test2', skip_if_exists=True)
35 35 Session.commit()
36 36 self.g3 = _make_group('test3', skip_if_exists=True)
37 37 Session.commit()
38 38
39 39 def tearDown(self):
40 40 print 'out'
41 41
42 42 def __check_path(self, *path):
43 43 """
44 44 Checks the path for existance !
45 45 """
46 46 path = [TESTS_TMP_PATH] + list(path)
47 47 path = os.path.join(*path)
48 48 return os.path.isdir(path)
49 49
50 50 def _check_folders(self):
51 51 print os.listdir(TESTS_TMP_PATH)
52 52
53 53 def __delete_group(self, id_):
54 54 ReposGroupModel().delete(id_)
55 55
56 56 def __update_group(self, id_, path, desc='desc', parent_id=None):
57 57 form_data = dict(
58 58 group_name=path,
59 59 group_description=desc,
60 60 group_parent_id=parent_id,
61 61 perms_updates=[],
62 62 perms_new=[]
63 63 )
64 64 gr = ReposGroupModel().update(id_, form_data)
65 65 return gr
66 66
67 67 def test_create_group(self):
68 68 g = _make_group('newGroup')
69 69 self.assertEqual(g.full_path, 'newGroup')
70 70
71 71 self.assertTrue(self.__check_path('newGroup'))
72 72
73 73 def test_create_same_name_group(self):
74 74 self.assertRaises(IntegrityError, lambda:_make_group('newGroup'))
75 75 Session.rollback()
76 76
77 77 def test_same_subgroup(self):
78 78 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
79 79 self.assertEqual(sg1.parent_group, self.g1)
80 80 self.assertEqual(sg1.full_path, 'test1/sub1')
81 81 self.assertTrue(self.__check_path('test1', 'sub1'))
82 82
83 83 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
84 84 self.assertEqual(ssg1.parent_group, sg1)
85 85 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
86 86 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
87 87
88 88 def test_remove_group(self):
89 89 sg1 = _make_group('deleteme')
90 90 self.__delete_group(sg1.group_id)
91 91
92 92 self.assertEqual(RepoGroup.get(sg1.group_id), None)
93 93 self.assertFalse(self.__check_path('deteteme'))
94 94
95 95 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
96 96 self.__delete_group(sg1.group_id)
97 97
98 98 self.assertEqual(RepoGroup.get(sg1.group_id), None)
99 99 self.assertFalse(self.__check_path('test1', 'deteteme'))
100 100
101 101 def test_rename_single_group(self):
102 102 sg1 = _make_group('initial')
103 103
104 104 new_sg1 = self.__update_group(sg1.group_id, 'after')
105 105 self.assertTrue(self.__check_path('after'))
106 106 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
107 107
108 108 def test_update_group_parent(self):
109 109
110 110 sg1 = _make_group('initial', parent_id=self.g1.group_id)
111 111
112 112 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
113 113 self.assertTrue(self.__check_path('test1', 'after'))
114 114 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
115 115
116 116 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
117 117 self.assertTrue(self.__check_path('test3', 'after'))
118 118 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
119 119
120 120 new_sg1 = self.__update_group(sg1.group_id, 'hello')
121 121 self.assertTrue(self.__check_path('hello'))
122 122
123 123 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
124 124
125 125 def test_subgrouping_with_repo(self):
126 126
127 127 g1 = _make_group('g1')
128 128 g2 = _make_group('g2')
129 129
130 130 # create new repo
131 131 form_data = dict(repo_name='john',
132 132 repo_name_full='john',
133 133 fork_name=None,
134 134 description=None,
135 135 repo_group=None,
136 136 private=False,
137 137 repo_type='hg',
138 138 clone_uri=None)
139 139 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
140 140 r = RepoModel().create(form_data, cur_user)
141 141
142 142 self.assertEqual(r.repo_name, 'john')
143 143
144 144 # put repo into group
145 145 form_data = form_data
146 146 form_data['repo_group'] = g1.group_id
147 147 form_data['perms_new'] = []
148 148 form_data['perms_updates'] = []
149 149 RepoModel().update(r.repo_name, form_data)
150 150 self.assertEqual(r.repo_name, 'g1/john')
151 151
152 152 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
153 153 self.assertTrue(self.__check_path('g2', 'g1'))
154 154
155 155 # test repo
156 156 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
157 157
158 158
159 159 def test_move_to_root(self):
160 160 g1 = _make_group('t11')
161 161 Session.commit()
162 162 g2 = _make_group('t22',parent_id=g1.group_id)
163 163 Session.commit()
164 164
165 165 self.assertEqual(g2.full_path,'t11/t22')
166 166 self.assertTrue(self.__check_path('t11', 't22'))
167 167
168 168 g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
169 169 Session.commit()
170 170
171 171 self.assertEqual(g2.group_name,'g22')
172 172 # we moved out group from t1 to '' so it's full path should be 'g2'
173 173 self.assertEqual(g2.full_path,'g22')
174 174 self.assertFalse(self.__check_path('t11', 't22'))
175 175 self.assertTrue(self.__check_path('g22'))
176 176
177 177
178 178 class TestUser(unittest.TestCase):
179 179 def __init__(self, methodName='runTest'):
180 180 Session.remove()
181 181 super(TestUser, self).__init__(methodName=methodName)
182 182
183 183 def test_create_and_remove(self):
184 184 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
185 185 email=u'u232@rhodecode.org',
186 186 name=u'u1', lastname=u'u1')
187 187 Session.commit()
188 188 self.assertEqual(User.get_by_username(u'test_user'), usr)
189 189
190 190 # make users group
191 191 users_group = UsersGroupModel().create('some_example_group')
192 192 Session.commit()
193 193
194 194 UsersGroupModel().add_user_to_group(users_group, usr)
195 195 Session.commit()
196 196
197 197 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
198 198 self.assertEqual(UsersGroupMember.query().count(), 1)
199 199 UserModel().delete(usr.user_id)
200 200 Session.commit()
201 201
202 202 self.assertEqual(UsersGroupMember.query().all(), [])
203 203
204 204
205 205 class TestNotifications(unittest.TestCase):
206 206
207 207 def __init__(self, methodName='runTest'):
208 208 Session.remove()
209 209 self.u1 = UserModel().create_or_update(username=u'u1',
210 210 password=u'qweqwe',
211 211 email=u'u1@rhodecode.org',
212 212 name=u'u1', lastname=u'u1')
213 213 Session.commit()
214 214 self.u1 = self.u1.user_id
215 215
216 216 self.u2 = UserModel().create_or_update(username=u'u2',
217 217 password=u'qweqwe',
218 218 email=u'u2@rhodecode.org',
219 219 name=u'u2', lastname=u'u3')
220 220 Session.commit()
221 221 self.u2 = self.u2.user_id
222 222
223 223 self.u3 = UserModel().create_or_update(username=u'u3',
224 224 password=u'qweqwe',
225 225 email=u'u3@rhodecode.org',
226 226 name=u'u3', lastname=u'u3')
227 227 Session.commit()
228 228 self.u3 = self.u3.user_id
229 229
230 230 super(TestNotifications, self).__init__(methodName=methodName)
231 231
232 232 def _clean_notifications(self):
233 233 for n in Notification.query().all():
234 234 Session.delete(n)
235 235
236 236 Session.commit()
237 237 self.assertEqual(Notification.query().all(), [])
238 238
239 239 def tearDown(self):
240 240 self._clean_notifications()
241 241
242 242 def test_create_notification(self):
243 243 self.assertEqual([], Notification.query().all())
244 244 self.assertEqual([], UserNotification.query().all())
245 245
246 246 usrs = [self.u1, self.u2]
247 247 notification = NotificationModel().create(created_by=self.u1,
248 248 subject=u'subj', body=u'hi there',
249 249 recipients=usrs)
250 250 Session.commit()
251 251 u1 = User.get(self.u1)
252 252 u2 = User.get(self.u2)
253 253 u3 = User.get(self.u3)
254 254 notifications = Notification.query().all()
255 255 self.assertEqual(len(notifications), 1)
256 256
257 257 unotification = UserNotification.query()\
258 258 .filter(UserNotification.notification == notification).all()
259 259
260 260 self.assertEqual(notifications[0].recipients, [u1, u2])
261 261 self.assertEqual(notification.notification_id,
262 262 notifications[0].notification_id)
263 263 self.assertEqual(len(unotification), len(usrs))
264 264 self.assertEqual([x.user.user_id for x in unotification], usrs)
265 265
266 266 def test_user_notifications(self):
267 267 self.assertEqual([], Notification.query().all())
268 268 self.assertEqual([], UserNotification.query().all())
269 269
270 270 notification1 = NotificationModel().create(created_by=self.u1,
271 271 subject=u'subj', body=u'hi there1',
272 272 recipients=[self.u3])
273 273 Session.commit()
274 274 notification2 = NotificationModel().create(created_by=self.u1,
275 275 subject=u'subj', body=u'hi there2',
276 276 recipients=[self.u3])
277 277 Session.commit()
278 278 u3 = Session.query(User).get(self.u3)
279 279
280 280 self.assertEqual(sorted([x.notification for x in u3.notifications]),
281 281 sorted([notification2, notification1]))
282 282
283 283 def test_delete_notifications(self):
284 284 self.assertEqual([], Notification.query().all())
285 285 self.assertEqual([], UserNotification.query().all())
286 286
287 287 notification = NotificationModel().create(created_by=self.u1,
288 288 subject=u'title', body=u'hi there3',
289 289 recipients=[self.u3, self.u1, self.u2])
290 290 Session.commit()
291 291 notifications = Notification.query().all()
292 292 self.assertTrue(notification in notifications)
293 293
294 294 Notification.delete(notification.notification_id)
295 295 Session.commit()
296 296
297 297 notifications = Notification.query().all()
298 298 self.assertFalse(notification in notifications)
299 299
300 300 un = UserNotification.query().filter(UserNotification.notification
301 301 == notification).all()
302 302 self.assertEqual(un, [])
303 303
304 304 def test_delete_association(self):
305 305
306 306 self.assertEqual([], Notification.query().all())
307 307 self.assertEqual([], UserNotification.query().all())
308 308
309 309 notification = NotificationModel().create(created_by=self.u1,
310 310 subject=u'title', body=u'hi there3',
311 311 recipients=[self.u3, self.u1, self.u2])
312 312 Session.commit()
313 313
314 314 unotification = UserNotification.query()\
315 315 .filter(UserNotification.notification ==
316 316 notification)\
317 317 .filter(UserNotification.user_id == self.u3)\
318 318 .scalar()
319 319
320 320 self.assertEqual(unotification.user_id, self.u3)
321 321
322 322 NotificationModel().delete(self.u3,
323 323 notification.notification_id)
324 324 Session.commit()
325 325
326 326 u3notification = UserNotification.query()\
327 327 .filter(UserNotification.notification ==
328 328 notification)\
329 329 .filter(UserNotification.user_id == self.u3)\
330 330 .scalar()
331 331
332 332 self.assertEqual(u3notification, None)
333 333
334 334 # notification object is still there
335 335 self.assertEqual(Notification.query().all(), [notification])
336 336
337 337 #u1 and u2 still have assignments
338 338 u1notification = UserNotification.query()\
339 339 .filter(UserNotification.notification ==
340 340 notification)\
341 341 .filter(UserNotification.user_id == self.u1)\
342 342 .scalar()
343 343 self.assertNotEqual(u1notification, None)
344 344 u2notification = UserNotification.query()\
345 345 .filter(UserNotification.notification ==
346 346 notification)\
347 347 .filter(UserNotification.user_id == self.u2)\
348 348 .scalar()
349 349 self.assertNotEqual(u2notification, None)
350 350
351 351 def test_notification_counter(self):
352 352 self._clean_notifications()
353 353 self.assertEqual([], Notification.query().all())
354 354 self.assertEqual([], UserNotification.query().all())
355 355
356 356 NotificationModel().create(created_by=self.u1,
357 357 subject=u'title', body=u'hi there_delete',
358 358 recipients=[self.u3, self.u1])
359 359 Session.commit()
360 360
361 361 self.assertEqual(NotificationModel()
362 362 .get_unread_cnt_for_user(self.u1), 1)
363 363 self.assertEqual(NotificationModel()
364 364 .get_unread_cnt_for_user(self.u2), 0)
365 365 self.assertEqual(NotificationModel()
366 366 .get_unread_cnt_for_user(self.u3), 1)
367 367
368 368 notification = NotificationModel().create(created_by=self.u1,
369 369 subject=u'title', body=u'hi there3',
370 370 recipients=[self.u3, self.u1, self.u2])
371 371 Session.commit()
372 372
373 373 self.assertEqual(NotificationModel()
374 374 .get_unread_cnt_for_user(self.u1), 2)
375 375 self.assertEqual(NotificationModel()
376 376 .get_unread_cnt_for_user(self.u2), 1)
377 377 self.assertEqual(NotificationModel()
378 378 .get_unread_cnt_for_user(self.u3), 2)
379 379
380 380
381 381 class TestUsers(unittest.TestCase):
382 382
383 383 def __init__(self, methodName='runTest'):
384 384 super(TestUsers, self).__init__(methodName=methodName)
385 385
386 386 def setUp(self):
387 387 self.u1 = UserModel().create_or_update(username=u'u1',
388 388 password=u'qweqwe',
389 389 email=u'u1@rhodecode.org',
390 390 name=u'u1', lastname=u'u1')
391 391
392 392 def tearDown(self):
393 393 perm = Permission.query().all()
394 394 for p in perm:
395 395 UserModel().revoke_perm(self.u1, p)
396 396
397 397 UserModel().delete(self.u1)
398 398 Session.commit()
399 399
400 400 def test_add_perm(self):
401 401 perm = Permission.query().all()[0]
402 402 UserModel().grant_perm(self.u1, perm)
403 403 Session.commit()
404 404 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
405 405
406 406 def test_has_perm(self):
407 407 perm = Permission.query().all()
408 408 for p in perm:
409 409 has_p = UserModel().has_perm(self.u1, p)
410 410 self.assertEqual(False, has_p)
411 411
412 412 def test_revoke_perm(self):
413 413 perm = Permission.query().all()[0]
414 414 UserModel().grant_perm(self.u1, perm)
415 415 Session.commit()
416 416 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
417 417
418 418 #revoke
419 419 UserModel().revoke_perm(self.u1, perm)
420 420 Session.commit()
421 421 self.assertEqual(UserModel().has_perm(self.u1, perm), False)
422 422
423 423
424 424 class TestPermissions(unittest.TestCase):
425 425 def __init__(self, methodName='runTest'):
426 426 super(TestPermissions, self).__init__(methodName=methodName)
427 427
428 428 def setUp(self):
429 429 self.u1 = UserModel().create_or_update(
430 430 username=u'u1', password=u'qweqwe',
431 431 email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
432 432 )
433 self.u2 = UserModel().create_or_update(
434 username=u'u2', password=u'qweqwe',
435 email=u'u2@rhodecode.org', name=u'u2', lastname=u'u2'
436 )
437 self.anon = User.get_by_username('default')
433 438 self.a1 = UserModel().create_or_update(
434 439 username=u'a1', password=u'qweqwe',
435 440 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
436 441 )
437 442 Session.commit()
438 443
439 444 def tearDown(self):
445 if hasattr(self, 'test_repo'):
446 RepoModel().delete(repo=self.test_repo)
440 447 UserModel().delete(self.u1)
448 UserModel().delete(self.u2)
441 449 UserModel().delete(self.a1)
442 450 if hasattr(self, 'g1'):
443 451 ReposGroupModel().delete(self.g1.group_id)
444 452 if hasattr(self, 'g2'):
445 453 ReposGroupModel().delete(self.g2.group_id)
446 454
447 455 if hasattr(self, 'ug1'):
448 456 UsersGroupModel().delete(self.ug1, force=True)
449 457
450 458 Session.commit()
451 459
452 460 def test_default_perms_set(self):
453 461 u1_auth = AuthUser(user_id=self.u1.user_id)
454 462 perms = {
455 463 'repositories_groups': {},
456 464 'global': set([u'hg.create.repository', u'repository.read',
457 465 u'hg.register.manual_activate']),
458 466 'repositories': {u'vcs_test_hg': u'repository.read'}
459 467 }
460 468 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
461 469 perms['repositories'][HG_REPO])
462 470 new_perm = 'repository.write'
463 471 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
464 472 Session.commit()
465 473
466 474 u1_auth = AuthUser(user_id=self.u1.user_id)
467 475 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm)
468 476
469 477 def test_default_admin_perms_set(self):
470 478 a1_auth = AuthUser(user_id=self.a1.user_id)
471 479 perms = {
472 480 'repositories_groups': {},
473 481 'global': set([u'hg.admin']),
474 482 'repositories': {u'vcs_test_hg': u'repository.admin'}
475 483 }
476 484 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
477 485 perms['repositories'][HG_REPO])
478 486 new_perm = 'repository.write'
479 487 RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm)
480 488 Session.commit()
481 489 # cannot really downgrade admins permissions !? they still get's set as
482 490 # admin !
483 491 u1_auth = AuthUser(user_id=self.a1.user_id)
484 492 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
485 493 perms['repositories'][HG_REPO])
486 494
487 495 def test_default_group_perms(self):
488 496 self.g1 = _make_group('test1', skip_if_exists=True)
489 497 self.g2 = _make_group('test2', skip_if_exists=True)
490 498 u1_auth = AuthUser(user_id=self.u1.user_id)
491 499 perms = {
492 500 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
493 501 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
494 502 'repositories': {u'vcs_test_hg': u'repository.read'}
495 503 }
496 504 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
497 505 perms['repositories'][HG_REPO])
498 506 self.assertEqual(u1_auth.permissions['repositories_groups'],
499 507 perms['repositories_groups'])
500 508
501 509 def test_default_admin_group_perms(self):
502 510 self.g1 = _make_group('test1', skip_if_exists=True)
503 511 self.g2 = _make_group('test2', skip_if_exists=True)
504 512 a1_auth = AuthUser(user_id=self.a1.user_id)
505 513 perms = {
506 514 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
507 515 'global': set(['hg.admin']),
508 516 'repositories': {u'vcs_test_hg': 'repository.admin'}
509 517 }
510 518
511 519 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
512 520 perms['repositories'][HG_REPO])
513 521 self.assertEqual(a1_auth.permissions['repositories_groups'],
514 522 perms['repositories_groups'])
515 523
516 524 def test_propagated_permission_from_users_group(self):
517 525 # make group
518 526 self.ug1 = UsersGroupModel().create('G1')
519 527 # add user to group
520 528 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
521 529
522 530 # set permission to lower
523 531 new_perm = 'repository.none'
524 532 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
525 533 Session.commit()
526 534 u1_auth = AuthUser(user_id=self.u1.user_id)
527 535 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
528 536 new_perm)
529 537
530 538 # grant perm for group this should override permission from user
531 539 new_perm = 'repository.write'
532 540 RepoModel().grant_users_group_permission(repo=HG_REPO,
533 541 group_name=self.ug1,
534 542 perm=new_perm)
535 543 # check perms
536 544 u1_auth = AuthUser(user_id=self.u1.user_id)
537 545 perms = {
538 546 'repositories_groups': {},
539 547 'global': set([u'hg.create.repository', u'repository.read',
540 548 u'hg.register.manual_activate']),
541 549 'repositories': {u'vcs_test_hg': u'repository.read'}
542 550 }
543 551 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
544 552 new_perm)
545 553 self.assertEqual(u1_auth.permissions['repositories_groups'],
546 554 perms['repositories_groups'])
547 555
548 556 def test_propagated_permission_from_users_group_lower_weight(self):
549 557 # make group
550 558 self.ug1 = UsersGroupModel().create('G1')
551 559 # add user to group
552 560 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
553 561
554 562 # set permission to lower
555 563 new_perm_h = 'repository.write'
556 564 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
557 565 perm=new_perm_h)
558 566 Session.commit()
559 567 u1_auth = AuthUser(user_id=self.u1.user_id)
560 568 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
561 569 new_perm_h)
562 570
563 571 # grant perm for group this should NOT override permission from user
564 572 # since it's lower than granted
565 573 new_perm_l = 'repository.read'
566 574 RepoModel().grant_users_group_permission(repo=HG_REPO,
567 575 group_name=self.ug1,
568 576 perm=new_perm_l)
569 577 # check perms
570 578 u1_auth = AuthUser(user_id=self.u1.user_id)
571 579 perms = {
572 580 'repositories_groups': {},
573 581 'global': set([u'hg.create.repository', u'repository.read',
574 582 u'hg.register.manual_activate']),
575 583 'repositories': {u'vcs_test_hg': u'repository.write'}
576 584 }
577 585 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
578 586 new_perm_h)
579 587 self.assertEqual(u1_auth.permissions['repositories_groups'],
580 588 perms['repositories_groups'])
589
590 def test_repo_in_group_permissions(self):
591 self.g1 = _make_group('group1', skip_if_exists=True)
592 self.g2 = _make_group('group2', skip_if_exists=True)
593 Session.commit()
594 # both perms should be read !
595 u1_auth = AuthUser(user_id=self.u1.user_id)
596 self.assertEqual(u1_auth.permissions['repositories_groups'],
597 {u'group1': u'group.read', u'group2': u'group.read'})
598
599 a1_auth = AuthUser(user_id=self.anon.user_id)
600 self.assertEqual(a1_auth.permissions['repositories_groups'],
601 {u'group1': u'group.read', u'group2': u'group.read'})
602
603 #Change perms to none for both groups
604 ReposGroupModel().grant_user_permission(repos_group=self.g1,
605 user=self.anon,
606 perm='group.none')
607 ReposGroupModel().grant_user_permission(repos_group=self.g2,
608 user=self.anon,
609 perm='group.none')
610
611 u1_auth = AuthUser(user_id=self.u1.user_id)
612 self.assertEqual(u1_auth.permissions['repositories_groups'],
613 {u'group1': u'group.none', u'group2': u'group.none'})
614
615 a1_auth = AuthUser(user_id=self.anon.user_id)
616 self.assertEqual(a1_auth.permissions['repositories_groups'],
617 {u'group1': u'group.none', u'group2': u'group.none'})
618
619 # add repo to group
620 form_data = {
621 'repo_name':HG_REPO,
622 'repo_name_full':os.path.join(self.g1.group_name,HG_REPO),
623 'repo_type':'hg',
624 'clone_uri':'',
625 'repo_group':self.g1.group_id,
626 'description':'desc',
627 'private':False
628 }
629 self.test_repo = RepoModel().create(form_data, cur_user=self.u1)
630 Session.commit()
631
632 u1_auth = AuthUser(user_id=self.u1.user_id)
633 self.assertEqual(u1_auth.permissions['repositories_groups'],
634 {u'group1': u'group.none', u'group2': u'group.none'})
635
636 a1_auth = AuthUser(user_id=self.anon.user_id)
637 self.assertEqual(a1_auth.permissions['repositories_groups'],
638 {u'group1': u'group.none', u'group2': u'group.none'})
639
640 #grant permission for u2 !
641 ReposGroupModel().grant_user_permission(repos_group=self.g1,
642 user=self.u2,
643 perm='group.read')
644 ReposGroupModel().grant_user_permission(repos_group=self.g2,
645 user=self.u2,
646 perm='group.read')
647 Session.commit()
648 self.assertNotEqual(self.u1, self.u2)
649 #u1 and anon should have not change perms while u2 should !
650 u1_auth = AuthUser(user_id=self.u1.user_id)
651 self.assertEqual(u1_auth.permissions['repositories_groups'],
652 {u'group1': u'group.none', u'group2': u'group.none'})
653
654 u2_auth = AuthUser(user_id=self.u2.user_id)
655 self.assertEqual(u2_auth.permissions['repositories_groups'],
656 {u'group1': u'group.read', u'group2': u'group.read'})
657
658 a1_auth = AuthUser(user_id=self.anon.user_id)
659 self.assertEqual(a1_auth.permissions['repositories_groups'],
660 {u'group1': u'group.none', u'group2': u'group.none'})
General Comments 0
You need to be logged in to leave comments. Login now