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