##// END OF EJS Templates
"Users groups" is grammatically incorrect English - rename to "user groups"...
Mads Kiilerich -
r3410:5f1850e4 beta
parent child Browse files
Show More
@@ -1,179 +1,179 b''
1 1 =========
2 2 RhodeCode
3 3 =========
4 4
5 5 About
6 6 -----
7 7
8 8 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
9 9 with a built in push/pull server, full text search and code-review.
10 10 It works on http/https and has a built in permission/authentication system with
11 11 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
12 12 simple API so it's easy integrable with existing external systems.
13 13
14 14 RhodeCode is similar in some respects to github_ or bitbucket_,
15 15 however RhodeCode can be run as standalone hosted application on your own server.
16 16 It is open source and donation ware and focuses more on providing a customized,
17 17 self administered interface for Mercurial_ and GIT_ repositories.
18 18 RhodeCode works on \*nix systems and Windows it is powered by a vcs_ library
19 19 that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple
20 20 different version control systems.
21 21
22 22 RhodeCode uses `PEP386 versioning <http://www.python.org/dev/peps/pep-0386/>`_
23 23
24 24 Installation
25 25 ------------
26 26 Stable releases of RhodeCode are best installed via::
27 27
28 28 easy_install rhodecode
29 29
30 30 Or::
31 31
32 32 pip install rhodecode
33 33
34 34 Detailed instructions and links may be found on the Installation page.
35 35
36 36 Please visit http://packages.python.org/RhodeCode/installation.html for
37 37 more details
38 38
39 39 RhodeCode demo
40 40 --------------
41 41
42 42 http://demo.rhodecode.org
43 43
44 44 The default access is anonymous but you can login to an administrative account
45 45 using the following credentials:
46 46
47 47 - username: demo
48 48 - password: demo12
49 49
50 50 Source code
51 51 -----------
52 52
53 53 The latest sources can be obtained from official RhodeCode instance
54 54 https://secure.rhodecode.org
55 55
56 56
57 57 MIRRORS:
58 58
59 59 Issue tracker and sources at bitbucket_
60 60
61 61 http://bitbucket.org/marcinkuzminski/rhodecode
62 62
63 63 Sources at github_
64 64
65 65 https://github.com/marcinkuzminski/rhodecode
66 66
67 67
68 68 RhodeCode Features
69 69 ------------------
70 70
71 71 - Has its own middleware to handle mercurial_ and git_ protocol requests.
72 72 Each request is authenticated and logged together with IP address.
73 73 - Build for speed and performance. You can make multiple pulls/pushes simultaneous.
74 74 Proven to work with 1000s of repositories and users
75 75 - Supports http/https, LDAP, AD, proxy-pass authentication.
76 76 - Full permissions (private/read/write/admin) together with IP restrictions for each repository,
77 77 additional explicit forking and repository creation permissions.
78 - Users groups for easier permission management
78 - User groups for easier permission management
79 79 - Repository groups let you group repos and manage them easier.
80 80 - Users can fork other users repos, and compare them at any time.
81 81 - Integrates easily with other systems, with custom created mappers you can connect it to almost
82 82 any issue tracker, and with an JSON-RPC API you can make much more
83 83 - Build in commit-api let's you add, edit and commit files right from RhodeCode
84 84 web interface using simple editor or upload binary files using simple form.
85 85 - Powerfull pull-request driven review system with inline commenting,
86 86 changeset statuses, and notification system.
87 87 - Importing and syncing repositories from remote locations for GIT_, Mercurial_ and SVN.
88 88 - Mako templates let's you customize the look and feel of the application.
89 89 - Beautiful diffs, annotations and source code browsing all colored by pygments.
90 90 Raw diffs are made in git-diff format for both VCS systems, including GIT_ binary-patches
91 91 - Mercurial_ and Git_ DAG graphs and yui-flot powered graphs with zooming and statistics
92 92 to track activity for repositories
93 93 - Admin interface with user/permission management. Admin activity journal, logs
94 94 pulls, pushes, forks, registrations and other actions made by all users.
95 95 - Server side forks. It is possible to fork a project and modify it freely
96 96 without breaking the main repository.
97 97 - rst and markdown README support for repositories.
98 98 - Full text search powered by Whoosh on the source files, commit messages, and file names.
99 99 Build in indexing daemons, with optional incremental index build
100 100 (no external search servers required all in one application)
101 101 - Setup project descriptions/tags and info inside built in db for easy, non
102 102 file-system operations.
103 103 - Intelligent cache with invalidation after push or project change, provides
104 104 high performance and always up to date data.
105 105 - RSS / Atom feeds, gravatar support, downloadable sources as zip/tar/gz
106 106 - Optional async tasks for speed and performance using celery_
107 107 - Backup scripts can do backup of whole app and send it over scp to desired
108 108 location
109 109 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
110 110
111 111
112 112 Incoming / Plans
113 113 ----------------
114 114
115 115 - Finer granular permissions per branch, or subrepo
116 116 - Web based merges for pull requests
117 117 - Tracking history for each lines in files
118 118 - Simple issue tracker
119 119 - SSH based authentication with server side key management
120 120 - Commit based built in wiki system
121 121 - Gist server
122 122 - More statistics and graph (global annotation + some more statistics)
123 123 - Other advancements as development continues (or you can of course make
124 124 additions and or requests)
125 125
126 126 License
127 127 -------
128 128
129 129 ``RhodeCode`` is released under the GPLv3 license.
130 130
131 131
132 132 Getting help
133 133 ------------
134 134
135 135 Listed bellow are various support resources that should help.
136 136
137 137 .. note::
138 138
139 139 Please try to read the documentation before posting any issues, especially
140 140 the **troubleshooting section**
141 141
142 142 - Join the `Google group <http://groups.google.com/group/rhodecode>`_ and ask
143 143 any questions.
144 144
145 145 - Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
146 146
147 147 - Join #rhodecode on FreeNode (irc.freenode.net)
148 148 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
149 149
150 150 - You can also follow me on twitter **@marcinkuzminski** where i often post some
151 151 news about RhodeCode
152 152
153 153
154 154 Online documentation
155 155 --------------------
156 156
157 157 Online documentation for the current version of RhodeCode is available at
158 158 - http://packages.python.org/RhodeCode/
159 159 - http://rhodecode.readthedocs.org/en/latest/index.html
160 160
161 161 You may also build the documentation for yourself - go into ``docs/`` and run::
162 162
163 163 make html
164 164
165 165 (You need to have sphinx_ installed to build the documentation. If you don't
166 166 have sphinx_ installed you can install it via the command:
167 167 ``easy_install sphinx``)
168 168
169 169 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
170 170 .. _python: http://www.python.org/
171 171 .. _sphinx: http://sphinx.pocoo.org/
172 172 .. _mercurial: http://mercurial.selenic.com/
173 173 .. _bitbucket: http://bitbucket.org/
174 174 .. _github: http://github.com/
175 175 .. _subversion: http://subversion.tigris.org/
176 176 .. _git: http://git-scm.com/
177 177 .. _celery: http://celeryproject.org/
178 178 .. _Sphinx: http://sphinx.pocoo.org/
179 179 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,981 +1,981 b''
1 1 .. _api:
2 2
3 3 ===
4 4 API
5 5 ===
6 6
7 7
8 8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 9 There's a single schema for calling all api methods. API is implemented
10 10 with JSON protocol both ways. An url to send API request to RhodeCode is
11 11 <your_server>/_admin/api
12 12
13 13 API ACCESS FOR WEB VIEWS
14 14 ++++++++++++++++++++++++
15 15
16 16 API access can also be turned on for each web view in RhodeCode that is
17 17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 19 After this change, a rhodecode view can be accessed without login by adding a
20 20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 21 enabled on RSS/ATOM feed views.
22 22
23 23
24 24 API ACCESS
25 25 ++++++++++
26 26
27 27 All clients are required to send JSON-RPC spec JSON data::
28 28
29 29 {
30 30 "id:"<id>",
31 31 "api_key":"<api_key>",
32 32 "method":"<method_name>",
33 33 "args":{"<arg_key>":"<arg_val>"}
34 34 }
35 35
36 36 Example call for autopulling remotes repos using curl::
37 37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
38 38
39 39 Simply provide
40 40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 41 - *api_key* for access and permission validation.
42 42 - *method* is name of method to call
43 43 - *args* is an key:value list of arguments to pass to method
44 44
45 45 .. note::
46 46
47 47 api_key can be found in your user account page
48 48
49 49
50 50 RhodeCode API will return always a JSON-RPC response::
51 51
52 52 {
53 53 "id":<id>, # matching id sent by request
54 54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 55 "error": "null"|<error_message> # JSON formatted error (if any)
56 56 }
57 57
58 58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 59 calling api *error* key from response will contain failure description
60 60 and result will be null.
61 61
62 62
63 63 API CLIENT
64 64 ++++++++++
65 65
66 66 From version 1.4 RhodeCode adds a script that allows to easily
67 67 communicate with API. After installing RhodeCode a `rhodecode-api` script
68 68 will be available.
69 69
70 70 To get started quickly simply run::
71 71
72 72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
73 73
74 74 This will create a file named .config in the directory you executed it storing
75 75 json config file with credentials. You can skip this step and always provide
76 76 both of the arguments to be able to communicate with server
77 77
78 78
79 79 after that simply run any api command for example get_repo::
80 80
81 81 rhodecode-api get_repo
82 82
83 83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
84 84 rhodecode said:
85 85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
86 86 'id': 75,
87 87 'result': None}
88 88
89 89 Ups looks like we forgot to add an argument
90 90
91 91 Let's try again now giving the repoid as parameters::
92 92
93 93 rhodecode-api get_repo repoid:rhodecode
94 94
95 95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
96 96 rhodecode said:
97 97 {'error': None,
98 98 'id': 39,
99 99 'result': <json data...>}
100 100
101 101
102 102
103 103 API METHODS
104 104 +++++++++++
105 105
106 106
107 107 pull
108 108 ----
109 109
110 110 Pulls given repo from remote location. Can be used to automatically keep
111 111 remote repos up to date. This command can be executed only using api_key
112 112 belonging to user with admin rights
113 113
114 114 INPUT::
115 115
116 116 id : <id_for_response>
117 117 api_key : "<api_key>"
118 118 method : "pull"
119 119 args : {
120 120 "repoid" : "<reponame or repo_id>"
121 121 }
122 122
123 123 OUTPUT::
124 124
125 125 id : <id_given_in_input>
126 126 result : "Pulled from `<reponame>`"
127 127 error : null
128 128
129 129
130 130 rescan_repos
131 131 ------------
132 132
133 133 Dispatch rescan repositories action. If remove_obsolete is set
134 134 RhodeCode will delete repos that are in database but not in the filesystem.
135 135 This command can be executed only using api_key belonging to user with admin
136 136 rights.
137 137
138 138 INPUT::
139 139
140 140 id : <id_for_response>
141 141 api_key : "<api_key>"
142 142 method : "rescan_repos"
143 143 args : {
144 144 "remove_obsolete" : "<boolean = Optional(False)>"
145 145 }
146 146
147 147 OUTPUT::
148 148
149 149 id : <id_given_in_input>
150 150 result : "{'added': [<list of names of added repos>],
151 151 'removed': [<list of names of removed repos>]}"
152 152 error : null
153 153
154 154
155 155 invalidate_cache
156 156 ----------------
157 157
158 158 Invalidate cache for repository.
159 159 This command can be executed only using api_key belonging to user with admin
160 160 rights or regular user that have write or admin or write access to repository.
161 161
162 162 INPUT::
163 163
164 164 id : <id_for_response>
165 165 api_key : "<api_key>"
166 166 method : "invalidate_cache"
167 167 args : {
168 168 "repoid" : "<reponame or repo_id>"
169 169 }
170 170
171 171 OUTPUT::
172 172
173 173 id : <id_given_in_input>
174 174 result : "Cache for repository `<reponame>` was invalidated: invalidated cache keys: <list_of_cache_keys>"
175 175 error : null
176 176
177 177 lock
178 178 ----
179 179
180 180 Set locking state on given repository by given user. If userid param is skipped
181 181 , then it is set to id of user whos calling this method.
182 182 This command can be executed only using api_key belonging to user with admin
183 183 rights or regular user that have admin or write access to repository.
184 184
185 185 INPUT::
186 186
187 187 id : <id_for_response>
188 188 api_key : "<api_key>"
189 189 method : "lock"
190 190 args : {
191 191 "repoid" : "<reponame or repo_id>"
192 192 "userid" : "<user_id or username = Optional(=apiuser)>",
193 193 "locked" : "<bool true|false>"
194 194 }
195 195
196 196 OUTPUT::
197 197
198 198 id : <id_given_in_input>
199 199 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
200 200 error : null
201 201
202 202
203 203 show_ip
204 204 -------
205 205
206 206 Shows IP address as seen from RhodeCode server, together with all
207 207 defined IP addresses for given user.
208 208 This command can be executed only using api_key belonging to user with admin
209 209 rights.
210 210
211 211 INPUT::
212 212
213 213 id : <id_for_response>
214 214 api_key : "<api_key>"
215 215 method : "show_ip"
216 216 args : {
217 217 "userid" : "<user_id or username>",
218 218 }
219 219
220 220 OUTPUT::
221 221
222 222 id : <id_given_in_input>
223 223 result : {
224 224 "ip_addr_server": <ip_from_clien>",
225 225 "user_ips": [
226 226 {
227 227 "ip_addr": "<ip_with_mask>",
228 228 "ip_range": ["<start_ip>", "<end_ip>"],
229 229 },
230 230 ...
231 231 ]
232 232 }
233 233
234 234 error : null
235 235
236 236
237 237 get_user
238 238 --------
239 239
240 240 Get's an user by username or user_id, Returns empty result if user is not found.
241 241 If userid param is skipped it is set to id of user who is calling this method.
242 242 This command can be executed only using api_key belonging to user with admin
243 243 rights, or regular users that cannot specify different userid than theirs
244 244
245 245
246 246 INPUT::
247 247
248 248 id : <id_for_response>
249 249 api_key : "<api_key>"
250 250 method : "get_user"
251 251 args : {
252 252 "userid" : "<username or user_id Optional(=apiuser)>"
253 253 }
254 254
255 255 OUTPUT::
256 256
257 257 id : <id_given_in_input>
258 258 result: None if user does not exist or
259 259 {
260 260 "user_id" : "<user_id>",
261 261 "api_key" : "<api_key>",
262 262 "username" : "<username>",
263 263 "firstname": "<firstname>",
264 264 "lastname" : "<lastname>",
265 265 "email" : "<email>",
266 266 "emails": "<list_of_all_additional_emails>",
267 267 "ip_addresses": "<list_of_ip_addresses_for_user>",
268 268 "active" : "<bool>",
269 269 "admin" :Β  "<bool>",
270 270 "ldap_dn" : "<ldap_dn>",
271 271 "last_login": "<last_login>",
272 272 "permissions": {
273 273 "global": ["hg.create.repository",
274 274 "repository.read",
275 275 "hg.register.manual_activate"],
276 276 "repositories": {"repo1": "repository.none"},
277 277 "repositories_groups": {"Group1": "group.read"}
278 278 },
279 279 }
280 280
281 281 error: null
282 282
283 283
284 284 get_users
285 285 ---------
286 286
287 287 Lists all existing users. This command can be executed only using api_key
288 288 belonging to user with admin rights.
289 289
290 290
291 291 INPUT::
292 292
293 293 id : <id_for_response>
294 294 api_key : "<api_key>"
295 295 method : "get_users"
296 296 args : { }
297 297
298 298 OUTPUT::
299 299
300 300 id : <id_given_in_input>
301 301 result: [
302 302 {
303 303 "user_id" : "<user_id>",
304 304 "username" : "<username>",
305 305 "firstname": "<firstname>",
306 306 "lastname" : "<lastname>",
307 307 "email" : "<email>",
308 308 "emails": "<list_of_all_additional_emails>",
309 309 "ip_addresses": "<list_of_ip_addresses_for_user>",
310 310 "active" : "<bool>",
311 311 "admin" :Β  "<bool>",
312 312 "ldap_dn" : "<ldap_dn>",
313 313 "last_login": "<last_login>",
314 314 },
315 315 …
316 316 ]
317 317 error: null
318 318
319 319
320 320 create_user
321 321 -----------
322 322
323 323 Creates new user. This command can
324 324 be executed only using api_key belonging to user with admin rights.
325 325
326 326
327 327 INPUT::
328 328
329 329 id : <id_for_response>
330 330 api_key : "<api_key>"
331 331 method : "create_user"
332 332 args : {
333 333 "username" : "<username>",
334 334 "email" : "<useremail>",
335 335 "password" : "<password>",
336 336 "firstname" : "<firstname> = Optional(None)",
337 337 "lastname" : "<lastname> = Optional(None)",
338 338 "active" : "<bool> = Optional(True)",
339 339 "admin" : "<bool> = Optional(False)",
340 340 "ldap_dn" : "<ldap_dn> = Optional(None)"
341 341 }
342 342
343 343 OUTPUT::
344 344
345 345 id : <id_given_in_input>
346 346 result: {
347 347 "msg" : "created new user `<username>`",
348 348 "user": {
349 349 "user_id" : "<user_id>",
350 350 "username" : "<username>",
351 351 "firstname": "<firstname>",
352 352 "lastname" : "<lastname>",
353 353 "email" : "<email>",
354 354 "emails": "<list_of_all_additional_emails>",
355 355 "active" : "<bool>",
356 356 "admin" :Β  "<bool>",
357 357 "ldap_dn" : "<ldap_dn>",
358 358 "last_login": "<last_login>",
359 359 },
360 360 }
361 361 error: null
362 362
363 363
364 364 update_user
365 365 -----------
366 366
367 367 updates given user if such user exists. This command can
368 368 be executed only using api_key belonging to user with admin rights.
369 369
370 370
371 371 INPUT::
372 372
373 373 id : <id_for_response>
374 374 api_key : "<api_key>"
375 375 method : "update_user"
376 376 args : {
377 377 "userid" : "<user_id or username>",
378 378 "username" : "<username> = Optional(None)",
379 379 "email" : "<useremail> = Optional(None)",
380 380 "password" : "<password> = Optional(None)",
381 381 "firstname" : "<firstname> = Optional(None)",
382 382 "lastname" : "<lastname> = Optional(None)",
383 383 "active" : "<bool> = Optional(None)",
384 384 "admin" : "<bool> = Optional(None)",
385 385 "ldap_dn" : "<ldap_dn> = Optional(None)"
386 386 }
387 387
388 388 OUTPUT::
389 389
390 390 id : <id_given_in_input>
391 391 result: {
392 392 "msg" : "updated user ID:<userid> <username>",
393 393 "user": {
394 394 "user_id" : "<user_id>",
395 395 "username" : "<username>",
396 396 "firstname": "<firstname>",
397 397 "lastname" : "<lastname>",
398 398 "email" : "<email>",
399 399 "emails": "<list_of_all_additional_emails>",
400 400 "active" : "<bool>",
401 401 "admin" :Β  "<bool>",
402 402 "ldap_dn" : "<ldap_dn>",
403 403 "last_login": "<last_login>",
404 404 },
405 405 }
406 406 error: null
407 407
408 408
409 409 delete_user
410 410 -----------
411 411
412 412
413 413 deletes givenuser if such user exists. This command can
414 414 be executed only using api_key belonging to user with admin rights.
415 415
416 416
417 417 INPUT::
418 418
419 419 id : <id_for_response>
420 420 api_key : "<api_key>"
421 421 method : "delete_user"
422 422 args : {
423 423 "userid" : "<user_id or username>",
424 424 }
425 425
426 426 OUTPUT::
427 427
428 428 id : <id_given_in_input>
429 429 result: {
430 430 "msg" : "deleted user ID:<userid> <username>",
431 431 "user": null
432 432 }
433 433 error: null
434 434
435 435
436 436 get_users_group
437 437 ---------------
438 438
439 Gets an existing users group. This command can be executed only using api_key
439 Gets an existing user group. This command can be executed only using api_key
440 440 belonging to user with admin rights.
441 441
442 442
443 443 INPUT::
444 444
445 445 id : <id_for_response>
446 446 api_key : "<api_key>"
447 447 method : "get_users_group"
448 448 args : {
449 "usersgroupid" : "<users group id or name>"
449 "usersgroupid" : "<user group id or name>"
450 450 }
451 451
452 452 OUTPUT::
453 453
454 454 id : <id_given_in_input>
455 455 result : None if group not exist
456 456 {
457 457 "users_group_id" : "<id>",
458 458 "group_name" : "<groupname>",
459 459 "active": "<bool>",
460 460 "members" : [
461 461 {
462 462 "user_id" : "<user_id>",
463 463 "username" : "<username>",
464 464 "firstname": "<firstname>",
465 465 "lastname" : "<lastname>",
466 466 "email" : "<email>",
467 467 "emails": "<list_of_all_additional_emails>",
468 468 "active" : "<bool>",
469 469 "admin" :Β  "<bool>",
470 470 "ldap_dn" : "<ldap_dn>",
471 471 "last_login": "<last_login>",
472 472 },
473 473 …
474 474 ]
475 475 }
476 476 error : null
477 477
478 478
479 479 get_users_groups
480 480 ----------------
481 481
482 Lists all existing users groups. This command can be executed only using
482 Lists all existing user groups. This command can be executed only using
483 483 api_key belonging to user with admin rights.
484 484
485 485
486 486 INPUT::
487 487
488 488 id : <id_for_response>
489 489 api_key : "<api_key>"
490 490 method : "get_users_groups"
491 491 args : { }
492 492
493 493 OUTPUT::
494 494
495 495 id : <id_given_in_input>
496 496 result : [
497 497 {
498 498 "users_group_id" : "<id>",
499 499 "group_name" : "<groupname>",
500 500 "active": "<bool>",
501 501 },
502 502 …
503 503 ]
504 504 error : null
505 505
506 506
507 507 create_users_group
508 508 ------------------
509 509
510 Creates new users group. This command can be executed only using api_key
510 Creates new user group. This command can be executed only using api_key
511 511 belonging to user with admin rights
512 512
513 513
514 514 INPUT::
515 515
516 516 id : <id_for_response>
517 517 api_key : "<api_key>"
518 518 method : "create_users_group"
519 519 args: {
520 520 "group_name": "<groupname>",
521 521 "active":"<bool> = Optional(True)"
522 522 }
523 523
524 524 OUTPUT::
525 525
526 526 id : <id_given_in_input>
527 527 result: {
528 "msg": "created new users group `<groupname>`",
528 "msg": "created new user group `<groupname>`",
529 529 "users_group": {
530 530 "users_group_id" : "<id>",
531 531 "group_name" : "<groupname>",
532 532 "active": "<bool>",
533 533 },
534 534 }
535 535 error: null
536 536
537 537
538 538 add_user_to_users_group
539 539 -----------------------
540 540
541 Adds a user to a users group. If user exists in that group success will be
541 Adds a user to a user group. If user exists in that group success will be
542 542 `false`. This command can be executed only using api_key
543 543 belonging to user with admin rights
544 544
545 545
546 546 INPUT::
547 547
548 548 id : <id_for_response>
549 549 api_key : "<api_key>"
550 550 method : "add_user_users_group"
551 551 args: {
552 "usersgroupid" : "<users group id or name>",
552 "usersgroupid" : "<user group id or name>",
553 553 "userid" : "<user_id or username>",
554 554 }
555 555
556 556 OUTPUT::
557 557
558 558 id : <id_given_in_input>
559 559 result: {
560 560 "success": True|False # depends on if member is in group
561 "msg": "added member `<username>` to users group `<groupname>` |
561 "msg": "added member `<username>` to user group `<groupname>` |
562 562 User is already in that group"
563 563 }
564 564 error: null
565 565
566 566
567 567 remove_user_from_users_group
568 568 ----------------------------
569 569
570 Removes a user from a users group. If user is not in given group success will
570 Removes a user from a user group. If user is not in given group success will
571 571 be `false`. This command can be executed only
572 572 using api_key belonging to user with admin rights
573 573
574 574
575 575 INPUT::
576 576
577 577 id : <id_for_response>
578 578 api_key : "<api_key>"
579 579 method : "remove_user_from_users_group"
580 580 args: {
581 "usersgroupid" : "<users group id or name>",
581 "usersgroupid" : "<user group id or name>",
582 582 "userid" : "<user_id or username>",
583 583 }
584 584
585 585 OUTPUT::
586 586
587 587 id : <id_given_in_input>
588 588 result: {
589 589 "success": True|False, # depends on if member is in group
590 "msg": "removed member <username> from users group <groupname> |
590 "msg": "removed member <username> from user group <groupname> |
591 591 User wasn't in group"
592 592 }
593 593 error: null
594 594
595 595
596 596 get_repo
597 597 --------
598 598
599 599 Gets an existing repository by it's name or repository_id. Members will return
600 600 either users_group or user associated to that repository. This command can be
601 601 executed only using api_key belonging to user with admin
602 602 rights or regular user that have at least read access to repository.
603 603
604 604
605 605 INPUT::
606 606
607 607 id : <id_for_response>
608 608 api_key : "<api_key>"
609 609 method : "get_repo"
610 610 args: {
611 611 "repoid" : "<reponame or repo_id>"
612 612 }
613 613
614 614 OUTPUT::
615 615
616 616 id : <id_given_in_input>
617 617 result: None if repository does not exist or
618 618 {
619 619 "repo_id" : "<repo_id>",
620 620 "repo_name" : "<reponame>"
621 621 "repo_type" : "<repo_type>",
622 622 "clone_uri" : "<clone_uri>",
623 623 "enable_downloads": "<bool>",
624 624 "enable_locking": "<bool>",
625 625 "enable_statistics": "<bool>",
626 626 "private": "<bool>",
627 627 "created_on" : "<date_time_created>",
628 628 "description" : "<description>",
629 629 "landing_rev": "<landing_rev>",
630 630 "last_changeset": {
631 631 "author": "<full_author>",
632 632 "date": "<date_time_of_commit>",
633 633 "message": "<commit_message>",
634 634 "raw_id": "<raw_id>",
635 635 "revision": "<numeric_revision>",
636 636 "short_id": "<short_id>"
637 637 }
638 638 "owner": "<repo_owner>",
639 639 "fork_of": "<name_of_fork_parent>",
640 640 "members" : [
641 641 {
642 642 "type": "user",
643 643 "user_id" : "<user_id>",
644 644 "username" : "<username>",
645 645 "firstname": "<firstname>",
646 646 "lastname" : "<lastname>",
647 647 "email" : "<email>",
648 648 "emails": "<list_of_all_additional_emails>",
649 649 "active" : "<bool>",
650 650 "admin" :Β  "<bool>",
651 651 "ldap_dn" : "<ldap_dn>",
652 652 "last_login": "<last_login>",
653 653 "permission" : "repository.(read|write|admin)"
654 654 },
655 655 …
656 656 {
657 657 "type": "users_group",
658 658 "id" : "<usersgroupid>",
659 659 "name" : "<usersgroupname>",
660 660 "active": "<bool>",
661 661 "permission" : "repository.(read|write|admin)"
662 662 },
663 663 …
664 664 ]
665 665 "followers": [
666 666 {
667 667 "user_id" : "<user_id>",
668 668 "username" : "<username>",
669 669 "firstname": "<firstname>",
670 670 "lastname" : "<lastname>",
671 671 "email" : "<email>",
672 672 "emails": "<list_of_all_additional_emails>",
673 673 "ip_addresses": "<list_of_ip_addresses_for_user>",
674 674 "active" : "<bool>",
675 675 "admin" :Β  "<bool>",
676 676 "ldap_dn" : "<ldap_dn>",
677 677 "last_login": "<last_login>",
678 678 },
679 679 …
680 680 ]
681 681 }
682 682 error: null
683 683
684 684
685 685 get_repos
686 686 ---------
687 687
688 688 Lists all existing repositories. This command can be executed only using
689 689 api_key belonging to user with admin rights or regular user that have
690 690 admin, write or read access to repository.
691 691
692 692
693 693 INPUT::
694 694
695 695 id : <id_for_response>
696 696 api_key : "<api_key>"
697 697 method : "get_repos"
698 698 args: { }
699 699
700 700 OUTPUT::
701 701
702 702 id : <id_given_in_input>
703 703 result: [
704 704 {
705 705 "repo_id" : "<repo_id>",
706 706 "repo_name" : "<reponame>"
707 707 "repo_type" : "<repo_type>",
708 708 "clone_uri" : "<clone_uri>",
709 709 "private": : "<bool>",
710 710 "created_on" : "<datetimecreated>",
711 711 "description" : "<description>",
712 712 "landing_rev": "<landing_rev>",
713 713 "owner": "<repo_owner>",
714 714 "fork_of": "<name_of_fork_parent>",
715 715 "enable_downloads": "<bool>",
716 716 "enable_locking": "<bool>",
717 717 "enable_statistics": "<bool>",
718 718 },
719 719 …
720 720 ]
721 721 error: null
722 722
723 723
724 724 get_repo_nodes
725 725 --------------
726 726
727 727 returns a list of nodes and it's children in a flat list for a given path
728 728 at given revision. It's possible to specify ret_type to show only `files` or
729 729 `dirs`. This command can be executed only using api_key belonging to user
730 730 with admin rights
731 731
732 732
733 733 INPUT::
734 734
735 735 id : <id_for_response>
736 736 api_key : "<api_key>"
737 737 method : "get_repo_nodes"
738 738 args: {
739 739 "repoid" : "<reponame or repo_id>"
740 740 "revision" : "<revision>",
741 741 "root_path" : "<root_path>",
742 742 "ret_type" : "<ret_type> = Optional('all')"
743 743 }
744 744
745 745 OUTPUT::
746 746
747 747 id : <id_given_in_input>
748 748 result: [
749 749 {
750 750 "name" : "<name>"
751 751 "type" : "<type>",
752 752 },
753 753 …
754 754 ]
755 755 error: null
756 756
757 757
758 758 create_repo
759 759 -----------
760 760
761 761 Creates a repository. If repository name contains "/", all needed repository
762 762 groups will be created. For example "foo/bar/baz" will create groups
763 763 "foo", "bar" (with "foo" as parent), and create "baz" repository with
764 764 "bar" as group. This command can be executed only using api_key belonging to user with admin
765 765 rights or regular user that have create repository permission. Regular users
766 766 cannot specify owner parameter
767 767
768 768
769 769 INPUT::
770 770
771 771 id : <id_for_response>
772 772 api_key : "<api_key>"
773 773 method : "create_repo"
774 774 args: {
775 775 "repo_name" : "<reponame>",
776 776 "owner" : "<onwer_name_or_id = Optional(=apiuser)>",
777 777 "repo_type" : "<repo_type> = Optional('hg')",
778 778 "description" : "<description> = Optional('')",
779 779 "private" : "<bool> = Optional(False)",
780 780 "clone_uri" : "<clone_uri> = Optional(None)",
781 781 "landing_rev" : "<landing_rev> = Optional('tip')",
782 782 "enable_downloads": "<bool> = Optional(False)",
783 783 "enable_locking": "<bool> = Optional(False)",
784 784 "enable_statistics": "<bool> = Optional(False)",
785 785 }
786 786
787 787 OUTPUT::
788 788
789 789 id : <id_given_in_input>
790 790 result: {
791 791 "msg": "Created new repository `<reponame>`",
792 792 "repo": {
793 793 "repo_id" : "<repo_id>",
794 794 "repo_name" : "<reponame>"
795 795 "repo_type" : "<repo_type>",
796 796 "clone_uri" : "<clone_uri>",
797 797 "private": : "<bool>",
798 798 "created_on" : "<datetimecreated>",
799 799 "description" : "<description>",
800 800 "landing_rev": "<landing_rev>",
801 801 "owner": "<username or user_id>",
802 802 "fork_of": "<name_of_fork_parent>",
803 803 "enable_downloads": "<bool>",
804 804 "enable_locking": "<bool>",
805 805 "enable_statistics": "<bool>",
806 806 },
807 807 }
808 808 error: null
809 809
810 810
811 811 fork_repo
812 812 ---------
813 813
814 814 Creates a fork of given repo. In case of using celery this will
815 815 immidiatelly return success message, while fork is going to be created
816 816 asynchronous. This command can be executed only using api_key belonging to
817 817 user with admin rights or regular user that have fork permission, and at least
818 818 read access to forking repository. Regular users cannot specify owner parameter.
819 819
820 820
821 821 INPUT::
822 822
823 823 id : <id_for_response>
824 824 api_key : "<api_key>"
825 825 method : "fork_repo"
826 826 args: {
827 827 "repoid" : "<reponame or repo_id>",
828 828 "fork_name": "<forkname>",
829 829 "owner": "<username or user_id = Optional(=apiuser)>",
830 830 "description": "<description>",
831 831 "copy_permissions": "<bool>",
832 832 "private": "<bool>",
833 833 "landing_rev": "<landing_rev>"
834 834
835 835 }
836 836
837 837 OUTPUT::
838 838
839 839 id : <id_given_in_input>
840 840 result: {
841 841 "msg": "Created fork of `<reponame>` as `<forkname>`",
842 842 "success": true
843 843 }
844 844 error: null
845 845
846 846
847 847 delete_repo
848 848 -----------
849 849
850 850 Deletes a repository. This command can be executed only using api_key belonging to user with admin
851 851 rights or regular user that have admin access to repository.
852 852
853 853
854 854 INPUT::
855 855
856 856 id : <id_for_response>
857 857 api_key : "<api_key>"
858 858 method : "delete_repo"
859 859 args: {
860 860 "repoid" : "<reponame or repo_id>"
861 861 }
862 862
863 863 OUTPUT::
864 864
865 865 id : <id_given_in_input>
866 866 result: {
867 867 "msg": "Deleted repository `<reponame>`",
868 868 "success": true
869 869 }
870 870 error: null
871 871
872 872
873 873 grant_user_permission
874 874 ---------------------
875 875
876 876 Grant permission for user on given repository, or update existing one
877 877 if found. This command can be executed only using api_key belonging to user
878 878 with admin rights.
879 879
880 880
881 881 INPUT::
882 882
883 883 id : <id_for_response>
884 884 api_key : "<api_key>"
885 885 method : "grant_user_permission"
886 886 args: {
887 887 "repoid" : "<reponame or repo_id>"
888 888 "userid" : "<username or user_id>"
889 889 "perm" : "(repository.(none|read|write|admin))",
890 890 }
891 891
892 892 OUTPUT::
893 893
894 894 id : <id_given_in_input>
895 895 result: {
896 896 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
897 897 "success": true
898 898 }
899 899 error: null
900 900
901 901
902 902 revoke_user_permission
903 903 ----------------------
904 904
905 905 Revoke permission for user on given repository. This command can be executed
906 906 only using api_key belonging to user with admin rights.
907 907
908 908
909 909 INPUT::
910 910
911 911 id : <id_for_response>
912 912 api_key : "<api_key>"
913 913 method : "revoke_user_permission"
914 914 args: {
915 915 "repoid" : "<reponame or repo_id>"
916 916 "userid" : "<username or user_id>"
917 917 }
918 918
919 919 OUTPUT::
920 920
921 921 id : <id_given_in_input>
922 922 result: {
923 923 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
924 924 "success": true
925 925 }
926 926 error: null
927 927
928 928
929 929 grant_users_group_permission
930 930 ----------------------------
931 931
932 Grant permission for users group on given repository, or update
932 Grant permission for user group on given repository, or update
933 933 existing one if found. This command can be executed only using
934 934 api_key belonging to user with admin rights.
935 935
936 936
937 937 INPUT::
938 938
939 939 id : <id_for_response>
940 940 api_key : "<api_key>"
941 941 method : "grant_users_group_permission"
942 942 args: {
943 943 "repoid" : "<reponame or repo_id>"
944 "usersgroupid" : "<users group id or name>"
944 "usersgroupid" : "<user group id or name>"
945 945 "perm" : "(repository.(none|read|write|admin))",
946 946 }
947 947
948 948 OUTPUT::
949 949
950 950 id : <id_given_in_input>
951 951 result: {
952 952 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
953 953 "success": true
954 954 }
955 955 error: null
956 956
957 957
958 958 revoke_users_group_permission
959 959 -----------------------------
960 960
961 Revoke permission for users group on given repository.This command can be
961 Revoke permission for user group on given repository.This command can be
962 962 executed only using api_key belonging to user with admin rights.
963 963
964 964 INPUT::
965 965
966 966 id : <id_for_response>
967 967 api_key : "<api_key>"
968 968 method : "revoke_users_group_permission"
969 969 args: {
970 970 "repoid" : "<reponame or repo_id>"
971 "usersgroupid" : "<users group id or name>"
971 "usersgroupid" : "<user group id or name>"
972 972 }
973 973
974 974 OUTPUT::
975 975
976 976 id : <id_given_in_input>
977 977 result: {
978 978 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
979 979 "success": true
980 980 }
981 981 error: null
@@ -1,968 +1,968 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7 1.6.0 (**2013-XX-XX**)
8 8 ----------------------
9 9
10 10 :status: in-progress
11 11 :branch: beta
12 12
13 13 news
14 14 ++++
15 15
16 16 fixes
17 17 +++++
18 18
19 19 1.5.3 (**2013-02-12**)
20 20 ----------------------
21 21
22 22 news
23 23 ++++
24 24
25 25 - IP restrictions now also enabled for IPv6
26 26
27 27 fixes
28 28 +++++
29 29
30 30 - fixed issues with private checkbox not always working
31 31 - fixed #746 unicodeDedode errors on feed controllers
32 32 - fixes issue #756 cleanup repos didn't properly compose paths of repos to be cleaned up.
33 33 - fixed cache invalidation issues together with vcs_full_cache option
34 34 - repo scan should skip directories with starting with '.'
35 35 - fixes for issue #731, update-repoinfo sometimes failed to update data when changesets
36 36 were initial commits
37 37 - recursive mode of setting permission skips private repositories
38 38
39 39 1.5.2 (**2013-01-14**)
40 40 ----------------------
41 41
42 42 news
43 43 ++++
44 44
45 45 - IP restrictions for users. Each user can get a set of whitelist IP+mask for
46 46 extra protection. Useful for buildbots etc.
47 47 - added full last changeset info to lightweight dashboard. lightweight dashboard
48 48 is now fully functional replacement of original dashboard.
49 49 - implemented certain API calls for non-admin users.
50 50 - enabled all Markdown Extra plugins
51 51 - implemented #725 Pull Request View - Show origin repo URL
52 52 - show comments from pull requests into associated changesets
53 53
54 54 fixes
55 55 +++++
56 56
57 57 - update repoinfo script is more failsafe
58 58 - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
59 59 - fixed #691: Notifications for pull requests: move link to top for better
60 60 readability
61 61 - fixed #699: fix missing fork docs for API
62 62 - fixed #693 Opening changeset from pull request fails
63 63 - fixed #710 File view stripping empty lines from beginning and end of file
64 64 - fixed issues with getting repos by path on windows, caused GIT hooks to fail
65 65 - fixed issues with groups paginator on main dashboard
66 66 - improved fetch/pull command for git repos, now pulling all refs
67 67 - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
68 68 when running in a subdir
69 69 - fixed issue #702 API methods without arguments fail when "args":null
70 70 - set the status of changesets initially on pull request. Fixes issues #690 and #587
71 71
72 72 1.5.1 (**2012-12-13**)
73 73 ----------------------
74 74
75 75 news
76 76 ++++
77 77
78 78 - implements #677: Don't allow to close pull requests when they are
79 79 under-review status
80 80 - implemented #670 Implementation of Roles in Pull Request
81 81
82 82 fixes
83 83 +++++
84 84
85 85 - default permissions can get duplicated after migration
86 86 - fixed changeset status labels, they now select radio buttons
87 87 - #682 translation difficult for multi-line text
88 88 - #683 fixed difference between messages about not mapped repositories
89 89 - email: fail nicely when no SMTP server has been configured
90 90
91 91 1.5.0 (**2012-12-12**)
92 92 ----------------------
93 93
94 94 news
95 95 ++++
96 96
97 97 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
98 98 of file renames, copies, change flags and binary files
99 99 - added lightweight dashboard option. ref #500. New version of dashboard
100 100 page that doesn't use any VCS data and is super fast to render. Recommended
101 101 for large amount of repositories.
102 102 - implements #648 write Script for updating last modification time for
103 103 lightweight dashboard
104 104 - implemented compare engine for git repositories.
105 105 - LDAP failover, option to specify multiple servers
106 106 - added Errormator and Sentry support for monitoring RhodeCode
107 107 - implemented #628: Pass server URL to rc-extensions hooks
108 108 - new tooltip implementation - added lazy loading of changesets from journal
109 109 pages. This can significantly improve speed of rendering the page
110 110 - implements #632,added branch/tag/bookmarks info into feeds
111 111 added changeset link to body of message
112 112 - implemented #638 permissions overview to groups
113 113 - implements #636, lazy loading of history and authors to speed up source
114 114 pages rendering
115 115 - implemented #647, option to pass list of default encoding used to
116 116 encode to/decode from unicode
117 117 - added caching layer into RSS/ATOM feeds.
118 118 - basic implementation of cherry picking changesets for pull request, ref #575
119 119 - implemented #661 Add option to include diff in RSS feed
120 120 - implemented file history page for showing detailed changelog for a given file
121 121 - implemented #663 Admin/permission: specify default repogroup perms
122 122 - implemented #379 defaults settings page for creation of repositories, locking
123 123 statistics, downloads, repository type
124 124 - implemented #210 filtering of admin journal based on Whoosh Query language
125 125 - added parents/children links in changeset viewref #650
126 126
127 127 fixes
128 128 +++++
129 129
130 130 - fixed git version checker
131 131 - #586 patched basic auth handler to fix issues with git behind proxy
132 132 - #589 search urlgenerator didn't properly escape special characters
133 133 - fixed issue #614 Include repo name in delete confirmation dialog
134 134 - fixed #623: Lang meta-tag doesn't work with C#/C++
135 135 - fixes #612 Double quotes to Single quotes result in bad html in diff
136 136 - fixes #630 git statistics do too much work making them slow.
137 137 - fixes #625 Git-Tags are not displayed in Shortlog
138 138 - fix for issue #602, enforce str when setting mercurial UI object.
139 139 When this is used together with mercurial internal translation system
140 140 it can lead to UnicodeDecodeErrors
141 141 - fixes #645 Fix git handler when doing delete remote branch
142 142 - implements #649 added two seperate method for author and commiter to VCS
143 143 changeset class switch author for git backed to be the real author not commiter
144 144 - fix issue #504 RhodeCode is showing different versions of README on
145 145 different summary page loads
146 146 - implemented #658 Changing username in LDAP-Mode should not be allowed.
147 147 - fixes #652 switch to generator approach when doing file annotation to prevent
148 148 huge memory consumption
149 149 - fixes #666 move lockkey path location to cache_dir to ensure this path is
150 150 always writable for rhodecode server
151 151 - many more small fixes and improvements
152 152 - fixed issues with recursive scans on removed repositories that could take
153 153 long time on instance start
154 154
155 155 1.4.4 (**2012-10-08**)
156 156 ----------------------
157 157
158 158 news
159 159 ++++
160 160
161 161 - obfuscate db password in logs for engine connection string
162 162 - #574 Show pull request status also in shortlog (if any)
163 163 - remember selected tab in my account page
164 164 - Bumped mercurial version to 2.3.2
165 165 - #595 rcextension hook for repository delete
166 166
167 167 fixes
168 168 +++++
169 169
170 170 - Add git version detection to warn users that Git used in system is to
171 171 old. Ref #588 - also show git version in system details in settings page
172 172 - fixed files quick filter links
173 173 - #590 Add GET flag that controls the way the diff are generated, for pull
174 174 requests we want to use non-bundle based diffs, That are far better for
175 175 doing code reviews. The /compare url still uses bundle compare for full
176 176 comparison including the incoming changesets
177 177 - Fixed #585, checks for status of revision where to strict, and made
178 178 opening pull request with those revision impossible due to previously set
179 179 status. Checks now are made also for the repository.
180 180 - fixes #591 git backend was causing encoding errors when handling binary
181 181 files - added a test case for VCS lib tests
182 182 - fixed #597 commits in future get negative age.
183 183 - fixed #598 API docs methods had wrong members parameter as returned data
184 184
185 185 1.4.3 (**2012-09-28**)
186 186 ----------------------
187 187
188 188 news
189 189 ++++
190 190
191 191 - #558 Added config file to hooks extra data
192 192 - bumped mercurial version to 2.3.1
193 193 - #518 added possibility of specifying multiple patterns for issues
194 194 - update codemirror to latest version
195 195
196 196 fixes
197 197 +++++
198 198
199 - fixed #570 explicit users group permissions can overwrite owner permissions
199 - fixed #570 explicit user group permissions can overwrite owner permissions
200 200 - fixed #578 set proper PATH with current Python for Git
201 201 hooks to execute within same Python as RhodeCode
202 202 - fixed issue with Git bare repos that ends with .git in name
203 203
204 204 1.4.2 (**2012-09-12**)
205 205 ----------------------
206 206
207 207 news
208 208 ++++
209 209
210 210 - added option to menu to quick lock/unlock repository for users that have
211 211 write access to
212 212 - Implemented permissions for writing to repo
213 213 groups. Now only write access to group allows to create a repostiory
214 214 within that group
215 215 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
216 216 - updated translation for zh_CN
217 217
218 218 fixes
219 219 +++++
220 220
221 221 - fixed visual permissions check on repos groups inside groups
222 222 - fixed issues with non-ascii search terms in search, and indexers
223 223 - fixed parsing of page number in GET parameters
224 224 - fixed issues with generating pull-request overview for repos with
225 225 bookmarks and tags, also preview doesn't loose chosen revision from
226 226 select dropdown
227 227
228 228 1.4.1 (**2012-09-07**)
229 229 ----------------------
230 230
231 231 news
232 232 ++++
233 233
234 234 - always put a comment about code-review status change even if user send
235 235 empty data
236 236 - modified_on column saves repository update and it's going to be used
237 237 later for light version of main page ref #500
238 238 - pull request notifications send much nicer emails with details about pull
239 239 request
240 240 - #551 show breadcrumbs in summary view for repositories inside a group
241 241
242 242 fixes
243 243 +++++
244 244
245 245 - fixed migrations of permissions that can lead to inconsistency.
246 246 Some users sent feedback that after upgrading from older versions issues
247 247 with updating default permissions occurred. RhodeCode detects that now and
248 248 resets default user permission to initial state if there is a need for that.
249 249 Also forces users to set the default value for new forking permission.
250 250 - #535 improved apache wsgi example configuration in docs
251 251 - fixes #550 mercurial repositories comparision failed when origin repo had
252 252 additional not-common changesets
253 253 - fixed status of code-review in preview windows of pull request
254 254 - git forks were not initialized at bare repos
255 255 - fixes #555 fixes issues with comparing non-related repositories
256 256 - fixes #557 follower counter always counts up
257 257 - fixed issue #560 require push ssl checkbox wasn't shown when option was
258 258 enabled
259 259 - fixed #559
260 260 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
261 261 if it was a request to url by repository ID
262 262
263 263 1.4.0 (**2012-09-03**)
264 264 ----------------------
265 265
266 266 news
267 267 ++++
268 268
269 269 - new codereview system
270 270 - email map, allowing users to have multiple email addresses mapped into
271 271 their accounts
272 272 - improved git-hook system. Now all actions for git are logged into journal
273 273 including pushed revisions, user and IP address
274 274 - changed setup-app into setup-rhodecode and added default options to it.
275 275 - new git repos are created as bare now by default
276 276 - #464 added links to groups in permission box
277 277 - #465 mentions autocomplete inside comments boxes
278 278 - #469 added --update-only option to whoosh to re-index only given list
279 279 of repos in index
280 280 - rhodecode-api CLI client
281 281 - new git http protocol replaced buggy dulwich implementation.
282 282 Now based on pygrack & gitweb
283 283 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
284 284 reformated based on user suggestions. Additional rss/atom feeds for user
285 285 journal
286 286 - various i18n improvements
287 287 - #478 permissions overview for admin in user edit view
288 288 - File view now displays small gravatars off all authors of given file
289 289 - Implemented landing revisions. Each repository will get landing_rev attribute
290 290 that defines 'default' revision/branch for generating readme files
291 291 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
292 292 earliest possible call.
293 293 - Import remote svn repositories to mercurial using hgsubversion.
294 294 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
295 295 - RhodeCode can use alternative server for generating avatar icons
296 296 - implemented repositories locking. Pull locks, push unlocks. Also can be done
297 297 via API calls
298 298 - #538 form for permissions can handle multiple users at once
299 299
300 300 fixes
301 301 +++++
302 302
303 303 - improved translations
304 304 - fixes issue #455 Creating an archive generates an exception on Windows
305 305 - fixes #448 Download ZIP archive keeps file in /tmp open and results
306 306 in out of disk space
307 307 - fixes issue #454 Search results under Windows include proceeding
308 308 backslash
309 309 - fixed issue #450. Rhodecode no longer will crash when bad revision is
310 310 present in journal data.
311 311 - fix for issue #417, git execution was broken on windows for certain
312 312 commands.
313 313 - fixed #413. Don't disable .git directory for bare repos on deleting
314 314 - fixed issue #459. Changed the way of obtaining logger in reindex task.
315 315 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
316 316 reindexing modified files
317 317 - fixed #481 rhodecode emails are sent without Date header
318 318 - fixed #458 wrong count when no repos are present
319 319 - fixed issue #492 missing `\ No newline at end of file` test at the end of
320 320 new chunk in html diff
321 321 - full text search now works also for commit messages
322 322
323 323 1.3.6 (**2012-05-17**)
324 324 ----------------------
325 325
326 326 news
327 327 ++++
328 328
329 329 - chinese traditional translation
330 330 - changed setup-app into setup-rhodecode and added arguments for auto-setup
331 331 mode that doesn't need user interaction
332 332
333 333 fixes
334 334 +++++
335 335
336 336 - fixed no scm found warning
337 337 - fixed __future__ import error on rcextensions
338 338 - made simplejson required lib for speedup on JSON encoding
339 339 - fixes #449 bad regex could get more than revisions from parsing history
340 340 - don't clear DB session when CELERY_EAGER is turned ON
341 341
342 342 1.3.5 (**2012-05-10**)
343 343 ----------------------
344 344
345 345 news
346 346 ++++
347 347
348 348 - use ext_json for json module
349 349 - unified annotation view with file source view
350 350 - notification improvements, better inbox + css
351 351 - #419 don't strip passwords for login forms, make rhodecode
352 352 more compatible with LDAP servers
353 353 - Added HTTP_X_FORWARDED_FOR as another method of extracting
354 354 IP for pull/push logs. - moved all to base controller
355 355 - #415: Adding comment to changeset causes reload.
356 356 Comments are now added via ajax and doesn't reload the page
357 357 - #374 LDAP config is discarded when LDAP can't be activated
358 358 - limited push/pull operations are now logged for git in the journal
359 359 - bumped mercurial to 2.2.X series
360 360 - added support for displaying submodules in file-browser
361 361 - #421 added bookmarks in changelog view
362 362
363 363 fixes
364 364 +++++
365 365
366 366 - fixed dev-version marker for stable when served from source codes
367 367 - fixed missing permission checks on show forks page
368 368 - #418 cast to unicode fixes in notification objects
369 369 - #426 fixed mention extracting regex
370 370 - fixed remote-pulling for git remotes remopositories
371 371 - fixed #434: Error when accessing files or changesets of a git repository
372 372 with submodules
373 373 - fixed issue with empty APIKEYS for users after registration ref. #438
374 374 - fixed issue with getting README files from git repositories
375 375
376 376 1.3.4 (**2012-03-28**)
377 377 ----------------------
378 378
379 379 news
380 380 ++++
381 381
382 382 - Whoosh logging is now controlled by the .ini files logging setup
383 383 - added clone-url into edit form on /settings page
384 384 - added help text into repo add/edit forms
385 385 - created rcextensions module with additional mappings (ref #322) and
386 386 post push/pull/create repo hooks callbacks
387 387 - implemented #377 Users view for his own permissions on account page
388 - #399 added inheritance of permissions for users group on repos groups
388 - #399 added inheritance of permissions for user group on repos groups
389 389 - #401 repository group is automatically pre-selected when adding repos
390 390 inside a repository group
391 391 - added alternative HTTP 403 response when client failed to authenticate. Helps
392 392 solving issues with Mercurial and LDAP
393 393 - #402 removed group prefix from repository name when listing repositories
394 394 inside a group
395 395 - added gravatars into permission view and permissions autocomplete
396 396 - #347 when running multiple RhodeCode instances, properly invalidates cache
397 397 for all registered servers
398 398
399 399 fixes
400 400 +++++
401 401
402 402 - fixed #390 cache invalidation problems on repos inside group
403 403 - fixed #385 clone by ID url was loosing proxy prefix in URL
404 404 - fixed some unicode problems with waitress
405 405 - fixed issue with escaping < and > in changeset commits
406 406 - fixed error occurring during recursive group creation in API
407 407 create_repo function
408 408 - fixed #393 py2.5 fixes for routes url generator
409 409 - fixed #397 Private repository groups shows up before login
410 410 - fixed #396 fixed problems with revoking users in nested groups
411 411 - fixed mysql unicode issues + specified InnoDB as default engine with
412 412 utf8 charset
413 413 - #406 trim long branch/tag names in changelog to not break UI
414 414
415 415 1.3.3 (**2012-03-02**)
416 416 ----------------------
417 417
418 418 news
419 419 ++++
420 420
421 421
422 422 fixes
423 423 +++++
424 424
425 425 - fixed some python2.5 compatibility issues
426 426 - fixed issues with removed repos was accidentally added as groups, after
427 427 full rescan of paths
428 428 - fixes #376 Cannot edit user (using container auth)
429 429 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
430 430 configuration
431 431 - fixed initial sorting of repos inside repo group
432 432 - fixes issue when user tried to resubmit same permission into user/user_groups
433 433 - bumped beaker version that fixes #375 leap error bug
434 434 - fixed raw_changeset for git. It was generated with hg patch headers
435 435 - fixed vcs issue with last_changeset for filenodes
436 436 - fixed missing commit after hook delete
437 437 - fixed #372 issues with git operation detection that caused a security issue
438 438 for git repos
439 439
440 440 1.3.2 (**2012-02-28**)
441 441 ----------------------
442 442
443 443 news
444 444 ++++
445 445
446 446
447 447 fixes
448 448 +++++
449 449
450 450 - fixed git protocol issues with repos-groups
451 451 - fixed git remote repos validator that prevented from cloning remote git repos
452 452 - fixes #370 ending slashes fixes for repo and groups
453 453 - fixes #368 improved git-protocol detection to handle other clients
454 454 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
455 455 Moved To Root
456 456 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
457 457 - fixed #373 missing cascade drop on user_group_to_perm table
458 458
459 459 1.3.1 (**2012-02-27**)
460 460 ----------------------
461 461
462 462 news
463 463 ++++
464 464
465 465
466 466 fixes
467 467 +++++
468 468
469 469 - redirection loop occurs when remember-me wasn't checked during login
470 470 - fixes issues with git blob history generation
471 471 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
472 472
473 473 1.3.0 (**2012-02-26**)
474 474 ----------------------
475 475
476 476 news
477 477 ++++
478 478
479 479 - code review, inspired by github code-comments
480 480 - #215 rst and markdown README files support
481 481 - #252 Container-based and proxy pass-through authentication support
482 482 - #44 branch browser. Filtering of changelog by branches
483 483 - mercurial bookmarks support
484 484 - new hover top menu, optimized to add maximum size for important views
485 485 - configurable clone url template with possibility to specify protocol like
486 486 ssh:// or http:// and also manually alter other parts of clone_url.
487 487 - enabled largefiles extension by default
488 488 - optimized summary file pages and saved a lot of unused space in them
489 489 - #239 option to manually mark repository as fork
490 490 - #320 mapping of commit authors to RhodeCode users
491 491 - #304 hashes are displayed using monospace font
492 492 - diff configuration, toggle white lines and context lines
493 493 - #307 configurable diffs, whitespace toggle, increasing context lines
494 494 - sorting on branches, tags and bookmarks using YUI datatable
495 495 - improved file filter on files page
496 496 - implements #330 api method for listing nodes ar particular revision
497 497 - #73 added linking issues in commit messages to chosen issue tracker url
498 498 based on user defined regular expression
499 499 - added linking of changesets in commit messages
500 500 - new compact changelog with expandable commit messages
501 501 - firstname and lastname are optional in user creation
502 502 - #348 added post-create repository hook
503 503 - #212 global encoding settings is now configurable from .ini files
504 504 - #227 added repository groups permissions
505 505 - markdown gets codehilite extensions
506 506 - new API methods, delete_repositories, grante/revoke permissions for groups
507 507 and repos
508 508
509 509
510 510 fixes
511 511 +++++
512 512
513 513 - rewrote dbsession management for atomic operations, and better error handling
514 514 - fixed sorting of repo tables
515 515 - #326 escape of special html entities in diffs
516 516 - normalized user_name => username in api attributes
517 517 - fixes #298 ldap created users with mixed case emails created conflicts
518 518 on saving a form
519 519 - fixes issue when owner of a repo couldn't revoke permissions for users
520 520 and groups
521 521 - fixes #271 rare JSON serialization problem with statistics
522 522 - fixes #337 missing validation check for conflicting names of a group with a
523 523 repositories group
524 524 - #340 fixed session problem for mysql and celery tasks
525 525 - fixed #331 RhodeCode mangles repository names if the a repository group
526 526 contains the "full path" to the repositories
527 527 - #355 RhodeCode doesn't store encrypted LDAP passwords
528 528
529 529 1.2.5 (**2012-01-28**)
530 530 ----------------------
531 531
532 532 news
533 533 ++++
534 534
535 535 fixes
536 536 +++++
537 537
538 538 - #340 Celery complains about MySQL server gone away, added session cleanup
539 539 for celery tasks
540 540 - #341 "scanning for repositories in None" log message during Rescan was missing
541 541 a parameter
542 542 - fixed creating archives with subrepos. Some hooks were triggered during that
543 543 operation leading to crash.
544 544 - fixed missing email in account page.
545 545 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
546 546 forking on windows impossible
547 547
548 548 1.2.4 (**2012-01-19**)
549 549 ----------------------
550 550
551 551 news
552 552 ++++
553 553
554 554 - RhodeCode is bundled with mercurial series 2.0.X by default, with
555 555 full support to largefiles extension. Enabled by default in new installations
556 556 - #329 Ability to Add/Remove Groups to/from a Repository via AP
557 557 - added requires.txt file with requirements
558 558
559 559 fixes
560 560 +++++
561 561
562 562 - fixes db session issues with celery when emailing admins
563 563 - #331 RhodeCode mangles repository names if the a repository group
564 564 contains the "full path" to the repositories
565 565 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
566 566 - DB session cleanup after hg protocol operations, fixes issues with
567 567 `mysql has gone away` errors
568 568 - #333 doc fixes for get_repo api function
569 569 - #271 rare JSON serialization problem with statistics enabled
570 570 - #337 Fixes issues with validation of repository name conflicting with
571 571 a group name. A proper message is now displayed.
572 572 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
573 573 doesn't work
574 574 - #316 fixes issues with web description in hgrc files
575 575
576 576 1.2.3 (**2011-11-02**)
577 577 ----------------------
578 578
579 579 news
580 580 ++++
581 581
582 582 - added option to manage repos group for non admin users
583 583 - added following API methods for get_users, create_user, get_users_groups,
584 584 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
585 585 get_repo, create_repo, add_user_to_repo
586 586 - implements #237 added password confirmation for my account
587 587 and admin edit user.
588 588 - implements #291 email notification for global events are now sent to all
589 589 administrator users, and global config email.
590 590
591 591 fixes
592 592 +++++
593 593
594 594 - added option for passing auth method for smtp mailer
595 595 - #276 issue with adding a single user with id>10 to usergroups
596 596 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
597 597 - #288 fixes managing of repos in a group for non admin user
598 598
599 599 1.2.2 (**2011-10-17**)
600 600 ----------------------
601 601
602 602 news
603 603 ++++
604 604
605 605 - #226 repo groups are available by path instead of numerical id
606 606
607 607 fixes
608 608 +++++
609 609
610 610 - #259 Groups with the same name but with different parent group
611 611 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
612 612 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
613 613 - #265 ldap save fails sometimes on converting attributes to booleans,
614 614 added getter and setter into model that will prevent from this on db model level
615 615 - fixed problems with timestamps issues #251 and #213
616 616 - fixes #266 RhodeCode allows to create repo with the same name and in
617 617 the same parent as group
618 618 - fixes #245 Rescan of the repositories on Windows
619 619 - fixes #248 cannot edit repos inside a group on windows
620 620 - fixes #219 forking problems on windows
621 621
622 622 1.2.1 (**2011-10-08**)
623 623 ----------------------
624 624
625 625 news
626 626 ++++
627 627
628 628
629 629 fixes
630 630 +++++
631 631
632 632 - fixed problems with basic auth and push problems
633 633 - gui fixes
634 634 - fixed logger
635 635
636 636 1.2.0 (**2011-10-07**)
637 637 ----------------------
638 638
639 639 news
640 640 ++++
641 641
642 642 - implemented #47 repository groups
643 643 - implemented #89 Can setup google analytics code from settings menu
644 644 - implemented #91 added nicer looking archive urls with more download options
645 645 like tags, branches
646 646 - implemented #44 into file browsing, and added follow branch option
647 647 - implemented #84 downloads can be enabled/disabled for each repository
648 648 - anonymous repository can be cloned without having to pass default:default
649 649 into clone url
650 650 - fixed #90 whoosh indexer can index chooses repositories passed in command
651 651 line
652 652 - extended journal with day aggregates and paging
653 653 - implemented #107 source code lines highlight ranges
654 654 - implemented #93 customizable changelog on combined revision ranges -
655 655 equivalent of githubs compare view
656 656 - implemented #108 extended and more powerful LDAP configuration
657 - implemented #56 users groups
657 - implemented #56 user groups
658 658 - major code rewrites optimized codes for speed and memory usage
659 659 - raw and diff downloads are now in git format
660 660 - setup command checks for write access to given path
661 661 - fixed many issues with international characters and unicode. It uses utf8
662 662 decode with replace to provide less errors even with non utf8 encoded strings
663 663 - #125 added API KEY access to feeds
664 664 - #109 Repository can be created from external Mercurial link (aka. remote
665 665 repository, and manually updated (via pull) from admin panel
666 666 - beta git support - push/pull server + basic view for git repos
667 667 - added followers page and forks page
668 668 - server side file creation (with binary file upload interface)
669 669 and edition with commits powered by codemirror
670 670 - #111 file browser file finder, quick lookup files on whole file tree
671 671 - added quick login sliding menu into main page
672 672 - changelog uses lazy loading of affected files details, in some scenarios
673 673 this can improve speed of changelog page dramatically especially for
674 674 larger repositories.
675 675 - implements #214 added support for downloading subrepos in download menu.
676 676 - Added basic API for direct operations on rhodecode via JSON
677 677 - Implemented advanced hook management
678 678
679 679 fixes
680 680 +++++
681 681
682 682 - fixed file browser bug, when switching into given form revision the url was
683 683 not changing
684 684 - fixed propagation to error controller on simplehg and simplegit middlewares
685 685 - fixed error when trying to make a download on empty repository
686 686 - fixed problem with '[' chars in commit messages in journal
687 687 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
688 688 - journal fork fixes
689 689 - removed issue with space inside renamed repository after deletion
690 690 - fixed strange issue on formencode imports
691 691 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
692 692 - #150 fixes for errors on repositories mapped in db but corrupted in
693 693 filesystem
694 694 - fixed problem with ascendant characters in realm #181
695 695 - fixed problem with sqlite file based database connection pool
696 696 - whoosh indexer and code stats share the same dynamic extensions map
697 697 - fixes #188 - relationship delete of repo_to_perm entry on user removal
698 698 - fixes issue #189 Trending source files shows "show more" when no more exist
699 699 - fixes issue #197 Relative paths for pidlocks
700 700 - fixes issue #198 password will require only 3 chars now for login form
701 701 - fixes issue #199 wrong redirection for non admin users after creating a repository
702 702 - fixes issues #202, bad db constraint made impossible to attach same group
703 703 more than one time. Affects only mysql/postgres
704 704 - fixes #218 os.kill patch for windows was missing sig param
705 705 - improved rendering of dag (they are not trimmed anymore when number of
706 706 heads exceeds 5)
707 707
708 708 1.1.8 (**2011-04-12**)
709 709 ----------------------
710 710
711 711 news
712 712 ++++
713 713
714 714 - improved windows support
715 715
716 716 fixes
717 717 +++++
718 718
719 719 - fixed #140 freeze of python dateutil library, since new version is python2.x
720 720 incompatible
721 721 - setup-app will check for write permission in given path
722 722 - cleaned up license info issue #149
723 723 - fixes for issues #137,#116 and problems with unicode and accented characters.
724 724 - fixes crashes on gravatar, when passed in email as unicode
725 725 - fixed tooltip flickering problems
726 726 - fixed came_from redirection on windows
727 727 - fixed logging modules, and sql formatters
728 728 - windows fixes for os.kill issue #133
729 729 - fixes path splitting for windows issues #148
730 730 - fixed issue #143 wrong import on migration to 1.1.X
731 731 - fixed problems with displaying binary files, thanks to Thomas Waldmann
732 732 - removed name from archive files since it's breaking ui for long repo names
733 733 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
734 734 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
735 735 Thomas Waldmann
736 736 - fixed issue #166 summary pager was skipping 10 revisions on second page
737 737
738 738
739 739 1.1.7 (**2011-03-23**)
740 740 ----------------------
741 741
742 742 news
743 743 ++++
744 744
745 745 fixes
746 746 +++++
747 747
748 748 - fixed (again) #136 installation support for FreeBSD
749 749
750 750
751 751 1.1.6 (**2011-03-21**)
752 752 ----------------------
753 753
754 754 news
755 755 ++++
756 756
757 757 fixes
758 758 +++++
759 759
760 760 - fixed #136 installation support for FreeBSD
761 761 - RhodeCode will check for python version during installation
762 762
763 763 1.1.5 (**2011-03-17**)
764 764 ----------------------
765 765
766 766 news
767 767 ++++
768 768
769 769 - basic windows support, by exchanging pybcrypt into sha256 for windows only
770 770 highly inspired by idea of mantis406
771 771
772 772 fixes
773 773 +++++
774 774
775 775 - fixed sorting by author in main page
776 776 - fixed crashes with diffs on binary files
777 777 - fixed #131 problem with boolean values for LDAP
778 778 - fixed #122 mysql problems thanks to striker69
779 779 - fixed problem with errors on calling raw/raw_files/annotate functions
780 780 with unknown revisions
781 781 - fixed returned rawfiles attachment names with international character
782 782 - cleaned out docs, big thanks to Jason Harris
783 783
784 784 1.1.4 (**2011-02-19**)
785 785 ----------------------
786 786
787 787 news
788 788 ++++
789 789
790 790 fixes
791 791 +++++
792 792
793 793 - fixed formencode import problem on settings page, that caused server crash
794 794 when that page was accessed as first after server start
795 795 - journal fixes
796 796 - fixed option to access repository just by entering http://server/<repo_name>
797 797
798 798 1.1.3 (**2011-02-16**)
799 799 ----------------------
800 800
801 801 news
802 802 ++++
803 803
804 804 - implemented #102 allowing the '.' character in username
805 805 - added option to access repository just by entering http://server/<repo_name>
806 806 - celery task ignores result for better performance
807 807
808 808 fixes
809 809 +++++
810 810
811 811 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
812 812 apollo13 and Johan Walles
813 813 - small fixes in journal
814 814 - fixed problems with getting setting for celery from .ini files
815 815 - registration, password reset and login boxes share the same title as main
816 816 application now
817 817 - fixed #113: to high permissions to fork repository
818 818 - fixed problem with '[' chars in commit messages in journal
819 819 - removed issue with space inside renamed repository after deletion
820 820 - db transaction fixes when filesystem repository creation failed
821 821 - fixed #106 relation issues on databases different than sqlite
822 822 - fixed static files paths links to use of url() method
823 823
824 824 1.1.2 (**2011-01-12**)
825 825 ----------------------
826 826
827 827 news
828 828 ++++
829 829
830 830
831 831 fixes
832 832 +++++
833 833
834 834 - fixes #98 protection against float division of percentage stats
835 835 - fixed graph bug
836 836 - forced webhelpers version since it was making troubles during installation
837 837
838 838 1.1.1 (**2011-01-06**)
839 839 ----------------------
840 840
841 841 news
842 842 ++++
843 843
844 844 - added force https option into ini files for easier https usage (no need to
845 845 set server headers with this options)
846 846 - small css updates
847 847
848 848 fixes
849 849 +++++
850 850
851 851 - fixed #96 redirect loop on files view on repositories without changesets
852 852 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
853 853 and server crashed with errors
854 854 - fixed large tooltips problems on main page
855 855 - fixed #92 whoosh indexer is more error proof
856 856
857 857 1.1.0 (**2010-12-18**)
858 858 ----------------------
859 859
860 860 news
861 861 ++++
862 862
863 863 - rewrite of internals for vcs >=0.1.10
864 864 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
865 865 with older clients
866 866 - anonymous access, authentication via ldap
867 867 - performance upgrade for cached repos list - each repository has its own
868 868 cache that's invalidated when needed.
869 869 - performance upgrades on repositories with large amount of commits (20K+)
870 870 - main page quick filter for filtering repositories
871 871 - user dashboards with ability to follow chosen repositories actions
872 872 - sends email to admin on new user registration
873 873 - added cache/statistics reset options into repository settings
874 874 - more detailed action logger (based on hooks) with pushed changesets lists
875 875 and options to disable those hooks from admin panel
876 876 - introduced new enhanced changelog for merges that shows more accurate results
877 877 - new improved and faster code stats (based on pygments lexers mapping tables,
878 878 showing up to 10 trending sources for each repository. Additionally stats
879 879 can be disabled in repository settings.
880 880 - gui optimizations, fixed application width to 1024px
881 881 - added cut off (for large files/changesets) limit into config files
882 882 - whoosh, celeryd, upgrade moved to paster command
883 883 - other than sqlite database backends can be used
884 884
885 885 fixes
886 886 +++++
887 887
888 888 - fixes #61 forked repo was showing only after cache expired
889 889 - fixes #76 no confirmation on user deletes
890 890 - fixes #66 Name field misspelled
891 891 - fixes #72 block user removal when he owns repositories
892 892 - fixes #69 added password confirmation fields
893 893 - fixes #87 RhodeCode crashes occasionally on updating repository owner
894 894 - fixes #82 broken annotations on files with more than 1 blank line at the end
895 895 - a lot of fixes and tweaks for file browser
896 896 - fixed detached session issues
897 897 - fixed when user had no repos he would see all repos listed in my account
898 898 - fixed ui() instance bug when global hgrc settings was loaded for server
899 899 instance and all hgrc options were merged with our db ui() object
900 900 - numerous small bugfixes
901 901
902 902 (special thanks for TkSoh for detailed feedback)
903 903
904 904
905 905 1.0.2 (**2010-11-12**)
906 906 ----------------------
907 907
908 908 news
909 909 ++++
910 910
911 911 - tested under python2.7
912 912 - bumped sqlalchemy and celery versions
913 913
914 914 fixes
915 915 +++++
916 916
917 917 - fixed #59 missing graph.js
918 918 - fixed repo_size crash when repository had broken symlinks
919 919 - fixed python2.5 crashes.
920 920
921 921
922 922 1.0.1 (**2010-11-10**)
923 923 ----------------------
924 924
925 925 news
926 926 ++++
927 927
928 928 - small css updated
929 929
930 930 fixes
931 931 +++++
932 932
933 933 - fixed #53 python2.5 incompatible enumerate calls
934 934 - fixed #52 disable mercurial extension for web
935 935 - fixed #51 deleting repositories don't delete it's dependent objects
936 936
937 937
938 938 1.0.0 (**2010-11-02**)
939 939 ----------------------
940 940
941 941 - security bugfix simplehg wasn't checking for permissions on commands
942 942 other than pull or push.
943 943 - fixed doubled messages after push or pull in admin journal
944 944 - templating and css corrections, fixed repo switcher on chrome, updated titles
945 945 - admin menu accessible from options menu on repository view
946 946 - permissions cached queries
947 947
948 948 1.0.0rc4 (**2010-10-12**)
949 949 --------------------------
950 950
951 951 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
952 952 - removed cache_manager settings from sqlalchemy meta
953 953 - added sqlalchemy cache settings to ini files
954 954 - validated password length and added second try of failure on paster setup-app
955 955 - fixed setup database destroy prompt even when there was no db
956 956
957 957
958 958 1.0.0rc3 (**2010-10-11**)
959 959 -------------------------
960 960
961 961 - fixed i18n during installation.
962 962
963 963 1.0.0rc2 (**2010-10-11**)
964 964 -------------------------
965 965
966 966 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
967 967 occure. After vcs is fixed it'll be put back again.
968 968 - templating/css rewrites, optimized css.
@@ -1,648 +1,648 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11 # prefix for non repository related links needs to be prefixed with `/`
12 12 ADMIN_PREFIX = '/_admin'
13 13
14 14
15 15 def make_map(config):
16 16 """Create, configure and return the routes Mapper"""
17 17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 18 always_scan=config['debug'])
19 19 rmap.minimization = False
20 20 rmap.explicit = False
21 21
22 22 from rhodecode.lib.utils import is_valid_repo
23 23 from rhodecode.lib.utils import is_valid_repos_group
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28
29 29 :param environ:
30 30 :param match_dict:
31 31 """
32 32 from rhodecode.model.db import Repository
33 33 repo_name = match_dict.get('repo_name')
34 34
35 35 if match_dict.get('f_path'):
36 36 #fix for multiple initial slashes that causes errors
37 37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38 38
39 39 try:
40 40 by_id = repo_name.split('_')
41 41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 42 repo_name = Repository.get(by_id[1]).repo_name
43 43 match_dict['repo_name'] = repo_name
44 44 except:
45 45 pass
46 46
47 47 return is_valid_repo(repo_name, config['base_path'])
48 48
49 49 def check_group(environ, match_dict):
50 50 """
51 51 check for valid repositories group for proper 404 handling
52 52
53 53 :param environ:
54 54 :param match_dict:
55 55 """
56 56 repos_group_name = match_dict.get('group_name')
57 57 return is_valid_repos_group(repos_group_name, config['base_path'])
58 58
59 59 def check_int(environ, match_dict):
60 60 return match_dict.get('id').isdigit()
61 61
62 62 # The ErrorController route (handles 404/500 error pages); it should
63 63 # likely stay at the top, ensuring it can always be resolved
64 64 rmap.connect('/error/{action}', controller='error')
65 65 rmap.connect('/error/{action}/{id}', controller='error')
66 66
67 67 #==========================================================================
68 68 # CUSTOM ROUTES HERE
69 69 #==========================================================================
70 70
71 71 #MAIN PAGE
72 72 rmap.connect('home', '/', controller='home', action='index')
73 73 rmap.connect('repo_switcher', '/repos', controller='home',
74 74 action='repo_switcher')
75 75 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
76 76 controller='home', action='branch_tag_switcher')
77 77 rmap.connect('bugtracker',
78 78 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
79 79 _static=True)
80 80 rmap.connect('rst_help',
81 81 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
82 82 _static=True)
83 83 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
84 84
85 85 #ADMIN REPOSITORY REST ROUTES
86 86 with rmap.submapper(path_prefix=ADMIN_PREFIX,
87 87 controller='admin/repos') as m:
88 88 m.connect("repos", "/repos",
89 89 action="create", conditions=dict(method=["POST"]))
90 90 m.connect("repos", "/repos",
91 91 action="index", conditions=dict(method=["GET"]))
92 92 m.connect("formatted_repos", "/repos.{format}",
93 93 action="index",
94 94 conditions=dict(method=["GET"]))
95 95 m.connect("new_repo", "/repos/new",
96 96 action="new", conditions=dict(method=["GET"]))
97 97 m.connect("formatted_new_repo", "/repos/new.{format}",
98 98 action="new", conditions=dict(method=["GET"]))
99 99 m.connect("/repos/{repo_name:.*?}",
100 100 action="update", conditions=dict(method=["PUT"],
101 101 function=check_repo))
102 102 m.connect("/repos/{repo_name:.*?}",
103 103 action="delete", conditions=dict(method=["DELETE"],
104 104 function=check_repo))
105 105 # no longer used:
106 106 m.connect("edit_repo_admin", "/repos/{repo_name:.*?}/edit",
107 107 action="edit", conditions=dict(method=["GET"],
108 108 function=check_repo))
109 109 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
110 110 action="edit", conditions=dict(method=["GET"],
111 111 function=check_repo))
112 112 m.connect("repo", "/repos/{repo_name:.*?}",
113 113 action="show", conditions=dict(method=["GET"],
114 114 function=check_repo))
115 115 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
116 116 action="show", conditions=dict(method=["GET"],
117 117 function=check_repo))
118 118 #ajax delete repo perm user
119 119 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
120 120 action="delete_perm_user",
121 121 conditions=dict(method=["DELETE"], function=check_repo))
122 122
123 123 #ajax delete repo perm users_group
124 124 m.connect('delete_repo_users_group',
125 125 "/repos_delete_users_group/{repo_name:.*?}",
126 126 action="delete_perm_users_group",
127 127 conditions=dict(method=["DELETE"], function=check_repo))
128 128
129 129 #settings actions
130 130 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
131 131 action="repo_stats", conditions=dict(method=["DELETE"],
132 132 function=check_repo))
133 133 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
134 134 action="repo_cache", conditions=dict(method=["DELETE"],
135 135 function=check_repo))
136 136 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
137 137 action="repo_public_journal", conditions=dict(method=["PUT"],
138 138 function=check_repo))
139 139 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
140 140 action="repo_pull", conditions=dict(method=["PUT"],
141 141 function=check_repo))
142 142 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
143 143 action="repo_as_fork", conditions=dict(method=["PUT"],
144 144 function=check_repo))
145 145 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
146 146 action="repo_locking", conditions=dict(method=["PUT"],
147 147 function=check_repo))
148 148 #repo fields
149 149 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
150 150 action="create_repo_field", conditions=dict(method=["PUT"],
151 151 function=check_repo))
152 152
153 153 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
154 154 action="delete_repo_field", conditions=dict(method=["DELETE"],
155 155 function=check_repo))
156 156
157 157 with rmap.submapper(path_prefix=ADMIN_PREFIX,
158 158 controller='admin/repos_groups') as m:
159 159 m.connect("repos_groups", "/repos_groups",
160 160 action="create", conditions=dict(method=["POST"]))
161 161 m.connect("repos_groups", "/repos_groups",
162 162 action="index", conditions=dict(method=["GET"]))
163 163 m.connect("formatted_repos_groups", "/repos_groups.{format}",
164 164 action="index", conditions=dict(method=["GET"]))
165 165 m.connect("new_repos_group", "/repos_groups/new",
166 166 action="new", conditions=dict(method=["GET"]))
167 167 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
168 168 action="new", conditions=dict(method=["GET"]))
169 169 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
170 170 action="update", conditions=dict(method=["PUT"],
171 171 function=check_group))
172 172 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
173 173 action="delete", conditions=dict(method=["DELETE"],
174 174 function=check_group))
175 175 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
176 176 action="edit", conditions=dict(method=["GET"],))
177 177 m.connect("formatted_edit_repos_group",
178 178 "/repos_groups/{group_name:.*?}.{format}/edit",
179 179 action="edit", conditions=dict(method=["GET"],
180 180 function=check_group))
181 181 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
182 182 action="show", conditions=dict(method=["GET"],
183 183 function=check_group))
184 184 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
185 185 action="show", conditions=dict(method=["GET"],
186 186 function=check_group))
187 187 # ajax delete repos group perm user
188 188 m.connect('delete_repos_group_user_perm',
189 189 "/delete_repos_group_user_perm/{group_name:.*?}",
190 190 action="delete_repos_group_user_perm",
191 191 conditions=dict(method=["DELETE"], function=check_group))
192 192
193 193 # ajax delete repos group perm users_group
194 194 m.connect('delete_repos_group_users_group_perm',
195 195 "/delete_repos_group_users_group_perm/{group_name:.*?}",
196 196 action="delete_repos_group_users_group_perm",
197 197 conditions=dict(method=["DELETE"], function=check_group))
198 198
199 199 #ADMIN USER REST ROUTES
200 200 with rmap.submapper(path_prefix=ADMIN_PREFIX,
201 201 controller='admin/users') as m:
202 202 m.connect("users", "/users",
203 203 action="create", conditions=dict(method=["POST"]))
204 204 m.connect("users", "/users",
205 205 action="index", conditions=dict(method=["GET"]))
206 206 m.connect("formatted_users", "/users.{format}",
207 207 action="index", conditions=dict(method=["GET"]))
208 208 m.connect("new_user", "/users/new",
209 209 action="new", conditions=dict(method=["GET"]))
210 210 m.connect("formatted_new_user", "/users/new.{format}",
211 211 action="new", conditions=dict(method=["GET"]))
212 212 m.connect("update_user", "/users/{id}",
213 213 action="update", conditions=dict(method=["PUT"]))
214 214 m.connect("delete_user", "/users/{id}",
215 215 action="delete", conditions=dict(method=["DELETE"]))
216 216 m.connect("edit_user", "/users/{id}/edit",
217 217 action="edit", conditions=dict(method=["GET"]))
218 218 m.connect("formatted_edit_user",
219 219 "/users/{id}.{format}/edit",
220 220 action="edit", conditions=dict(method=["GET"]))
221 221 m.connect("user", "/users/{id}",
222 222 action="show", conditions=dict(method=["GET"]))
223 223 m.connect("formatted_user", "/users/{id}.{format}",
224 224 action="show", conditions=dict(method=["GET"]))
225 225
226 226 #EXTRAS USER ROUTES
227 227 m.connect("user_perm", "/users_perm/{id}",
228 228 action="update_perm", conditions=dict(method=["PUT"]))
229 229 m.connect("user_emails", "/users_emails/{id}",
230 230 action="add_email", conditions=dict(method=["PUT"]))
231 231 m.connect("user_emails_delete", "/users_emails/{id}",
232 232 action="delete_email", conditions=dict(method=["DELETE"]))
233 233 m.connect("user_ips", "/users_ips/{id}",
234 234 action="add_ip", conditions=dict(method=["PUT"]))
235 235 m.connect("user_ips_delete", "/users_ips/{id}",
236 236 action="delete_ip", conditions=dict(method=["DELETE"]))
237 237
238 #ADMIN USERS GROUPS REST ROUTES
238 #ADMIN USER GROUPS REST ROUTES
239 239 with rmap.submapper(path_prefix=ADMIN_PREFIX,
240 240 controller='admin/users_groups') as m:
241 241 m.connect("users_groups", "/users_groups",
242 242 action="create", conditions=dict(method=["POST"]))
243 243 m.connect("users_groups", "/users_groups",
244 244 action="index", conditions=dict(method=["GET"]))
245 245 m.connect("formatted_users_groups", "/users_groups.{format}",
246 246 action="index", conditions=dict(method=["GET"]))
247 247 m.connect("new_users_group", "/users_groups/new",
248 248 action="new", conditions=dict(method=["GET"]))
249 249 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
250 250 action="new", conditions=dict(method=["GET"]))
251 251 m.connect("update_users_group", "/users_groups/{id}",
252 252 action="update", conditions=dict(method=["PUT"]))
253 253 m.connect("delete_users_group", "/users_groups/{id}",
254 254 action="delete", conditions=dict(method=["DELETE"]))
255 255 m.connect("edit_users_group", "/users_groups/{id}/edit",
256 256 action="edit", conditions=dict(method=["GET"]))
257 257 m.connect("formatted_edit_users_group",
258 258 "/users_groups/{id}.{format}/edit",
259 259 action="edit", conditions=dict(method=["GET"]))
260 260 m.connect("users_group", "/users_groups/{id}",
261 261 action="show", conditions=dict(method=["GET"]))
262 262 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
263 263 action="show", conditions=dict(method=["GET"]))
264 264
265 265 #EXTRAS USER ROUTES
266 266 m.connect("users_group_perm", "/users_groups_perm/{id}",
267 267 action="update_perm", conditions=dict(method=["PUT"]))
268 268
269 269 #ADMIN GROUP REST ROUTES
270 270 rmap.resource('group', 'groups',
271 271 controller='admin/groups', path_prefix=ADMIN_PREFIX)
272 272
273 273 #ADMIN PERMISSIONS REST ROUTES
274 274 rmap.resource('permission', 'permissions',
275 275 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
276 276
277 277 #ADMIN DEFAULTS REST ROUTES
278 278 rmap.resource('default', 'defaults',
279 279 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
280 280
281 281 ##ADMIN LDAP SETTINGS
282 282 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
283 283 controller='admin/ldap_settings', action='ldap_settings',
284 284 conditions=dict(method=["POST"]))
285 285
286 286 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
287 287 controller='admin/ldap_settings')
288 288
289 289 #ADMIN SETTINGS REST ROUTES
290 290 with rmap.submapper(path_prefix=ADMIN_PREFIX,
291 291 controller='admin/settings') as m:
292 292 m.connect("admin_settings", "/settings",
293 293 action="create", conditions=dict(method=["POST"]))
294 294 m.connect("admin_settings", "/settings",
295 295 action="index", conditions=dict(method=["GET"]))
296 296 m.connect("formatted_admin_settings", "/settings.{format}",
297 297 action="index", conditions=dict(method=["GET"]))
298 298 m.connect("admin_new_setting", "/settings/new",
299 299 action="new", conditions=dict(method=["GET"]))
300 300 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
301 301 action="new", conditions=dict(method=["GET"]))
302 302 m.connect("/settings/{setting_id}",
303 303 action="update", conditions=dict(method=["PUT"]))
304 304 m.connect("/settings/{setting_id}",
305 305 action="delete", conditions=dict(method=["DELETE"]))
306 306 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
307 307 action="edit", conditions=dict(method=["GET"]))
308 308 m.connect("formatted_admin_edit_setting",
309 309 "/settings/{setting_id}.{format}/edit",
310 310 action="edit", conditions=dict(method=["GET"]))
311 311 m.connect("admin_setting", "/settings/{setting_id}",
312 312 action="show", conditions=dict(method=["GET"]))
313 313 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
314 314 action="show", conditions=dict(method=["GET"]))
315 315 m.connect("admin_settings_my_account", "/my_account",
316 316 action="my_account", conditions=dict(method=["GET"]))
317 317 m.connect("admin_settings_my_account_update", "/my_account_update",
318 318 action="my_account_update", conditions=dict(method=["PUT"]))
319 319 m.connect("admin_settings_create_repository", "/create_repository",
320 320 action="create_repository", conditions=dict(method=["GET"]))
321 321 m.connect("admin_settings_my_repos", "/my_account/repos",
322 322 action="my_account_my_repos", conditions=dict(method=["GET"]))
323 323 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
324 324 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
325 325
326 326 #NOTIFICATION REST ROUTES
327 327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
328 328 controller='admin/notifications') as m:
329 329 m.connect("notifications", "/notifications",
330 330 action="create", conditions=dict(method=["POST"]))
331 331 m.connect("notifications", "/notifications",
332 332 action="index", conditions=dict(method=["GET"]))
333 333 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
334 334 action="mark_all_read", conditions=dict(method=["GET"]))
335 335 m.connect("formatted_notifications", "/notifications.{format}",
336 336 action="index", conditions=dict(method=["GET"]))
337 337 m.connect("new_notification", "/notifications/new",
338 338 action="new", conditions=dict(method=["GET"]))
339 339 m.connect("formatted_new_notification", "/notifications/new.{format}",
340 340 action="new", conditions=dict(method=["GET"]))
341 341 m.connect("/notification/{notification_id}",
342 342 action="update", conditions=dict(method=["PUT"]))
343 343 m.connect("/notification/{notification_id}",
344 344 action="delete", conditions=dict(method=["DELETE"]))
345 345 m.connect("edit_notification", "/notification/{notification_id}/edit",
346 346 action="edit", conditions=dict(method=["GET"]))
347 347 m.connect("formatted_edit_notification",
348 348 "/notification/{notification_id}.{format}/edit",
349 349 action="edit", conditions=dict(method=["GET"]))
350 350 m.connect("notification", "/notification/{notification_id}",
351 351 action="show", conditions=dict(method=["GET"]))
352 352 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
353 353 action="show", conditions=dict(method=["GET"]))
354 354
355 355 #ADMIN MAIN PAGES
356 356 with rmap.submapper(path_prefix=ADMIN_PREFIX,
357 357 controller='admin/admin') as m:
358 358 m.connect('admin_home', '', action='index')
359 359 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
360 360 action='add_repo')
361 361
362 362 #==========================================================================
363 363 # API V2
364 364 #==========================================================================
365 365 with rmap.submapper(path_prefix=ADMIN_PREFIX,
366 366 controller='api/api') as m:
367 367 m.connect('api', '/api')
368 368
369 369 #USER JOURNAL
370 370 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
371 371 controller='journal', action='index')
372 372 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
373 373 controller='journal', action='journal_rss')
374 374 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
375 375 controller='journal', action='journal_atom')
376 376
377 377 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
378 378 controller='journal', action="public_journal")
379 379
380 380 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
381 381 controller='journal', action="public_journal_rss")
382 382
383 383 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
384 384 controller='journal', action="public_journal_rss")
385 385
386 386 rmap.connect('public_journal_atom',
387 387 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
388 388 action="public_journal_atom")
389 389
390 390 rmap.connect('public_journal_atom_old',
391 391 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
392 392 action="public_journal_atom")
393 393
394 394 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
395 395 controller='journal', action='toggle_following',
396 396 conditions=dict(method=["POST"]))
397 397
398 398 #SEARCH
399 399 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
400 400 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
401 401 controller='search',
402 402 conditions=dict(function=check_repo))
403 403 rmap.connect('search_repo', '/{repo_name:.*?}/search',
404 404 controller='search',
405 405 conditions=dict(function=check_repo),
406 406 )
407 407
408 408 #LOGIN/LOGOUT/REGISTER/SIGN IN
409 409 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
410 410 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
411 411 action='logout')
412 412
413 413 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
414 414 action='register')
415 415
416 416 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
417 417 controller='login', action='password_reset')
418 418
419 419 rmap.connect('reset_password_confirmation',
420 420 '%s/password_reset_confirmation' % ADMIN_PREFIX,
421 421 controller='login', action='password_reset_confirmation')
422 422
423 423 #FEEDS
424 424 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
425 425 controller='feed', action='rss',
426 426 conditions=dict(function=check_repo))
427 427
428 428 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
429 429 controller='feed', action='atom',
430 430 conditions=dict(function=check_repo))
431 431
432 432 #==========================================================================
433 433 # REPOSITORY ROUTES
434 434 #==========================================================================
435 435 rmap.connect('summary_home', '/{repo_name:.*?}',
436 436 controller='summary',
437 437 conditions=dict(function=check_repo))
438 438
439 439 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
440 440 controller='summary', action='repo_size',
441 441 conditions=dict(function=check_repo))
442 442
443 443 rmap.connect('repos_group_home', '/{group_name:.*}',
444 444 controller='admin/repos_groups', action="show_by_name",
445 445 conditions=dict(function=check_group))
446 446
447 447 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
448 448 controller='changeset', revision='tip',
449 449 conditions=dict(function=check_repo))
450 450
451 451 rmap.connect("edit_repo", "/{repo_name:.*?}/edit",
452 452 controller='admin/repos', action="edit",
453 453 conditions=dict(method=["GET"], function=check_repo)
454 454 )
455 455
456 456 #still working url for backward compat.
457 457 rmap.connect('raw_changeset_home_depraced',
458 458 '/{repo_name:.*?}/raw-changeset/{revision}',
459 459 controller='changeset', action='changeset_raw',
460 460 revision='tip', conditions=dict(function=check_repo))
461 461
462 462 ## new URLs
463 463 rmap.connect('changeset_raw_home',
464 464 '/{repo_name:.*?}/changeset-diff/{revision}',
465 465 controller='changeset', action='changeset_raw',
466 466 revision='tip', conditions=dict(function=check_repo))
467 467
468 468 rmap.connect('changeset_patch_home',
469 469 '/{repo_name:.*?}/changeset-patch/{revision}',
470 470 controller='changeset', action='changeset_patch',
471 471 revision='tip', conditions=dict(function=check_repo))
472 472
473 473 rmap.connect('changeset_download_home',
474 474 '/{repo_name:.*?}/changeset-download/{revision}',
475 475 controller='changeset', action='changeset_download',
476 476 revision='tip', conditions=dict(function=check_repo))
477 477
478 478 rmap.connect('changeset_comment',
479 479 '/{repo_name:.*?}/changeset/{revision}/comment',
480 480 controller='changeset', revision='tip', action='comment',
481 481 conditions=dict(function=check_repo))
482 482
483 483 rmap.connect('changeset_comment_delete',
484 484 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
485 485 controller='changeset', action='delete_comment',
486 486 conditions=dict(function=check_repo, method=["DELETE"]))
487 487
488 488 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
489 489 controller='changeset', action='changeset_info')
490 490
491 491 rmap.connect('compare_url',
492 492 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
493 493 controller='compare', action='index',
494 494 conditions=dict(function=check_repo),
495 495 requirements=dict(
496 496 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
497 497 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
498 498 )
499 499
500 500 rmap.connect('pullrequest_home',
501 501 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
502 502 action='index', conditions=dict(function=check_repo,
503 503 method=["GET"]))
504 504
505 505 rmap.connect('pullrequest',
506 506 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
507 507 action='create', conditions=dict(function=check_repo,
508 508 method=["POST"]))
509 509
510 510 rmap.connect('pullrequest_show',
511 511 '/{repo_name:.*?}/pull-request/{pull_request_id}',
512 512 controller='pullrequests',
513 513 action='show', conditions=dict(function=check_repo,
514 514 method=["GET"]))
515 515 rmap.connect('pullrequest_update',
516 516 '/{repo_name:.*?}/pull-request/{pull_request_id}',
517 517 controller='pullrequests',
518 518 action='update', conditions=dict(function=check_repo,
519 519 method=["PUT"]))
520 520 rmap.connect('pullrequest_delete',
521 521 '/{repo_name:.*?}/pull-request/{pull_request_id}',
522 522 controller='pullrequests',
523 523 action='delete', conditions=dict(function=check_repo,
524 524 method=["DELETE"]))
525 525
526 526 rmap.connect('pullrequest_show_all',
527 527 '/{repo_name:.*?}/pull-request',
528 528 controller='pullrequests',
529 529 action='show_all', conditions=dict(function=check_repo,
530 530 method=["GET"]))
531 531
532 532 rmap.connect('pullrequest_comment',
533 533 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
534 534 controller='pullrequests',
535 535 action='comment', conditions=dict(function=check_repo,
536 536 method=["POST"]))
537 537
538 538 rmap.connect('pullrequest_comment_delete',
539 539 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
540 540 controller='pullrequests', action='delete_comment',
541 541 conditions=dict(function=check_repo, method=["DELETE"]))
542 542
543 543 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
544 544 controller='summary', conditions=dict(function=check_repo))
545 545
546 546 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
547 547 controller='shortlog', conditions=dict(function=check_repo))
548 548
549 549 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
550 550 controller='shortlog', f_path=None,
551 551 conditions=dict(function=check_repo))
552 552
553 553 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
554 554 controller='branches', conditions=dict(function=check_repo))
555 555
556 556 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
557 557 controller='tags', conditions=dict(function=check_repo))
558 558
559 559 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
560 560 controller='bookmarks', conditions=dict(function=check_repo))
561 561
562 562 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
563 563 controller='changelog', conditions=dict(function=check_repo))
564 564
565 565 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
566 566 controller='changelog', action='changelog_details',
567 567 conditions=dict(function=check_repo))
568 568
569 569 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
570 570 controller='files', revision='tip', f_path='',
571 571 conditions=dict(function=check_repo))
572 572
573 573 rmap.connect('files_history_home',
574 574 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
575 575 controller='files', action='history', revision='tip', f_path='',
576 576 conditions=dict(function=check_repo))
577 577
578 578 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
579 579 controller='files', action='diff', revision='tip', f_path='',
580 580 conditions=dict(function=check_repo))
581 581
582 582 rmap.connect('files_rawfile_home',
583 583 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
584 584 controller='files', action='rawfile', revision='tip',
585 585 f_path='', conditions=dict(function=check_repo))
586 586
587 587 rmap.connect('files_raw_home',
588 588 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
589 589 controller='files', action='raw', revision='tip', f_path='',
590 590 conditions=dict(function=check_repo))
591 591
592 592 rmap.connect('files_annotate_home',
593 593 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
594 594 controller='files', action='index', revision='tip',
595 595 f_path='', annotate=True, conditions=dict(function=check_repo))
596 596
597 597 rmap.connect('files_edit_home',
598 598 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
599 599 controller='files', action='edit', revision='tip',
600 600 f_path='', conditions=dict(function=check_repo))
601 601
602 602 rmap.connect('files_add_home',
603 603 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
604 604 controller='files', action='add', revision='tip',
605 605 f_path='', conditions=dict(function=check_repo))
606 606
607 607 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
608 608 controller='files', action='archivefile',
609 609 conditions=dict(function=check_repo))
610 610
611 611 rmap.connect('files_nodelist_home',
612 612 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
613 613 controller='files', action='nodelist',
614 614 conditions=dict(function=check_repo))
615 615
616 616 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
617 617 controller='settings', action="delete",
618 618 conditions=dict(method=["DELETE"], function=check_repo))
619 619
620 620 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
621 621 controller='settings', action="update",
622 622 conditions=dict(method=["PUT"], function=check_repo))
623 623
624 624 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
625 625 controller='settings', action='index',
626 626 conditions=dict(function=check_repo))
627 627
628 628 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
629 629 controller='settings', action="toggle_locking",
630 630 conditions=dict(method=["GET"], function=check_repo))
631 631
632 632 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
633 633 controller='forks', action='fork_create',
634 634 conditions=dict(function=check_repo, method=["POST"]))
635 635
636 636 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
637 637 controller='forks', action='fork',
638 638 conditions=dict(function=check_repo))
639 639
640 640 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
641 641 controller='forks', action='forks',
642 642 conditions=dict(function=check_repo))
643 643
644 644 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
645 645 controller='followers', action='followers',
646 646 conditions=dict(function=check_repo))
647 647
648 648 return rmap
@@ -1,536 +1,536 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError, HTTPForbidden
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
41 41 HasPermissionAny, HasReposGroupPermissionAny
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 44 from rhodecode.lib.helpers import get_token
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
47 47 RhodeCodeSetting, RepositoryField
48 48 from rhodecode.model.forms import RepoForm, RepoFieldForm
49 49 from rhodecode.model.scm import ScmModel, GroupList
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.lib.compat import json
52 52 from sqlalchemy.sql.expression import func
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class ReposController(BaseRepoController):
58 58 """
59 59 REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('repo', 'repos')
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 c.admin_user = session.get('admin_user')
67 67 c.admin_username = session.get('admin_username')
68 68 super(ReposController, self).__before__()
69 69
70 70 def __load_defaults(self):
71 71 acl_groups = GroupList(RepoGroup.query().all(),
72 72 perm_set=['group.write', 'group.admin'])
73 73 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
74 74 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
75 75
76 76 repo_model = RepoModel()
77 77 c.users_array = repo_model.get_users_js()
78 78 c.users_groups_array = repo_model.get_users_groups_js()
79 79 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
80 80 c.landing_revs_choices = choices
81 81
82 82 def __load_data(self, repo_name=None):
83 83 """
84 84 Load defaults settings for edit, and update
85 85
86 86 :param repo_name:
87 87 """
88 88 self.__load_defaults()
89 89
90 90 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
91 91 repo = db_repo.scm_instance
92 92
93 93 if c.repo_info is None:
94 94 h.not_mapped_error(repo_name)
95 95 return redirect(url('repos'))
96 96
97 97 ##override defaults for exact repo info here git/hg etc
98 98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
99 99 c.landing_revs_choices = choices
100 100
101 101 c.default_user_id = User.get_by_username('default').user_id
102 102 c.in_public_journal = UserFollowing.query()\
103 103 .filter(UserFollowing.user_id == c.default_user_id)\
104 104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
105 105
106 106 if c.repo_info.stats:
107 107 # this is on what revision we ended up so we add +1 for count
108 108 last_rev = c.repo_info.stats.stat_on_revision + 1
109 109 else:
110 110 last_rev = 0
111 111 c.stats_revision = last_rev
112 112
113 113 c.repo_last_rev = repo.count() if repo.revisions else 0
114 114
115 115 if last_rev == 0 or c.repo_last_rev == 0:
116 116 c.stats_percentage = 0
117 117 else:
118 118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
119 119 c.repo_last_rev) * 100)
120 120
121 121 c.repo_fields = RepositoryField.query()\
122 122 .filter(RepositoryField.repository == db_repo).all()
123 123
124 124 defaults = RepoModel()._get_defaults(repo_name)
125 125
126 126 c.repos_list = [('', _('--REMOVE FORK--'))]
127 127 c.repos_list += [(x.repo_id, x.repo_name) for x in
128 128 Repository.query().order_by(Repository.repo_name).all()
129 129 if x.repo_id != c.repo_info.repo_id]
130 130
131 131 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
132 132 return defaults
133 133
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 def index(self, format='html'):
136 136 """GET /repos: All items in the collection"""
137 137 # url('repos')
138 138
139 139 c.repos_list = Repository.query()\
140 140 .order_by(func.lower(Repository.repo_name))\
141 141 .all()
142 142
143 143 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
144 144 admin=True,
145 145 super_user_actions=True)
146 146 #json used to render the grid
147 147 c.data = json.dumps(repos_data)
148 148
149 149 return render('admin/repos/repos.html')
150 150
151 151 @NotAnonymous()
152 152 def create(self):
153 153 """
154 154 POST /repos: Create a new item"""
155 155 # url('repos')
156 156
157 157 self.__load_defaults()
158 158 form_result = {}
159 159 try:
160 160 form_result = RepoForm(repo_groups=c.repo_groups_choices,
161 161 landing_revs=c.landing_revs_choices)()\
162 162 .to_python(dict(request.POST))
163 163
164 164 new_repo = RepoModel().create(form_result,
165 165 self.rhodecode_user.user_id)
166 166 if form_result['clone_uri']:
167 167 h.flash(_('created repository %s from %s') \
168 168 % (form_result['repo_name'], form_result['clone_uri']),
169 169 category='success')
170 170 else:
171 171 h.flash(_('created repository %s') % form_result['repo_name'],
172 172 category='success')
173 173
174 174 if request.POST.get('user_created'):
175 175 # created by regular non admin user
176 176 action_logger(self.rhodecode_user, 'user_created_repo',
177 177 form_result['repo_name_full'], self.ip_addr,
178 178 self.sa)
179 179 else:
180 180 action_logger(self.rhodecode_user, 'admin_created_repo',
181 181 form_result['repo_name_full'], self.ip_addr,
182 182 self.sa)
183 183 Session().commit()
184 184 except formencode.Invalid, errors:
185 185 return htmlfill.render(
186 186 render('admin/repos/repo_add.html'),
187 187 defaults=errors.value,
188 188 errors=errors.error_dict or {},
189 189 prefix_error=False,
190 190 encoding="UTF-8")
191 191
192 192 except Exception:
193 193 log.error(traceback.format_exc())
194 194 msg = _('error occurred during creation of repository %s') \
195 195 % form_result.get('repo_name')
196 196 h.flash(msg, category='error')
197 197 if c.rhodecode_user.is_admin:
198 198 return redirect(url('repos'))
199 199 return redirect(url('home'))
200 200 #redirect to our new repo !
201 201 return redirect(url('summary_home', repo_name=new_repo.repo_name))
202 202
203 203 @HasPermissionAllDecorator('hg.admin')
204 204 def new(self, format='html'):
205 205 """
206 206 WARNING: this function is depracated see settings.create_repo !!
207 207
208 208 GET /repos/new: Form to create a new item
209 209 """
210 210
211 211 parent_group = request.GET.get('parent_group')
212 212 self.__load_defaults()
213 213 ## apply the defaults from defaults page
214 214 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
215 215 if parent_group:
216 216 defaults.update({'repo_group': parent_group})
217 217
218 218 return htmlfill.render(
219 219 render('admin/repos/repo_add.html'),
220 220 defaults=defaults,
221 221 errors={},
222 222 prefix_error=False,
223 223 encoding="UTF-8"
224 224 )
225 225
226 226 @HasPermissionAllDecorator('hg.admin')
227 227 def update(self, repo_name):
228 228 """
229 229 PUT /repos/repo_name: Update an existing item"""
230 230 # Forms posted to this method should contain a hidden field:
231 231 # <input type="hidden" name="_method" value="PUT" />
232 232 # Or using helpers:
233 233 # h.form(url('repo', repo_name=ID),
234 234 # method='put')
235 235 # url('repo', repo_name=ID)
236 236 self.__load_defaults()
237 237 repo_model = RepoModel()
238 238 changed_name = repo_name
239 239 #override the choices with extracted revisions !
240 240 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
241 241 c.landing_revs_choices = choices
242 242
243 243 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
244 244 repo_groups=c.repo_groups_choices,
245 245 landing_revs=c.landing_revs_choices)()
246 246 try:
247 247 form_result = _form.to_python(dict(request.POST))
248 248 repo = repo_model.update(repo_name, **form_result)
249 249 invalidate_cache('get_repo_cached_%s' % repo_name)
250 250 h.flash(_('Repository %s updated successfully') % repo_name,
251 251 category='success')
252 252 changed_name = repo.repo_name
253 253 action_logger(self.rhodecode_user, 'admin_updated_repo',
254 254 changed_name, self.ip_addr, self.sa)
255 255 Session().commit()
256 256 except formencode.Invalid, errors:
257 257 defaults = self.__load_data(repo_name)
258 258 defaults.update(errors.value)
259 259 return htmlfill.render(
260 260 render('admin/repos/repo_edit.html'),
261 261 defaults=defaults,
262 262 errors=errors.error_dict or {},
263 263 prefix_error=False,
264 264 encoding="UTF-8")
265 265
266 266 except Exception:
267 267 log.error(traceback.format_exc())
268 268 h.flash(_('error occurred during update of repository %s') \
269 269 % repo_name, category='error')
270 270 return redirect(url('edit_repo', repo_name=changed_name))
271 271
272 272 @HasPermissionAllDecorator('hg.admin')
273 273 def delete(self, repo_name):
274 274 """
275 275 DELETE /repos/repo_name: Delete an existing item"""
276 276 # Forms posted to this method should contain a hidden field:
277 277 # <input type="hidden" name="_method" value="DELETE" />
278 278 # Or using helpers:
279 279 # h.form(url('repo', repo_name=ID),
280 280 # method='delete')
281 281 # url('repo', repo_name=ID)
282 282
283 283 repo_model = RepoModel()
284 284 repo = repo_model.get_by_repo_name(repo_name)
285 285 if not repo:
286 286 h.not_mapped_error(repo_name)
287 287 return redirect(url('repos'))
288 288 try:
289 289 _forks = repo.forks.count()
290 290 if _forks and request.POST.get('forks'):
291 291 do = request.POST['forks']
292 292 if do == 'detach_forks':
293 293 for r in repo.forks:
294 294 log.debug('Detaching fork %s from repo %s' % (r, repo))
295 295 r.fork = None
296 296 Session().add(r)
297 297 h.flash(_('detached %s forks') % _forks, category='success')
298 298 elif do == 'delete_forks':
299 299 for r in repo.forks:
300 300 log.debug('Deleting fork %s of repo %s' % (r, repo))
301 301 repo_model.delete(r)
302 302 h.flash(_('deleted %s forks') % _forks, category='success')
303 303 action_logger(self.rhodecode_user, 'admin_deleted_repo',
304 304 repo_name, self.ip_addr, self.sa)
305 305 repo_model.delete(repo)
306 306 invalidate_cache('get_repo_cached_%s' % repo_name)
307 307 h.flash(_('deleted repository %s') % repo_name, category='success')
308 308 Session().commit()
309 309 except IntegrityError, e:
310 310 if e.message.find('repositories_fork_id_fkey') != -1:
311 311 log.error(traceback.format_exc())
312 312 h.flash(_('Cannot delete %s it still contains attached '
313 313 'forks') % repo_name,
314 314 category='warning')
315 315 else:
316 316 log.error(traceback.format_exc())
317 317 h.flash(_('An error occurred during '
318 318 'deletion of %s') % repo_name,
319 319 category='error')
320 320
321 321 except Exception, e:
322 322 log.error(traceback.format_exc())
323 323 h.flash(_('An error occurred during deletion of %s') % repo_name,
324 324 category='error')
325 325
326 326 return redirect(url('repos'))
327 327
328 328 @HasRepoPermissionAllDecorator('repository.admin')
329 329 def delete_perm_user(self, repo_name):
330 330 """
331 331 DELETE an existing repository permission user
332 332
333 333 :param repo_name:
334 334 """
335 335 try:
336 336 RepoModel().revoke_user_permission(repo=repo_name,
337 337 user=request.POST['user_id'])
338 338 Session().commit()
339 339 except Exception:
340 340 log.error(traceback.format_exc())
341 341 h.flash(_('An error occurred during deletion of repository user'),
342 342 category='error')
343 343 raise HTTPInternalServerError()
344 344
345 345 @HasRepoPermissionAllDecorator('repository.admin')
346 346 def delete_perm_users_group(self, repo_name):
347 347 """
348 DELETE an existing repository permission users group
348 DELETE an existing repository permission user group
349 349
350 350 :param repo_name:
351 351 """
352 352
353 353 try:
354 354 RepoModel().revoke_users_group_permission(
355 355 repo=repo_name, group_name=request.POST['users_group_id']
356 356 )
357 357 Session().commit()
358 358 except Exception:
359 359 log.error(traceback.format_exc())
360 360 h.flash(_('An error occurred during deletion of repository'
361 ' users groups'),
361 ' user groups'),
362 362 category='error')
363 363 raise HTTPInternalServerError()
364 364
365 365 @HasPermissionAllDecorator('hg.admin')
366 366 def repo_stats(self, repo_name):
367 367 """
368 368 DELETE an existing repository statistics
369 369
370 370 :param repo_name:
371 371 """
372 372
373 373 try:
374 374 RepoModel().delete_stats(repo_name)
375 375 Session().commit()
376 376 except Exception, e:
377 377 log.error(traceback.format_exc())
378 378 h.flash(_('An error occurred during deletion of repository stats'),
379 379 category='error')
380 380 return redirect(url('edit_repo', repo_name=repo_name))
381 381
382 382 @HasPermissionAllDecorator('hg.admin')
383 383 def repo_cache(self, repo_name):
384 384 """
385 385 INVALIDATE existing repository cache
386 386
387 387 :param repo_name:
388 388 """
389 389
390 390 try:
391 391 ScmModel().mark_for_invalidation(repo_name)
392 392 Session().commit()
393 393 except Exception, e:
394 394 log.error(traceback.format_exc())
395 395 h.flash(_('An error occurred during cache invalidation'),
396 396 category='error')
397 397 return redirect(url('edit_repo', repo_name=repo_name))
398 398
399 399 @HasPermissionAllDecorator('hg.admin')
400 400 def repo_locking(self, repo_name):
401 401 """
402 402 Unlock repository when it is locked !
403 403
404 404 :param repo_name:
405 405 """
406 406
407 407 try:
408 408 repo = Repository.get_by_repo_name(repo_name)
409 409 if request.POST.get('set_lock'):
410 410 Repository.lock(repo, c.rhodecode_user.user_id)
411 411 elif request.POST.get('set_unlock'):
412 412 Repository.unlock(repo)
413 413 except Exception, e:
414 414 log.error(traceback.format_exc())
415 415 h.flash(_('An error occurred during unlocking'),
416 416 category='error')
417 417 return redirect(url('edit_repo', repo_name=repo_name))
418 418
419 419 @HasPermissionAllDecorator('hg.admin')
420 420 def repo_public_journal(self, repo_name):
421 421 """
422 422 Set's this repository to be visible in public journal,
423 423 in other words assing default user to follow this repo
424 424
425 425 :param repo_name:
426 426 """
427 427
428 428 cur_token = request.POST.get('auth_token')
429 429 token = get_token()
430 430 if cur_token == token:
431 431 try:
432 432 repo_id = Repository.get_by_repo_name(repo_name).repo_id
433 433 user_id = User.get_by_username('default').user_id
434 434 self.scm_model.toggle_following_repo(repo_id, user_id)
435 435 h.flash(_('Updated repository visibility in public journal'),
436 436 category='success')
437 437 Session().commit()
438 438 except:
439 439 h.flash(_('An error occurred during setting this'
440 440 ' repository in public journal'),
441 441 category='error')
442 442
443 443 else:
444 444 h.flash(_('Token mismatch'), category='error')
445 445 return redirect(url('edit_repo', repo_name=repo_name))
446 446
447 447 @HasPermissionAllDecorator('hg.admin')
448 448 def repo_pull(self, repo_name):
449 449 """
450 450 Runs task to update given repository with remote changes,
451 451 ie. make pull on remote location
452 452
453 453 :param repo_name:
454 454 """
455 455 try:
456 456 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
457 457 h.flash(_('Pulled from remote location'), category='success')
458 458 except Exception, e:
459 459 h.flash(_('An error occurred during pull from remote location'),
460 460 category='error')
461 461
462 462 return redirect(url('edit_repo', repo_name=repo_name))
463 463
464 464 @HasPermissionAllDecorator('hg.admin')
465 465 def repo_as_fork(self, repo_name):
466 466 """
467 467 Mark given repository as a fork of another
468 468
469 469 :param repo_name:
470 470 """
471 471 try:
472 472 fork_id = request.POST.get('id_fork_of')
473 473 repo = ScmModel().mark_as_fork(repo_name, fork_id,
474 474 self.rhodecode_user.username)
475 475 fork = repo.fork.repo_name if repo.fork else _('Nothing')
476 476 Session().commit()
477 477 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
478 478 category='success')
479 479 except Exception, e:
480 480 log.error(traceback.format_exc())
481 481 h.flash(_('An error occurred during this operation'),
482 482 category='error')
483 483
484 484 return redirect(url('edit_repo', repo_name=repo_name))
485 485
486 486 @HasPermissionAllDecorator('hg.admin')
487 487 def show(self, repo_name, format='html'):
488 488 """GET /repos/repo_name: Show a specific item"""
489 489 # url('repo', repo_name=ID)
490 490
491 491 @HasPermissionAllDecorator('hg.admin')
492 492 def edit(self, repo_name, format='html'):
493 493 """GET /repos/repo_name/edit: Form to edit an existing item"""
494 494 # url('edit_repo', repo_name=ID)
495 495 defaults = self.__load_data(repo_name)
496 496
497 497 return htmlfill.render(
498 498 render('admin/repos/repo_edit.html'),
499 499 defaults=defaults,
500 500 encoding="UTF-8",
501 501 force_defaults=False
502 502 )
503 503
504 504 @HasPermissionAllDecorator('hg.admin')
505 505 def create_repo_field(self, repo_name):
506 506 try:
507 507 form_result = RepoFieldForm()().to_python(dict(request.POST))
508 508 new_field = RepositoryField()
509 509 new_field.repository = Repository.get_by_repo_name(repo_name)
510 510 new_field.field_key = form_result['new_field_key']
511 511 new_field.field_type = form_result['new_field_type'] # python type
512 512 new_field.field_value = form_result['new_field_value'] # set initial blank value
513 513 new_field.field_desc = form_result['new_field_desc']
514 514 new_field.field_label = form_result['new_field_label']
515 515 Session().add(new_field)
516 516 Session().commit()
517 517
518 518 except Exception, e:
519 519 log.error(traceback.format_exc())
520 520 msg = _('An error occurred during creation of field')
521 521 if isinstance(e, formencode.Invalid):
522 522 msg += ". " + e.msg
523 523 h.flash(msg, category='error')
524 524 return redirect(url('edit_repo', repo_name=repo_name))
525 525
526 526 @HasPermissionAllDecorator('hg.admin')
527 527 def delete_repo_field(self, repo_name, field_id):
528 528 field = RepositoryField.get_or_404(field_id)
529 529 try:
530 530 Session().delete(field)
531 531 Session().commit()
532 532 except Exception, e:
533 533 log.error(traceback.format_exc())
534 534 msg = _('An error occurred during removal of field')
535 535 h.flash(msg, category='error')
536 536 return redirect(url('edit_repo', repo_name=repo_name))
@@ -1,398 +1,398 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 Repositories groups controller for RhodeCode
6 Repository groups controller for RhodeCode
7 7
8 8 :created_on: Mar 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import request, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from sqlalchemy.exc import IntegrityError
37 37
38 38 import rhodecode
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.ext_json import json
41 41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 43 HasPermissionAll
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.model.db import RepoGroup, Repository
46 46 from rhodecode.model.repos_group import ReposGroupModel
47 47 from rhodecode.model.forms import ReposGroupForm
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo import RepoModel
50 50 from webob.exc import HTTPInternalServerError, HTTPNotFound
51 51 from rhodecode.lib.utils2 import str2bool, safe_int
52 52 from sqlalchemy.sql.expression import func
53 53 from rhodecode.model.scm import GroupList
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class ReposGroupsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('repos_group', 'repos_groups')
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 super(ReposGroupsController, self).__before__()
67 67
68 68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
69 69 if HasPermissionAll('hg.admin')('group edit'):
70 70 #we're global admin, we're ok and we can create TOP level groups
71 71 allow_empty_group = True
72 72
73 73 #override the choices for this form, we need to filter choices
74 74 #and display only those we have ADMIN right
75 75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 76 perm_set=['group.admin'])
77 77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 78 show_empty_group=allow_empty_group)
79 79 # exclude filtered ids
80 80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 81 c.repo_groups)
82 82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
83 83 repo_model = RepoModel()
84 84 c.users_array = repo_model.get_users_js()
85 85 c.users_groups_array = repo_model.get_users_groups_js()
86 86
87 87 def __load_data(self, group_id):
88 88 """
89 89 Load defaults settings for edit, and update
90 90
91 91 :param group_id:
92 92 """
93 93 repo_group = RepoGroup.get_or_404(group_id)
94 94 data = repo_group.get_dict()
95 95 data['group_name'] = repo_group.name
96 96
97 97 # fill repository users
98 98 for p in repo_group.repo_group_to_perm:
99 99 data.update({'u_perm_%s' % p.user.username:
100 100 p.permission.permission_name})
101 101
102 102 # fill repository groups
103 103 for p in repo_group.users_group_to_perm:
104 104 data.update({'g_perm_%s' % p.users_group.users_group_name:
105 105 p.permission.permission_name})
106 106
107 107 return data
108 108
109 109 def _revoke_perms_on_yourself(self, form_result):
110 110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
111 111 form_result['perms_updates'])
112 112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
113 113 form_result['perms_new'])
114 114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
115 115 return True
116 116 return False
117 117
118 118 def index(self, format='html'):
119 119 """GET /repos_groups: All items in the collection"""
120 120 # url('repos_groups')
121 121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
122 122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
123 123 c.groups = sorted(group_iter, key=sk)
124 124 return render('admin/repos_groups/repos_groups_show.html')
125 125
126 126 def create(self):
127 127 """POST /repos_groups: Create a new item"""
128 128 # url('repos_groups')
129 129
130 130 self.__load_defaults()
131 131
132 132 # permissions for can create group based on parent_id are checked
133 133 # here in the Form
134 134 repos_group_form = ReposGroupForm(available_groups=
135 135 map(lambda k: unicode(k[0]), c.repo_groups))()
136 136 try:
137 137 form_result = repos_group_form.to_python(dict(request.POST))
138 138 ReposGroupModel().create(
139 139 group_name=form_result['group_name'],
140 140 group_description=form_result['group_description'],
141 141 parent=form_result['group_parent_id'],
142 142 owner=self.rhodecode_user.user_id
143 143 )
144 144 Session().commit()
145 145 h.flash(_('created repos group %s') \
146 146 % form_result['group_name'], category='success')
147 147 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 148 except formencode.Invalid, errors:
149 149 return htmlfill.render(
150 150 render('admin/repos_groups/repos_groups_add.html'),
151 151 defaults=errors.value,
152 152 errors=errors.error_dict or {},
153 153 prefix_error=False,
154 154 encoding="UTF-8")
155 155 except Exception:
156 156 log.error(traceback.format_exc())
157 157 h.flash(_('error occurred during creation of repos group %s') \
158 158 % request.POST.get('group_name'), category='error')
159 159 parent_group_id = form_result['group_parent_id']
160 160 #TODO: maybe we should get back to the main view, not the admin one
161 161 return redirect(url('repos_groups', parent_group=parent_group_id))
162 162
163 163 def new(self, format='html'):
164 164 """GET /repos_groups/new: Form to create a new item"""
165 165 # url('new_repos_group')
166 166 if HasPermissionAll('hg.admin')('group create'):
167 167 #we're global admin, we're ok and we can create TOP level groups
168 168 pass
169 169 else:
170 170 # we pass in parent group into creation form, thus we know
171 171 # what would be the group, we can check perms here !
172 172 group_id = safe_int(request.GET.get('parent_group'))
173 173 group = RepoGroup.get(group_id) if group_id else None
174 174 group_name = group.group_name if group else None
175 175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
176 176 pass
177 177 else:
178 178 return abort(403)
179 179
180 180 self.__load_defaults()
181 181 return render('admin/repos_groups/repos_groups_add.html')
182 182
183 183 @HasReposGroupPermissionAnyDecorator('group.admin')
184 184 def update(self, group_name):
185 185 """PUT /repos_groups/group_name: Update an existing item"""
186 186 # Forms posted to this method should contain a hidden field:
187 187 # <input type="hidden" name="_method" value="PUT" />
188 188 # Or using helpers:
189 189 # h.form(url('repos_group', group_name=GROUP_NAME),
190 190 # method='put')
191 191 # url('repos_group', group_name=GROUP_NAME)
192 192
193 193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
194 194 if HasPermissionAll('hg.admin')('group edit'):
195 195 #we're global admin, we're ok and we can create TOP level groups
196 196 allow_empty_group = True
197 197 elif not c.repos_group.parent_group:
198 198 allow_empty_group = True
199 199 else:
200 200 allow_empty_group = False
201 201 self.__load_defaults(allow_empty_group=allow_empty_group,
202 202 exclude_group_ids=[c.repos_group.group_id])
203 203
204 204 repos_group_form = ReposGroupForm(
205 205 edit=True,
206 206 old_data=c.repos_group.get_dict(),
207 207 available_groups=c.repo_groups_choices,
208 208 can_create_in_root=allow_empty_group,
209 209 )()
210 210 try:
211 211 form_result = repos_group_form.to_python(dict(request.POST))
212 212 if not c.rhodecode_user.is_admin:
213 213 if self._revoke_perms_on_yourself(form_result):
214 214 msg = _('Cannot revoke permission for yourself as admin')
215 215 h.flash(msg, category='warning')
216 216 raise Exception('revoke admin permission on self')
217 217
218 218 new_gr = ReposGroupModel().update(group_name, form_result)
219 219 Session().commit()
220 220 h.flash(_('updated repos group %s') \
221 221 % form_result['group_name'], category='success')
222 222 # we now have new name !
223 223 group_name = new_gr.group_name
224 224 #TODO: in future action_logger(, '', '', '', self.sa)
225 225 except formencode.Invalid, errors:
226 226
227 227 return htmlfill.render(
228 228 render('admin/repos_groups/repos_groups_edit.html'),
229 229 defaults=errors.value,
230 230 errors=errors.error_dict or {},
231 231 prefix_error=False,
232 232 encoding="UTF-8")
233 233 except Exception:
234 234 log.error(traceback.format_exc())
235 235 h.flash(_('error occurred during update of repos group %s') \
236 236 % request.POST.get('group_name'), category='error')
237 237
238 238 return redirect(url('edit_repos_group', group_name=group_name))
239 239
240 240 @HasReposGroupPermissionAnyDecorator('group.admin')
241 241 def delete(self, group_name):
242 242 """DELETE /repos_groups/group_name: Delete an existing item"""
243 243 # Forms posted to this method should contain a hidden field:
244 244 # <input type="hidden" name="_method" value="DELETE" />
245 245 # Or using helpers:
246 246 # h.form(url('repos_group', group_name=GROUP_NAME),
247 247 # method='delete')
248 248 # url('repos_group', group_name=GROUP_NAME)
249 249
250 250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
251 251 repos = gr.repositories.all()
252 252 if repos:
253 253 h.flash(_('This group contains %s repositores and cannot be '
254 254 'deleted') % len(repos),
255 255 category='error')
256 256 return redirect(url('repos_groups'))
257 257
258 258 try:
259 259 ReposGroupModel().delete(group_name)
260 260 Session().commit()
261 261 h.flash(_('removed repos group %s') % gr.group_name,
262 262 category='success')
263 263 #TODO: in future action_logger(, '', '', '', self.sa)
264 264 except IntegrityError, e:
265 265 if str(e.message).find('groups_group_parent_id_fkey') != -1:
266 266 log.error(traceback.format_exc())
267 267 h.flash(_('Cannot delete this group it still contains '
268 268 'subgroups'),
269 269 category='warning')
270 270 else:
271 271 log.error(traceback.format_exc())
272 272 h.flash(_('error occurred during deletion of repos '
273 273 'group %s') % gr.group_name, category='error')
274 274
275 275 except Exception:
276 276 log.error(traceback.format_exc())
277 277 h.flash(_('error occurred during deletion of repos '
278 278 'group %s') % gr.group_name, category='error')
279 279
280 280 return redirect(url('repos_groups'))
281 281
282 282 @HasReposGroupPermissionAnyDecorator('group.admin')
283 283 def delete_repos_group_user_perm(self, group_name):
284 284 """
285 DELETE an existing repositories group permission user
285 DELETE an existing repository group permission user
286 286
287 287 :param group_name:
288 288 """
289 289 try:
290 290 if not c.rhodecode_user.is_admin:
291 291 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
292 292 msg = _('Cannot revoke permission for yourself as admin')
293 293 h.flash(msg, category='warning')
294 294 raise Exception('revoke admin permission on self')
295 295 recursive = str2bool(request.POST.get('recursive', False))
296 296 ReposGroupModel().delete_permission(
297 297 repos_group=group_name, obj=request.POST['user_id'],
298 298 obj_type='user', recursive=recursive
299 299 )
300 300 Session().commit()
301 301 except Exception:
302 302 log.error(traceback.format_exc())
303 303 h.flash(_('An error occurred during deletion of group user'),
304 304 category='error')
305 305 raise HTTPInternalServerError()
306 306
307 307 @HasReposGroupPermissionAnyDecorator('group.admin')
308 308 def delete_repos_group_users_group_perm(self, group_name):
309 309 """
310 DELETE an existing repositories group permission users group
310 DELETE an existing repository group permission user group
311 311
312 312 :param group_name:
313 313 """
314 314
315 315 try:
316 316 recursive = str2bool(request.POST.get('recursive', False))
317 317 ReposGroupModel().delete_permission(
318 318 repos_group=group_name, obj=request.POST['users_group_id'],
319 319 obj_type='users_group', recursive=recursive
320 320 )
321 321 Session().commit()
322 322 except Exception:
323 323 log.error(traceback.format_exc())
324 324 h.flash(_('An error occurred during deletion of group'
325 ' users groups'),
325 ' user groups'),
326 326 category='error')
327 327 raise HTTPInternalServerError()
328 328
329 329 def show_by_name(self, group_name):
330 330 """
331 331 This is a proxy that does a lookup group_name -> id, and shows
332 332 the group by id view instead
333 333 """
334 334 group_name = group_name.rstrip('/')
335 335 id_ = RepoGroup.get_by_group_name(group_name)
336 336 if id_:
337 337 return self.show(id_.group_id)
338 338 raise HTTPNotFound
339 339
340 340 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
341 341 'group.admin')
342 342 def show(self, group_name, format='html'):
343 343 """GET /repos_groups/group_name: Show a specific item"""
344 344 # url('repos_group', group_name=GROUP_NAME)
345 345
346 346 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
347 347 c.group_repos = c.group.repositories.all()
348 348
349 349 #overwrite our cached list with current filter
350 350 gr_filter = c.group_repos
351 351 c.repo_cnt = 0
352 352
353 353 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
354 354 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
355 355 c.groups = self.scm_model.get_repos_groups(groups)
356 356
357 357 if c.visual.lightweight_dashboard is False:
358 358 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
359 359 ## lightweight version of dashboard
360 360 else:
361 361 c.repos_list = Repository.query()\
362 362 .filter(Repository.group_id == c.group.group_id)\
363 363 .order_by(func.lower(Repository.repo_name))\
364 364 .all()
365 365
366 366 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
367 367 admin=False)
368 368 #json used to render the grid
369 369 c.data = json.dumps(repos_data)
370 370
371 371 return render('admin/repos_groups/repos_groups.html')
372 372
373 373 @HasReposGroupPermissionAnyDecorator('group.admin')
374 374 def edit(self, group_name, format='html'):
375 375 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
376 376 # url('edit_repos_group', group_name=GROUP_NAME)
377 377
378 378 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
379 379 #we can only allow moving empty group if it's already a top-level
380 380 #group, ie has no parents, or we're admin
381 381 if HasPermissionAll('hg.admin')('group edit'):
382 382 #we're global admin, we're ok and we can create TOP level groups
383 383 allow_empty_group = True
384 384 elif not c.repos_group.parent_group:
385 385 allow_empty_group = True
386 386 else:
387 387 allow_empty_group = False
388 388
389 389 self.__load_defaults(allow_empty_group=allow_empty_group,
390 390 exclude_group_ids=[c.repos_group.group_id])
391 391 defaults = self.__load_data(c.repos_group.group_id)
392 392
393 393 return htmlfill.render(
394 394 render('admin/repos_groups/repos_groups_edit.html'),
395 395 defaults=defaults,
396 396 encoding="UTF-8",
397 397 force_defaults=False
398 398 )
@@ -1,282 +1,282 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 Users Groups crud controller for pylons
6 User Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31 from pylons import request, session, tmpl_context as c, url, config
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.exceptions import UsersGroupsAssignedException
37 37 from rhodecode.lib.utils2 import safe_unicode, str2bool
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 39 from rhodecode.lib.base import BaseController, render
40 40
41 41 from rhodecode.model.users_group import UsersGroupModel
42 42
43 43 from rhodecode.model.db import User, UsersGroup, UsersGroupToPerm,\
44 44 UsersGroupRepoToPerm, UsersGroupRepoGroupToPerm
45 45 from rhodecode.model.forms import UsersGroupForm
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.lib.utils import action_logger
48 48 from sqlalchemy.orm import joinedload
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class UsersGroupsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('users_group', 'users_groups')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAllDecorator('hg.admin')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(UsersGroupsController, self).__before__()
65 65 c.available_permissions = config['available_permissions']
66 66
67 67 def index(self, format='html'):
68 68 """GET /users_groups: All items in the collection"""
69 69 # url('users_groups')
70 70 c.users_groups_list = UsersGroup().query().all()
71 71 return render('admin/users_groups/users_groups.html')
72 72
73 73 def create(self):
74 74 """POST /users_groups: Create a new item"""
75 75 # url('users_groups')
76 76
77 77 users_group_form = UsersGroupForm()()
78 78 try:
79 79 form_result = users_group_form.to_python(dict(request.POST))
80 80 UsersGroupModel().create(name=form_result['users_group_name'],
81 81 active=form_result['users_group_active'])
82 82 gr = form_result['users_group_name']
83 83 action_logger(self.rhodecode_user,
84 84 'admin_created_users_group:%s' % gr,
85 85 None, self.ip_addr, self.sa)
86 h.flash(_('created users group %s') % gr, category='success')
86 h.flash(_('created user group %s') % gr, category='success')
87 87 Session().commit()
88 88 except formencode.Invalid, errors:
89 89 return htmlfill.render(
90 90 render('admin/users_groups/users_group_add.html'),
91 91 defaults=errors.value,
92 92 errors=errors.error_dict or {},
93 93 prefix_error=False,
94 94 encoding="UTF-8")
95 95 except Exception:
96 96 log.error(traceback.format_exc())
97 h.flash(_('error occurred during creation of users group %s') \
97 h.flash(_('error occurred during creation of user group %s') \
98 98 % request.POST.get('users_group_name'), category='error')
99 99
100 100 return redirect(url('users_groups'))
101 101
102 102 def new(self, format='html'):
103 103 """GET /users_groups/new: Form to create a new item"""
104 104 # url('new_users_group')
105 105 return render('admin/users_groups/users_group_add.html')
106 106
107 107 def _load_data(self, id):
108 108 c.users_group.permissions = {
109 109 'repositories': {},
110 110 'repositories_groups': {}
111 111 }
112 112
113 113 ugroup_repo_perms = UsersGroupRepoToPerm.query()\
114 114 .options(joinedload(UsersGroupRepoToPerm.permission))\
115 115 .options(joinedload(UsersGroupRepoToPerm.repository))\
116 116 .filter(UsersGroupRepoToPerm.users_group_id == id)\
117 117 .all()
118 118
119 119 for gr in ugroup_repo_perms:
120 120 c.users_group.permissions['repositories'][gr.repository.repo_name] \
121 121 = gr.permission.permission_name
122 122
123 123 ugroup_group_perms = UsersGroupRepoGroupToPerm.query()\
124 124 .options(joinedload(UsersGroupRepoGroupToPerm.permission))\
125 125 .options(joinedload(UsersGroupRepoGroupToPerm.group))\
126 126 .filter(UsersGroupRepoGroupToPerm.users_group_id == id)\
127 127 .all()
128 128
129 129 for gr in ugroup_group_perms:
130 130 c.users_group.permissions['repositories_groups'][gr.group.group_name] \
131 131 = gr.permission.permission_name
132 132
133 133 c.group_members_obj = [x.user for x in c.users_group.members]
134 134 c.group_members = [(x.user_id, x.username) for x in
135 135 c.group_members_obj]
136 136 c.available_members = [(x.user_id, x.username) for x in
137 137 User.query().all()]
138 138
139 139 def update(self, id):
140 140 """PUT /users_groups/id: Update an existing item"""
141 141 # Forms posted to this method should contain a hidden field:
142 142 # <input type="hidden" name="_method" value="PUT" />
143 143 # Or using helpers:
144 144 # h.form(url('users_group', id=ID),
145 145 # method='put')
146 146 # url('users_group', id=ID)
147 147
148 148 c.users_group = UsersGroup.get_or_404(id)
149 149 self._load_data(id)
150 150
151 151 available_members = [safe_unicode(x[0]) for x in c.available_members]
152 152
153 153 users_group_form = UsersGroupForm(edit=True,
154 154 old_data=c.users_group.get_dict(),
155 155 available_members=available_members)()
156 156
157 157 try:
158 158 form_result = users_group_form.to_python(request.POST)
159 159 UsersGroupModel().update(c.users_group, form_result)
160 160 gr = form_result['users_group_name']
161 161 action_logger(self.rhodecode_user,
162 162 'admin_updated_users_group:%s' % gr,
163 163 None, self.ip_addr, self.sa)
164 h.flash(_('updated users group %s') % gr, category='success')
164 h.flash(_('updated user group %s') % gr, category='success')
165 165 Session().commit()
166 166 except formencode.Invalid, errors:
167 167 ug_model = UsersGroupModel()
168 168 defaults = errors.value
169 169 e = errors.error_dict or {}
170 170 defaults.update({
171 171 'create_repo_perm': ug_model.has_perm(id,
172 172 'hg.create.repository'),
173 173 'fork_repo_perm': ug_model.has_perm(id,
174 174 'hg.fork.repository'),
175 175 '_method': 'put'
176 176 })
177 177
178 178 return htmlfill.render(
179 179 render('admin/users_groups/users_group_edit.html'),
180 180 defaults=defaults,
181 181 errors=e,
182 182 prefix_error=False,
183 183 encoding="UTF-8")
184 184 except Exception:
185 185 log.error(traceback.format_exc())
186 h.flash(_('error occurred during update of users group %s') \
186 h.flash(_('error occurred during update of user group %s') \
187 187 % request.POST.get('users_group_name'), category='error')
188 188
189 189 return redirect(url('edit_users_group', id=id))
190 190
191 191 def delete(self, id):
192 192 """DELETE /users_groups/id: Delete an existing item"""
193 193 # Forms posted to this method should contain a hidden field:
194 194 # <input type="hidden" name="_method" value="DELETE" />
195 195 # Or using helpers:
196 196 # h.form(url('users_group', id=ID),
197 197 # method='delete')
198 198 # url('users_group', id=ID)
199 199 usr_gr = UsersGroup.get_or_404(id)
200 200 try:
201 201 UsersGroupModel().delete(usr_gr)
202 202 Session().commit()
203 h.flash(_('successfully deleted users group'), category='success')
203 h.flash(_('successfully deleted user group'), category='success')
204 204 except UsersGroupsAssignedException, e:
205 205 h.flash(e, category='error')
206 206 except Exception:
207 207 log.error(traceback.format_exc())
208 h.flash(_('An error occurred during deletion of users group'),
208 h.flash(_('An error occurred during deletion of user group'),
209 209 category='error')
210 210 return redirect(url('users_groups'))
211 211
212 212 def show(self, id, format='html'):
213 213 """GET /users_groups/id: Show a specific item"""
214 214 # url('users_group', id=ID)
215 215
216 216 def edit(self, id, format='html'):
217 217 """GET /users_groups/id/edit: Form to edit an existing item"""
218 218 # url('edit_users_group', id=ID)
219 219
220 220 c.users_group = UsersGroup.get_or_404(id)
221 221 self._load_data(id)
222 222
223 223 ug_model = UsersGroupModel()
224 224 defaults = c.users_group.get_dict()
225 225 defaults.update({
226 226 'create_repo_perm': ug_model.has_perm(c.users_group,
227 227 'hg.create.repository'),
228 228 'fork_repo_perm': ug_model.has_perm(c.users_group,
229 229 'hg.fork.repository'),
230 230 })
231 231
232 232 return htmlfill.render(
233 233 render('admin/users_groups/users_group_edit.html'),
234 234 defaults=defaults,
235 235 encoding="UTF-8",
236 236 force_defaults=False
237 237 )
238 238
239 239 def update_perm(self, id):
240 240 """PUT /users_perm/id: Update an existing item"""
241 241 # url('users_group_perm', id=ID, method='put')
242 242
243 243 users_group = UsersGroup.get_or_404(id)
244 244 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
245 245 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
246 246 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
247 247
248 248 usersgroup_model = UsersGroupModel()
249 249
250 250 try:
251 251 users_group.inherit_default_permissions = inherit_perms
252 252 Session().add(users_group)
253 253
254 254 if grant_create_perm:
255 255 usersgroup_model.revoke_perm(id, 'hg.create.none')
256 256 usersgroup_model.grant_perm(id, 'hg.create.repository')
257 h.flash(_("Granted 'repository create' permission to users group"),
257 h.flash(_("Granted 'repository create' permission to user group"),
258 258 category='success')
259 259 else:
260 260 usersgroup_model.revoke_perm(id, 'hg.create.repository')
261 261 usersgroup_model.grant_perm(id, 'hg.create.none')
262 h.flash(_("Revoked 'repository create' permission to users group"),
262 h.flash(_("Revoked 'repository create' permission to user group"),
263 263 category='success')
264 264
265 265 if grant_fork_perm:
266 266 usersgroup_model.revoke_perm(id, 'hg.fork.none')
267 267 usersgroup_model.grant_perm(id, 'hg.fork.repository')
268 h.flash(_("Granted 'repository fork' permission to users group"),
268 h.flash(_("Granted 'repository fork' permission to user group"),
269 269 category='success')
270 270 else:
271 271 usersgroup_model.revoke_perm(id, 'hg.fork.repository')
272 272 usersgroup_model.grant_perm(id, 'hg.fork.none')
273 h.flash(_("Revoked 'repository fork' permission to users group"),
273 h.flash(_("Revoked 'repository fork' permission to user group"),
274 274 category='success')
275 275
276 276 Session().commit()
277 277 except Exception:
278 278 log.error(traceback.format_exc())
279 279 h.flash(_('An error occurred during permissions saving'),
280 280 category='error')
281 281
282 282 return redirect(url('edit_users_group', id=id))
@@ -1,958 +1,958 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 API controller for RhodeCode
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import traceback
29 29 import logging
30 30 from pylons.controllers.util import abort
31 31
32 32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
33 33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
34 34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
35 35 HasPermissionAnyApi, HasRepoPermissionAnyApi
36 36 from rhodecode.lib.utils import map_groups, repo2db_mapper
37 37 from rhodecode.model.meta import Session
38 38 from rhodecode.model.scm import ScmModel
39 39 from rhodecode.model.repo import RepoModel
40 40 from rhodecode.model.user import UserModel
41 41 from rhodecode.model.users_group import UsersGroupModel
42 42 from rhodecode.model.permission import PermissionModel
43 43 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class OptionalAttr(object):
49 49 """
50 50 Special Optional Option that defines other attribute
51 51 """
52 52 def __init__(self, attr_name):
53 53 self.attr_name = attr_name
54 54
55 55 def __repr__(self):
56 56 return '<OptionalAttr:%s>' % self.attr_name
57 57
58 58 def __call__(self):
59 59 return self
60 60 #alias
61 61 OAttr = OptionalAttr
62 62
63 63
64 64 class Optional(object):
65 65 """
66 66 Defines an optional parameter::
67 67
68 68 param = param.getval() if isinstance(param, Optional) else param
69 69 param = param() if isinstance(param, Optional) else param
70 70
71 71 is equivalent of::
72 72
73 73 param = Optional.extract(param)
74 74
75 75 """
76 76 def __init__(self, type_):
77 77 self.type_ = type_
78 78
79 79 def __repr__(self):
80 80 return '<Optional:%s>' % self.type_.__repr__()
81 81
82 82 def __call__(self):
83 83 return self.getval()
84 84
85 85 def getval(self):
86 86 """
87 87 returns value from this Optional instance
88 88 """
89 89 return self.type_
90 90
91 91 @classmethod
92 92 def extract(cls, val):
93 93 if isinstance(val, cls):
94 94 return val.getval()
95 95 return val
96 96
97 97
98 98 def get_user_or_error(userid):
99 99 """
100 100 Get user by id or name or return JsonRPCError if not found
101 101
102 102 :param userid:
103 103 """
104 104 user = UserModel().get_user(userid)
105 105 if user is None:
106 106 raise JSONRPCError("user `%s` does not exist" % userid)
107 107 return user
108 108
109 109
110 110 def get_repo_or_error(repoid):
111 111 """
112 112 Get repo by id or name or return JsonRPCError if not found
113 113
114 114 :param userid:
115 115 """
116 116 repo = RepoModel().get_repo(repoid)
117 117 if repo is None:
118 118 raise JSONRPCError('repository `%s` does not exist' % (repoid))
119 119 return repo
120 120
121 121
122 122 def get_users_group_or_error(usersgroupid):
123 123 """
124 Get users group by id or name or return JsonRPCError if not found
124 Get user group by id or name or return JsonRPCError if not found
125 125
126 126 :param userid:
127 127 """
128 128 users_group = UsersGroupModel().get_group(usersgroupid)
129 129 if users_group is None:
130 raise JSONRPCError('users group `%s` does not exist' % usersgroupid)
130 raise JSONRPCError('user group `%s` does not exist' % usersgroupid)
131 131 return users_group
132 132
133 133
134 134 def get_perm_or_error(permid):
135 135 """
136 136 Get permission by id or name or return JsonRPCError if not found
137 137
138 138 :param userid:
139 139 """
140 140 perm = PermissionModel().get_permission_by_name(permid)
141 141 if perm is None:
142 142 raise JSONRPCError('permission `%s` does not exist' % (permid))
143 143 return perm
144 144
145 145
146 146 class ApiController(JSONRPCController):
147 147 """
148 148 API Controller
149 149
150 150
151 151 Each method needs to have USER as argument this is then based on given
152 152 API_KEY propagated as instance of user object
153 153
154 154 Preferably this should be first argument also
155 155
156 156
157 157 Each function should also **raise** JSONRPCError for any
158 158 errors that happens
159 159
160 160 """
161 161
162 162 @HasPermissionAllDecorator('hg.admin')
163 163 def pull(self, apiuser, repoid):
164 164 """
165 165 Dispatch pull action on given repo
166 166
167 167 :param apiuser:
168 168 :param repoid:
169 169 """
170 170
171 171 repo = get_repo_or_error(repoid)
172 172
173 173 try:
174 174 ScmModel().pull_changes(repo.repo_name,
175 175 self.rhodecode_user.username)
176 176 return 'Pulled from `%s`' % repo.repo_name
177 177 except Exception:
178 178 log.error(traceback.format_exc())
179 179 raise JSONRPCError(
180 180 'Unable to pull changes from `%s`' % repo.repo_name
181 181 )
182 182
183 183 @HasPermissionAllDecorator('hg.admin')
184 184 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
185 185 """
186 186 Dispatch rescan repositories action. If remove_obsolete is set
187 187 than also delete repos that are in database but not in the filesystem.
188 188 aka "clean zombies"
189 189
190 190 :param apiuser:
191 191 :param remove_obsolete:
192 192 """
193 193
194 194 try:
195 195 rm_obsolete = Optional.extract(remove_obsolete)
196 196 added, removed = repo2db_mapper(ScmModel().repo_scan(),
197 197 remove_obsolete=rm_obsolete)
198 198 return {'added': added, 'removed': removed}
199 199 except Exception:
200 200 log.error(traceback.format_exc())
201 201 raise JSONRPCError(
202 202 'Error occurred during rescan repositories action'
203 203 )
204 204
205 205 def invalidate_cache(self, apiuser, repoid):
206 206 """
207 207 Dispatch cache invalidation action on given repo
208 208
209 209 :param apiuser:
210 210 :param repoid:
211 211 """
212 212 repo = get_repo_or_error(repoid)
213 213 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
214 214 # check if we have admin permission for this repo !
215 215 if HasRepoPermissionAnyApi('repository.admin',
216 216 'repository.write')(user=apiuser,
217 217 repo_name=repo.repo_name) is False:
218 218 raise JSONRPCError('repository `%s` does not exist' % (repoid))
219 219
220 220 try:
221 221 invalidated_keys = ScmModel().mark_for_invalidation(repo.repo_name)
222 222 Session().commit()
223 223 return ('Cache for repository `%s` was invalidated: '
224 224 'invalidated cache keys: %s' % (repoid, invalidated_keys))
225 225 except Exception:
226 226 log.error(traceback.format_exc())
227 227 raise JSONRPCError(
228 228 'Error occurred during cache invalidation action'
229 229 )
230 230
231 231 def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
232 232 """
233 233 Set locking state on particular repository by given user, if
234 234 this command is runned by non-admin account userid is set to user
235 235 who is calling this method
236 236
237 237 :param apiuser:
238 238 :param repoid:
239 239 :param userid:
240 240 :param locked:
241 241 """
242 242 repo = get_repo_or_error(repoid)
243 243 if HasPermissionAnyApi('hg.admin')(user=apiuser):
244 244 pass
245 245 elif HasRepoPermissionAnyApi('repository.admin',
246 246 'repository.write')(user=apiuser,
247 247 repo_name=repo.repo_name):
248 248 #make sure normal user does not pass someone else userid,
249 249 #he is not allowed to do that
250 250 if not isinstance(userid, Optional) and userid != apiuser.user_id:
251 251 raise JSONRPCError(
252 252 'userid is not the same as your user'
253 253 )
254 254 else:
255 255 raise JSONRPCError('repository `%s` does not exist' % (repoid))
256 256
257 257 if isinstance(userid, Optional):
258 258 userid = apiuser.user_id
259 259 user = get_user_or_error(userid)
260 260 locked = bool(locked)
261 261 try:
262 262 if locked:
263 263 Repository.lock(repo, user.user_id)
264 264 else:
265 265 Repository.unlock(repo)
266 266
267 267 return ('User `%s` set lock state for repo `%s` to `%s`'
268 268 % (user.username, repo.repo_name, locked))
269 269 except Exception:
270 270 log.error(traceback.format_exc())
271 271 raise JSONRPCError(
272 272 'Error occurred locking repository `%s`' % repo.repo_name
273 273 )
274 274
275 275 @HasPermissionAllDecorator('hg.admin')
276 276 def show_ip(self, apiuser, userid):
277 277 """
278 278 Shows IP address as seen from RhodeCode server, together with all
279 279 defined IP addresses for given user
280 280
281 281 :param apiuser:
282 282 :param userid:
283 283 """
284 284 user = get_user_or_error(userid)
285 285 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
286 286 return dict(
287 287 ip_addr_server=self.ip_addr,
288 288 user_ips=ips
289 289 )
290 290
291 291 def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
292 292 """"
293 293 Get a user by username, or userid, if userid is given
294 294
295 295 :param apiuser:
296 296 :param userid:
297 297 """
298 298 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
299 299 #make sure normal user does not pass someone else userid,
300 300 #he is not allowed to do that
301 301 if not isinstance(userid, Optional) and userid != apiuser.user_id:
302 302 raise JSONRPCError(
303 303 'userid is not the same as your user'
304 304 )
305 305
306 306 if isinstance(userid, Optional):
307 307 userid = apiuser.user_id
308 308
309 309 user = get_user_or_error(userid)
310 310 data = user.get_api_data()
311 311 data['permissions'] = AuthUser(user_id=user.user_id).permissions
312 312 return data
313 313
314 314 @HasPermissionAllDecorator('hg.admin')
315 315 def get_users(self, apiuser):
316 316 """"
317 317 Get all users
318 318
319 319 :param apiuser:
320 320 """
321 321
322 322 result = []
323 323 for user in UserModel().get_all():
324 324 result.append(user.get_api_data())
325 325 return result
326 326
327 327 @HasPermissionAllDecorator('hg.admin')
328 328 def create_user(self, apiuser, username, email, password,
329 329 firstname=Optional(None), lastname=Optional(None),
330 330 active=Optional(True), admin=Optional(False),
331 331 ldap_dn=Optional(None)):
332 332 """
333 333 Create new user
334 334
335 335 :param apiuser:
336 336 :param username:
337 337 :param email:
338 338 :param password:
339 339 :param firstname:
340 340 :param lastname:
341 341 :param active:
342 342 :param admin:
343 343 :param ldap_dn:
344 344 """
345 345
346 346 if UserModel().get_by_username(username):
347 347 raise JSONRPCError("user `%s` already exist" % username)
348 348
349 349 if UserModel().get_by_email(email, case_insensitive=True):
350 350 raise JSONRPCError("email `%s` already exist" % email)
351 351
352 352 if Optional.extract(ldap_dn):
353 353 # generate temporary password if ldap_dn
354 354 password = PasswordGenerator().gen_password(length=8)
355 355
356 356 try:
357 357 user = UserModel().create_or_update(
358 358 username=Optional.extract(username),
359 359 password=Optional.extract(password),
360 360 email=Optional.extract(email),
361 361 firstname=Optional.extract(firstname),
362 362 lastname=Optional.extract(lastname),
363 363 active=Optional.extract(active),
364 364 admin=Optional.extract(admin),
365 365 ldap_dn=Optional.extract(ldap_dn)
366 366 )
367 367 Session().commit()
368 368 return dict(
369 369 msg='created new user `%s`' % username,
370 370 user=user.get_api_data()
371 371 )
372 372 except Exception:
373 373 log.error(traceback.format_exc())
374 374 raise JSONRPCError('failed to create user `%s`' % username)
375 375
376 376 @HasPermissionAllDecorator('hg.admin')
377 377 def update_user(self, apiuser, userid, username=Optional(None),
378 378 email=Optional(None), firstname=Optional(None),
379 379 lastname=Optional(None), active=Optional(None),
380 380 admin=Optional(None), ldap_dn=Optional(None),
381 381 password=Optional(None)):
382 382 """
383 383 Updates given user
384 384
385 385 :param apiuser:
386 386 :param userid:
387 387 :param username:
388 388 :param email:
389 389 :param firstname:
390 390 :param lastname:
391 391 :param active:
392 392 :param admin:
393 393 :param ldap_dn:
394 394 :param password:
395 395 """
396 396
397 397 user = get_user_or_error(userid)
398 398
399 399 # call function and store only updated arguments
400 400 updates = {}
401 401
402 402 def store_update(attr, name):
403 403 if not isinstance(attr, Optional):
404 404 updates[name] = attr
405 405
406 406 try:
407 407
408 408 store_update(username, 'username')
409 409 store_update(password, 'password')
410 410 store_update(email, 'email')
411 411 store_update(firstname, 'name')
412 412 store_update(lastname, 'lastname')
413 413 store_update(active, 'active')
414 414 store_update(admin, 'admin')
415 415 store_update(ldap_dn, 'ldap_dn')
416 416
417 417 user = UserModel().update_user(user, **updates)
418 418 Session().commit()
419 419 return dict(
420 420 msg='updated user ID:%s %s' % (user.user_id, user.username),
421 421 user=user.get_api_data()
422 422 )
423 423 except Exception:
424 424 log.error(traceback.format_exc())
425 425 raise JSONRPCError('failed to update user `%s`' % userid)
426 426
427 427 @HasPermissionAllDecorator('hg.admin')
428 428 def delete_user(self, apiuser, userid):
429 429 """"
430 430 Deletes an user
431 431
432 432 :param apiuser:
433 433 :param userid:
434 434 """
435 435 user = get_user_or_error(userid)
436 436
437 437 try:
438 438 UserModel().delete(userid)
439 439 Session().commit()
440 440 return dict(
441 441 msg='deleted user ID:%s %s' % (user.user_id, user.username),
442 442 user=None
443 443 )
444 444 except Exception:
445 445 log.error(traceback.format_exc())
446 446 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
447 447 user.username))
448 448
449 449 @HasPermissionAllDecorator('hg.admin')
450 450 def get_users_group(self, apiuser, usersgroupid):
451 451 """"
452 Get users group by name or id
452 Get user group by name or id
453 453
454 454 :param apiuser:
455 455 :param usersgroupid:
456 456 """
457 457 users_group = get_users_group_or_error(usersgroupid)
458 458
459 459 data = users_group.get_api_data()
460 460
461 461 members = []
462 462 for user in users_group.members:
463 463 user = user.user
464 464 members.append(user.get_api_data())
465 465 data['members'] = members
466 466 return data
467 467
468 468 @HasPermissionAllDecorator('hg.admin')
469 469 def get_users_groups(self, apiuser):
470 470 """"
471 Get all users groups
471 Get all user groups
472 472
473 473 :param apiuser:
474 474 """
475 475
476 476 result = []
477 477 for users_group in UsersGroupModel().get_all():
478 478 result.append(users_group.get_api_data())
479 479 return result
480 480
481 481 @HasPermissionAllDecorator('hg.admin')
482 482 def create_users_group(self, apiuser, group_name, active=Optional(True)):
483 483 """
484 484 Creates an new usergroup
485 485
486 486 :param apiuser:
487 487 :param group_name:
488 488 :param active:
489 489 """
490 490
491 491 if UsersGroupModel().get_by_name(group_name):
492 raise JSONRPCError("users group `%s` already exist" % group_name)
492 raise JSONRPCError("user group `%s` already exist" % group_name)
493 493
494 494 try:
495 495 active = Optional.extract(active)
496 496 ug = UsersGroupModel().create(name=group_name, active=active)
497 497 Session().commit()
498 498 return dict(
499 msg='created new users group `%s`' % group_name,
499 msg='created new user group `%s`' % group_name,
500 500 users_group=ug.get_api_data()
501 501 )
502 502 except Exception:
503 503 log.error(traceback.format_exc())
504 504 raise JSONRPCError('failed to create group `%s`' % group_name)
505 505
506 506 @HasPermissionAllDecorator('hg.admin')
507 507 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
508 508 """"
509 Add a user to a users group
509 Add a user to a user group
510 510
511 511 :param apiuser:
512 512 :param usersgroupid:
513 513 :param userid:
514 514 """
515 515 user = get_user_or_error(userid)
516 516 users_group = get_users_group_or_error(usersgroupid)
517 517
518 518 try:
519 519 ugm = UsersGroupModel().add_user_to_group(users_group, user)
520 520 success = True if ugm != True else False
521 msg = 'added member `%s` to users group `%s`' % (
521 msg = 'added member `%s` to user group `%s`' % (
522 522 user.username, users_group.users_group_name
523 523 )
524 524 msg = msg if success else 'User is already in that group'
525 525 Session().commit()
526 526
527 527 return dict(
528 528 success=success,
529 529 msg=msg
530 530 )
531 531 except Exception:
532 532 log.error(traceback.format_exc())
533 533 raise JSONRPCError(
534 'failed to add member to users group `%s`' % (
534 'failed to add member to user group `%s`' % (
535 535 users_group.users_group_name
536 536 )
537 537 )
538 538
539 539 @HasPermissionAllDecorator('hg.admin')
540 540 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
541 541 """
542 542 Remove user from a group
543 543
544 544 :param apiuser:
545 545 :param usersgroupid:
546 546 :param userid:
547 547 """
548 548 user = get_user_or_error(userid)
549 549 users_group = get_users_group_or_error(usersgroupid)
550 550
551 551 try:
552 552 success = UsersGroupModel().remove_user_from_group(users_group,
553 553 user)
554 msg = 'removed member `%s` from users group `%s`' % (
554 msg = 'removed member `%s` from user group `%s`' % (
555 555 user.username, users_group.users_group_name
556 556 )
557 557 msg = msg if success else "User wasn't in group"
558 558 Session().commit()
559 559 return dict(success=success, msg=msg)
560 560 except Exception:
561 561 log.error(traceback.format_exc())
562 562 raise JSONRPCError(
563 'failed to remove member from users group `%s`' % (
563 'failed to remove member from user group `%s`' % (
564 564 users_group.users_group_name
565 565 )
566 566 )
567 567
568 568 def get_repo(self, apiuser, repoid):
569 569 """"
570 570 Get repository by name
571 571
572 572 :param apiuser:
573 573 :param repoid:
574 574 """
575 575 repo = get_repo_or_error(repoid)
576 576
577 577 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
578 578 # check if we have admin permission for this repo !
579 579 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
580 580 repo_name=repo.repo_name) is False:
581 581 raise JSONRPCError('repository `%s` does not exist' % (repoid))
582 582
583 583 members = []
584 584 followers = []
585 585 for user in repo.repo_to_perm:
586 586 perm = user.permission.permission_name
587 587 user = user.user
588 588 user_data = user.get_api_data()
589 589 user_data['type'] = "user"
590 590 user_data['permission'] = perm
591 591 members.append(user_data)
592 592
593 593 for users_group in repo.users_group_to_perm:
594 594 perm = users_group.permission.permission_name
595 595 users_group = users_group.users_group
596 596 users_group_data = users_group.get_api_data()
597 597 users_group_data['type'] = "users_group"
598 598 users_group_data['permission'] = perm
599 599 members.append(users_group_data)
600 600
601 601 for user in repo.followers:
602 602 followers.append(user.user.get_api_data())
603 603
604 604 data = repo.get_api_data()
605 605 data['members'] = members
606 606 data['followers'] = followers
607 607 return data
608 608
609 609 def get_repos(self, apiuser):
610 610 """"
611 611 Get all repositories
612 612
613 613 :param apiuser:
614 614 """
615 615 result = []
616 616 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
617 617 repos = RepoModel().get_all_user_repos(user=apiuser)
618 618 else:
619 619 repos = RepoModel().get_all()
620 620
621 621 for repo in repos:
622 622 result.append(repo.get_api_data())
623 623 return result
624 624
625 625 @HasPermissionAllDecorator('hg.admin')
626 626 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
627 627 ret_type='all'):
628 628 """
629 629 returns a list of nodes and it's children
630 630 for a given path at given revision. It's possible to specify ret_type
631 631 to show only files or dirs
632 632
633 633 :param apiuser:
634 634 :param repoid: name or id of repository
635 635 :param revision: revision for which listing should be done
636 636 :param root_path: path from which start displaying
637 637 :param ret_type: return type 'all|files|dirs' nodes
638 638 """
639 639 repo = get_repo_or_error(repoid)
640 640 try:
641 641 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
642 642 flat=False)
643 643 _map = {
644 644 'all': _d + _f,
645 645 'files': _f,
646 646 'dirs': _d,
647 647 }
648 648 return _map[ret_type]
649 649 except KeyError:
650 650 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
651 651 except Exception:
652 652 log.error(traceback.format_exc())
653 653 raise JSONRPCError(
654 654 'failed to get repo: `%s` nodes' % repo.repo_name
655 655 )
656 656
657 657 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
658 658 def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
659 659 repo_type=Optional('hg'),
660 660 description=Optional(''), private=Optional(False),
661 661 clone_uri=Optional(None), landing_rev=Optional('tip'),
662 662 enable_statistics=Optional(False),
663 663 enable_locking=Optional(False),
664 664 enable_downloads=Optional(False)):
665 665 """
666 666 Create repository, if clone_url is given it makes a remote clone
667 667 if repo_name is within a group name the groups will be created
668 668 automatically if they aren't present
669 669
670 670 :param apiuser:
671 671 :param repo_name:
672 672 :param onwer:
673 673 :param repo_type:
674 674 :param description:
675 675 :param private:
676 676 :param clone_uri:
677 677 :param landing_rev:
678 678 """
679 679 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
680 680 if not isinstance(owner, Optional):
681 681 #forbid setting owner for non-admins
682 682 raise JSONRPCError(
683 683 'Only RhodeCode admin can specify `owner` param'
684 684 )
685 685 if isinstance(owner, Optional):
686 686 owner = apiuser.user_id
687 687
688 688 owner = get_user_or_error(owner)
689 689
690 690 if RepoModel().get_by_repo_name(repo_name):
691 691 raise JSONRPCError("repo `%s` already exist" % repo_name)
692 692
693 693 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
694 694 if isinstance(private, Optional):
695 695 private = defs.get('repo_private') or Optional.extract(private)
696 696 if isinstance(repo_type, Optional):
697 697 repo_type = defs.get('repo_type')
698 698 if isinstance(enable_statistics, Optional):
699 699 enable_statistics = defs.get('repo_enable_statistics')
700 700 if isinstance(enable_locking, Optional):
701 701 enable_locking = defs.get('repo_enable_locking')
702 702 if isinstance(enable_downloads, Optional):
703 703 enable_downloads = defs.get('repo_enable_downloads')
704 704
705 705 clone_uri = Optional.extract(clone_uri)
706 706 description = Optional.extract(description)
707 707 landing_rev = Optional.extract(landing_rev)
708 708
709 709 try:
710 710 # create structure of groups and return the last group
711 711 group = map_groups(repo_name)
712 712
713 713 repo = RepoModel().create_repo(
714 714 repo_name=repo_name,
715 715 repo_type=repo_type,
716 716 description=description,
717 717 owner=owner,
718 718 private=private,
719 719 clone_uri=clone_uri,
720 720 repos_group=group,
721 721 landing_rev=landing_rev,
722 722 enable_statistics=enable_statistics,
723 723 enable_downloads=enable_downloads,
724 724 enable_locking=enable_locking
725 725 )
726 726
727 727 Session().commit()
728 728 return dict(
729 729 msg="Created new repository `%s`" % (repo.repo_name),
730 730 repo=repo.get_api_data()
731 731 )
732 732 except Exception:
733 733 log.error(traceback.format_exc())
734 734 raise JSONRPCError('failed to create repository `%s`' % repo_name)
735 735
736 736 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
737 737 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
738 738 description=Optional(''), copy_permissions=Optional(False),
739 739 private=Optional(False), landing_rev=Optional('tip')):
740 740 repo = get_repo_or_error(repoid)
741 741 repo_name = repo.repo_name
742 742
743 743 _repo = RepoModel().get_by_repo_name(fork_name)
744 744 if _repo:
745 745 type_ = 'fork' if _repo.fork else 'repo'
746 746 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
747 747
748 748 if HasPermissionAnyApi('hg.admin')(user=apiuser):
749 749 pass
750 750 elif HasRepoPermissionAnyApi('repository.admin',
751 751 'repository.write',
752 752 'repository.read')(user=apiuser,
753 753 repo_name=repo.repo_name):
754 754 if not isinstance(owner, Optional):
755 755 #forbid setting owner for non-admins
756 756 raise JSONRPCError(
757 757 'Only RhodeCode admin can specify `owner` param'
758 758 )
759 759 else:
760 760 raise JSONRPCError('repository `%s` does not exist' % (repoid))
761 761
762 762 if isinstance(owner, Optional):
763 763 owner = apiuser.user_id
764 764
765 765 owner = get_user_or_error(owner)
766 766
767 767 try:
768 768 # create structure of groups and return the last group
769 769 group = map_groups(fork_name)
770 770
771 771 form_data = dict(
772 772 repo_name=fork_name,
773 773 repo_name_full=fork_name,
774 774 repo_group=group,
775 775 repo_type=repo.repo_type,
776 776 description=Optional.extract(description),
777 777 private=Optional.extract(private),
778 778 copy_permissions=Optional.extract(copy_permissions),
779 779 landing_rev=Optional.extract(landing_rev),
780 780 update_after_clone=False,
781 781 fork_parent_id=repo.repo_id,
782 782 )
783 783 RepoModel().create_fork(form_data, cur_user=owner)
784 784 return dict(
785 785 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
786 786 fork_name),
787 787 success=True # cannot return the repo data here since fork
788 788 # cann be done async
789 789 )
790 790 except Exception:
791 791 log.error(traceback.format_exc())
792 792 raise JSONRPCError(
793 793 'failed to fork repository `%s` as `%s`' % (repo_name,
794 794 fork_name)
795 795 )
796 796
797 797 def delete_repo(self, apiuser, repoid):
798 798 """
799 799 Deletes a given repository
800 800
801 801 :param apiuser:
802 802 :param repoid:
803 803 """
804 804 repo = get_repo_or_error(repoid)
805 805
806 806 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
807 807 # check if we have admin permission for this repo !
808 808 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
809 809 repo_name=repo.repo_name) is False:
810 810 raise JSONRPCError('repository `%s` does not exist' % (repoid))
811 811
812 812 try:
813 813 RepoModel().delete(repo)
814 814 Session().commit()
815 815 return dict(
816 816 msg='Deleted repository `%s`' % repo.repo_name,
817 817 success=True
818 818 )
819 819 except Exception:
820 820 log.error(traceback.format_exc())
821 821 raise JSONRPCError(
822 822 'failed to delete repository `%s`' % repo.repo_name
823 823 )
824 824
825 825 @HasPermissionAllDecorator('hg.admin')
826 826 def grant_user_permission(self, apiuser, repoid, userid, perm):
827 827 """
828 828 Grant permission for user on given repository, or update existing one
829 829 if found
830 830
831 831 :param repoid:
832 832 :param userid:
833 833 :param perm:
834 834 """
835 835 repo = get_repo_or_error(repoid)
836 836 user = get_user_or_error(userid)
837 837 perm = get_perm_or_error(perm)
838 838
839 839 try:
840 840
841 841 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
842 842
843 843 Session().commit()
844 844 return dict(
845 845 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
846 846 perm.permission_name, user.username, repo.repo_name
847 847 ),
848 848 success=True
849 849 )
850 850 except Exception:
851 851 log.error(traceback.format_exc())
852 852 raise JSONRPCError(
853 853 'failed to edit permission for user: `%s` in repo: `%s`' % (
854 854 userid, repoid
855 855 )
856 856 )
857 857
858 858 @HasPermissionAllDecorator('hg.admin')
859 859 def revoke_user_permission(self, apiuser, repoid, userid):
860 860 """
861 861 Revoke permission for user on given repository
862 862
863 863 :param apiuser:
864 864 :param repoid:
865 865 :param userid:
866 866 """
867 867
868 868 repo = get_repo_or_error(repoid)
869 869 user = get_user_or_error(userid)
870 870 try:
871 871
872 872 RepoModel().revoke_user_permission(repo=repo, user=user)
873 873
874 874 Session().commit()
875 875 return dict(
876 876 msg='Revoked perm for user: `%s` in repo: `%s`' % (
877 877 user.username, repo.repo_name
878 878 ),
879 879 success=True
880 880 )
881 881 except Exception:
882 882 log.error(traceback.format_exc())
883 883 raise JSONRPCError(
884 884 'failed to edit permission for user: `%s` in repo: `%s`' % (
885 885 userid, repoid
886 886 )
887 887 )
888 888
889 889 @HasPermissionAllDecorator('hg.admin')
890 890 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
891 891 perm):
892 892 """
893 Grant permission for users group on given repository, or update
893 Grant permission for user group on given repository, or update
894 894 existing one if found
895 895
896 896 :param apiuser:
897 897 :param repoid:
898 898 :param usersgroupid:
899 899 :param perm:
900 900 """
901 901 repo = get_repo_or_error(repoid)
902 902 perm = get_perm_or_error(perm)
903 903 users_group = get_users_group_or_error(usersgroupid)
904 904
905 905 try:
906 906 RepoModel().grant_users_group_permission(repo=repo,
907 907 group_name=users_group,
908 908 perm=perm)
909 909
910 910 Session().commit()
911 911 return dict(
912 msg='Granted perm: `%s` for users group: `%s` in '
912 msg='Granted perm: `%s` for user group: `%s` in '
913 913 'repo: `%s`' % (
914 914 perm.permission_name, users_group.users_group_name,
915 915 repo.repo_name
916 916 ),
917 917 success=True
918 918 )
919 919 except Exception:
920 920 log.error(traceback.format_exc())
921 921 raise JSONRPCError(
922 'failed to edit permission for users group: `%s` in '
922 'failed to edit permission for user group: `%s` in '
923 923 'repo: `%s`' % (
924 924 usersgroupid, repo.repo_name
925 925 )
926 926 )
927 927
928 928 @HasPermissionAllDecorator('hg.admin')
929 929 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
930 930 """
931 Revoke permission for users group on given repository
931 Revoke permission for user group on given repository
932 932
933 933 :param apiuser:
934 934 :param repoid:
935 935 :param usersgroupid:
936 936 """
937 937 repo = get_repo_or_error(repoid)
938 938 users_group = get_users_group_or_error(usersgroupid)
939 939
940 940 try:
941 941 RepoModel().revoke_users_group_permission(repo=repo,
942 942 group_name=users_group)
943 943
944 944 Session().commit()
945 945 return dict(
946 msg='Revoked perm for users group: `%s` in repo: `%s`' % (
946 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
947 947 users_group.users_group_name, repo.repo_name
948 948 ),
949 949 success=True
950 950 )
951 951 except Exception:
952 952 log.error(traceback.format_exc())
953 953 raise JSONRPCError(
954 'failed to edit permission for users group: `%s` in '
954 'failed to edit permission for user group: `%s` in '
955 955 'repo: `%s`' % (
956 956 users_group.users_group_name, repo.repo_name
957 957 )
958 958 )
@@ -1,426 +1,426 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 users groups model for RhodeCode
6 repo group model for RhodeCode
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import shutil
30 30 import datetime
31 31
32 32 from rhodecode.lib.utils2 import LazyProperty
33 33
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
36 36 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup, Repository
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class ReposGroupModel(BaseModel):
42 42
43 43 cls = RepoGroup
44 44
45 45 def __get_users_group(self, users_group):
46 46 return self._get_instance(UsersGroup, users_group,
47 47 callback=UsersGroup.get_by_group_name)
48 48
49 49 def _get_repos_group(self, repos_group):
50 50 return self._get_instance(RepoGroup, repos_group,
51 51 callback=RepoGroup.get_by_group_name)
52 52
53 53 @LazyProperty
54 54 def repos_path(self):
55 55 """
56 56 Get's the repositories root path from database
57 57 """
58 58
59 59 q = RhodeCodeUi.get_by_key('/')
60 60 return q.ui_value
61 61
62 62 def _create_default_perms(self, new_group):
63 63 # create default permission
64 64 repo_group_to_perm = UserRepoGroupToPerm()
65 65 default_perm = 'group.read'
66 66 for p in User.get_by_username('default').user_perms:
67 67 if p.permission.permission_name.startswith('group.'):
68 68 default_perm = p.permission.permission_name
69 69 break
70 70
71 71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
72 72 .filter(Permission.permission_name == default_perm)\
73 73 .one().permission_id
74 74
75 75 repo_group_to_perm.group = new_group
76 76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
77 77
78 78 self.sa.add(repo_group_to_perm)
79 79
80 80 def __create_group(self, group_name):
81 81 """
82 82 makes repositories group on filesystem
83 83
84 84 :param repo_name:
85 85 :param parent_id:
86 86 """
87 87
88 88 create_path = os.path.join(self.repos_path, group_name)
89 89 log.debug('creating new group in %s' % create_path)
90 90
91 91 if os.path.isdir(create_path):
92 92 raise Exception('That directory already exists !')
93 93
94 94 os.makedirs(create_path)
95 95
96 96 def __rename_group(self, old, new):
97 97 """
98 98 Renames a group on filesystem
99 99
100 100 :param group_name:
101 101 """
102 102
103 103 if old == new:
104 104 log.debug('skipping group rename')
105 105 return
106 106
107 107 log.debug('renaming repos group from %s to %s' % (old, new))
108 108
109 109 old_path = os.path.join(self.repos_path, old)
110 110 new_path = os.path.join(self.repos_path, new)
111 111
112 112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
113 113
114 114 if os.path.isdir(new_path):
115 115 raise Exception('Was trying to rename to already '
116 116 'existing dir %s' % new_path)
117 117 shutil.move(old_path, new_path)
118 118
119 119 def __delete_group(self, group, force_delete=False):
120 120 """
121 121 Deletes a group from a filesystem
122 122
123 123 :param group: instance of group from database
124 124 :param force_delete: use shutil rmtree to remove all objects
125 125 """
126 126 paths = group.full_path.split(RepoGroup.url_sep())
127 127 paths = os.sep.join(paths)
128 128
129 129 rm_path = os.path.join(self.repos_path, paths)
130 130 log.info("Removing group %s" % (rm_path))
131 131 # delete only if that path really exists
132 132 if os.path.isdir(rm_path):
133 133 if force_delete:
134 134 shutil.rmtree(rm_path)
135 135 else:
136 136 #archive that group`
137 137 _now = datetime.datetime.now()
138 138 _ms = str(_now.microsecond).rjust(6, '0')
139 139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
140 140 group.name)
141 141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
142 142
143 143 def create(self, group_name, group_description, owner, parent=None, just_db=False):
144 144 try:
145 145 new_repos_group = RepoGroup()
146 146 new_repos_group.group_description = group_description or group_name
147 147 new_repos_group.parent_group = self._get_repos_group(parent)
148 148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
149 149
150 150 self.sa.add(new_repos_group)
151 151 self._create_default_perms(new_repos_group)
152 152
153 153 #create an ADMIN permission for owner, later owner should go into
154 154 #the owner field of groups
155 155 self.grant_user_permission(repos_group=new_repos_group,
156 156 user=owner, perm='group.admin')
157 157
158 158 if not just_db:
159 159 # we need to flush here, in order to check if database won't
160 160 # throw any exceptions, create filesystem dirs at the very end
161 161 self.sa.flush()
162 162 self.__create_group(new_repos_group.group_name)
163 163
164 164 return new_repos_group
165 165 except:
166 166 log.error(traceback.format_exc())
167 167 raise
168 168
169 169 def _update_permissions(self, repos_group, perms_new=None,
170 170 perms_updates=None, recursive=False):
171 171 from rhodecode.model.repo import RepoModel
172 172 if not perms_new:
173 173 perms_new = []
174 174 if not perms_updates:
175 175 perms_updates = []
176 176
177 177 def _set_perm_user(obj, user, perm):
178 178 if isinstance(obj, RepoGroup):
179 179 ReposGroupModel().grant_user_permission(
180 180 repos_group=obj, user=user, perm=perm
181 181 )
182 182 elif isinstance(obj, Repository):
183 183 #we do this ONLY IF repository is non-private
184 184 if obj.private:
185 185 return
186 186
187 187 # we set group permission but we have to switch to repo
188 188 # permission
189 189 perm = perm.replace('group.', 'repository.')
190 190 RepoModel().grant_user_permission(
191 191 repo=obj, user=user, perm=perm
192 192 )
193 193
194 194 def _set_perm_group(obj, users_group, perm):
195 195 if isinstance(obj, RepoGroup):
196 196 ReposGroupModel().grant_users_group_permission(
197 197 repos_group=obj, group_name=users_group, perm=perm
198 198 )
199 199 elif isinstance(obj, Repository):
200 200 # we set group permission but we have to switch to repo
201 201 # permission
202 202 perm = perm.replace('group.', 'repository.')
203 203 RepoModel().grant_users_group_permission(
204 204 repo=obj, group_name=users_group, perm=perm
205 205 )
206 206 updates = []
207 207 log.debug('Now updating permissions for %s in recursive mode:%s'
208 208 % (repos_group, recursive))
209 209
210 210 for obj in repos_group.recursive_groups_and_repos():
211 211 #obj is an instance of a group or repositories in that group
212 212 if not recursive:
213 213 obj = repos_group
214 214
215 215 # update permissions
216 216 for member, perm, member_type in perms_updates:
217 217 ## set for user
218 218 if member_type == 'user':
219 219 # this updates also current one if found
220 220 _set_perm_user(obj, user=member, perm=perm)
221 ## set for users group
221 ## set for user group
222 222 else:
223 223 _set_perm_group(obj, users_group=member, perm=perm)
224 224 # set new permissions
225 225 for member, perm, member_type in perms_new:
226 226 if member_type == 'user':
227 227 _set_perm_user(obj, user=member, perm=perm)
228 228 else:
229 229 _set_perm_group(obj, users_group=member, perm=perm)
230 230 updates.append(obj)
231 231 #if it's not recursive call
232 232 # break the loop and don't proceed with other changes
233 233 if not recursive:
234 234 break
235 235 return updates
236 236
237 237 def update(self, repos_group, form_data):
238 238
239 239 try:
240 240 repos_group = self._get_repos_group(repos_group)
241 241 recursive = form_data['recursive']
242 242 # iterate over all members(if in recursive mode) of this groups and
243 243 # set the permissions !
244 244 # this can be potentially heavy operation
245 245 self._update_permissions(repos_group, form_data['perms_new'],
246 246 form_data['perms_updates'], recursive)
247 247
248 248 old_path = repos_group.full_path
249 249
250 250 # change properties
251 251 repos_group.group_description = form_data['group_description']
252 252 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
253 253 repos_group.group_parent_id = form_data['group_parent_id']
254 254 repos_group.enable_locking = form_data['enable_locking']
255 255 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
256 256 new_path = repos_group.full_path
257 257
258 258 self.sa.add(repos_group)
259 259
260 260 # iterate over all members of this groups and set the locking !
261 261 # this can be potentially heavy operation
262 262 for obj in repos_group.recursive_groups_and_repos():
263 263 #set the value from it's parent
264 264 obj.enable_locking = repos_group.enable_locking
265 265 self.sa.add(obj)
266 266
267 267 # we need to get all repositories from this new group and
268 268 # rename them accordingly to new group path
269 269 for r in repos_group.repositories:
270 270 r.repo_name = r.get_new_name(r.just_name)
271 271 self.sa.add(r)
272 272
273 273 self.__rename_group(old_path, new_path)
274 274
275 275 return repos_group
276 276 except:
277 277 log.error(traceback.format_exc())
278 278 raise
279 279
280 280 def delete(self, repos_group, force_delete=False):
281 281 repos_group = self._get_repos_group(repos_group)
282 282 try:
283 283 self.sa.delete(repos_group)
284 284 self.__delete_group(repos_group, force_delete)
285 285 except:
286 286 log.error('Error removing repos_group %s' % repos_group)
287 287 raise
288 288
289 289 def delete_permission(self, repos_group, obj, obj_type, recursive):
290 290 """
291 291 Revokes permission for repos_group for given obj(user or users_group),
292 obj_type can be user or users group
292 obj_type can be user or user group
293 293
294 294 :param repos_group:
295 :param obj: user or users group id
296 :param obj_type: user or users group type
295 :param obj: user or user group id
296 :param obj_type: user or user group type
297 297 :param recursive: recurse to all children of group
298 298 """
299 299 from rhodecode.model.repo import RepoModel
300 300 repos_group = self._get_repos_group(repos_group)
301 301
302 302 for el in repos_group.recursive_groups_and_repos():
303 303 if not recursive:
304 304 # if we don't recurse set the permission on only the top level
305 305 # object
306 306 el = repos_group
307 307
308 308 if isinstance(el, RepoGroup):
309 309 if obj_type == 'user':
310 310 ReposGroupModel().revoke_user_permission(el, user=obj)
311 311 elif obj_type == 'users_group':
312 312 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
313 313 else:
314 314 raise Exception('undefined object type %s' % obj_type)
315 315 elif isinstance(el, Repository):
316 316 if obj_type == 'user':
317 317 RepoModel().revoke_user_permission(el, user=obj)
318 318 elif obj_type == 'users_group':
319 319 RepoModel().revoke_users_group_permission(el, group_name=obj)
320 320 else:
321 321 raise Exception('undefined object type %s' % obj_type)
322 322
323 323 #if it's not recursive call
324 324 # break the loop and don't proceed with other changes
325 325 if not recursive:
326 326 break
327 327
328 328 def grant_user_permission(self, repos_group, user, perm):
329 329 """
330 330 Grant permission for user on given repositories group, or update
331 331 existing one if found
332 332
333 333 :param repos_group: Instance of ReposGroup, repositories_group_id,
334 334 or repositories_group name
335 335 :param user: Instance of User, user_id or username
336 336 :param perm: Instance of Permission, or permission_name
337 337 """
338 338
339 339 repos_group = self._get_repos_group(repos_group)
340 340 user = self._get_user(user)
341 341 permission = self._get_perm(perm)
342 342
343 343 # check if we have that permission already
344 344 obj = self.sa.query(UserRepoGroupToPerm)\
345 345 .filter(UserRepoGroupToPerm.user == user)\
346 346 .filter(UserRepoGroupToPerm.group == repos_group)\
347 347 .scalar()
348 348 if obj is None:
349 349 # create new !
350 350 obj = UserRepoGroupToPerm()
351 351 obj.group = repos_group
352 352 obj.user = user
353 353 obj.permission = permission
354 354 self.sa.add(obj)
355 355 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
356 356
357 357 def revoke_user_permission(self, repos_group, user):
358 358 """
359 359 Revoke permission for user on given repositories group
360 360
361 361 :param repos_group: Instance of ReposGroup, repositories_group_id,
362 362 or repositories_group name
363 363 :param user: Instance of User, user_id or username
364 364 """
365 365
366 366 repos_group = self._get_repos_group(repos_group)
367 367 user = self._get_user(user)
368 368
369 369 obj = self.sa.query(UserRepoGroupToPerm)\
370 370 .filter(UserRepoGroupToPerm.user == user)\
371 371 .filter(UserRepoGroupToPerm.group == repos_group)\
372 372 .scalar()
373 373 if obj:
374 374 self.sa.delete(obj)
375 375 log.debug('Revoked perm on %s on %s' % (repos_group, user))
376 376
377 377 def grant_users_group_permission(self, repos_group, group_name, perm):
378 378 """
379 Grant permission for users group on given repositories group, or update
379 Grant permission for user group on given repositories group, or update
380 380 existing one if found
381 381
382 382 :param repos_group: Instance of ReposGroup, repositories_group_id,
383 383 or repositories_group name
384 384 :param group_name: Instance of UserGroup, users_group_id,
385 or users group name
385 or user group name
386 386 :param perm: Instance of Permission, or permission_name
387 387 """
388 388 repos_group = self._get_repos_group(repos_group)
389 389 group_name = self.__get_users_group(group_name)
390 390 permission = self._get_perm(perm)
391 391
392 392 # check if we have that permission already
393 393 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
394 394 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
395 395 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
396 396 .scalar()
397 397
398 398 if obj is None:
399 399 # create new
400 400 obj = UsersGroupRepoGroupToPerm()
401 401
402 402 obj.group = repos_group
403 403 obj.users_group = group_name
404 404 obj.permission = permission
405 405 self.sa.add(obj)
406 406 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
407 407
408 408 def revoke_users_group_permission(self, repos_group, group_name):
409 409 """
410 Revoke permission for users group on given repositories group
410 Revoke permission for user group on given repositories group
411 411
412 412 :param repos_group: Instance of ReposGroup, repositories_group_id,
413 413 or repositories_group name
414 414 :param group_name: Instance of UserGroup, users_group_id,
415 or users group name
415 or user group name
416 416 """
417 417 repos_group = self._get_repos_group(repos_group)
418 418 group_name = self.__get_users_group(group_name)
419 419
420 420 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
421 421 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
422 422 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
423 423 .scalar()
424 424 if obj:
425 425 self.sa.delete(obj)
426 426 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
@@ -1,763 +1,763 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import itertools
29 29 import collections
30 30 from pylons import url
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import joinedload
35 35
36 36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
37 37 from rhodecode.lib.caching_query import FromCache
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
40 40 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
41 41 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
42 42 UserEmailMap, UserIpMap
43 43 from rhodecode.lib.exceptions import DefaultUserException, \
44 44 UserOwnsReposException
45 45 from rhodecode.model.meta import Session
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51 51
52 52
53 53 class UserModel(BaseModel):
54 54 cls = User
55 55
56 56 def get(self, user_id, cache=False):
57 57 user = self.sa.query(User)
58 58 if cache:
59 59 user = user.options(FromCache("sql_cache_short",
60 60 "get_user_%s" % user_id))
61 61 return user.get(user_id)
62 62
63 63 def get_user(self, user):
64 64 return self._get_user(user)
65 65
66 66 def get_by_username(self, username, cache=False, case_insensitive=False):
67 67
68 68 if case_insensitive:
69 69 user = self.sa.query(User).filter(User.username.ilike(username))
70 70 else:
71 71 user = self.sa.query(User)\
72 72 .filter(User.username == username)
73 73 if cache:
74 74 user = user.options(FromCache("sql_cache_short",
75 75 "get_user_%s" % username))
76 76 return user.scalar()
77 77
78 78 def get_by_email(self, email, cache=False, case_insensitive=False):
79 79 return User.get_by_email(email, case_insensitive, cache)
80 80
81 81 def get_by_api_key(self, api_key, cache=False):
82 82 return User.get_by_api_key(api_key, cache)
83 83
84 84 def create(self, form_data):
85 85 from rhodecode.lib.auth import get_crypt_password
86 86 try:
87 87 new_user = User()
88 88 for k, v in form_data.items():
89 89 if k == 'password':
90 90 v = get_crypt_password(v)
91 91 if k == 'firstname':
92 92 k = 'name'
93 93 setattr(new_user, k, v)
94 94
95 95 new_user.api_key = generate_api_key(form_data['username'])
96 96 self.sa.add(new_user)
97 97 return new_user
98 98 except:
99 99 log.error(traceback.format_exc())
100 100 raise
101 101
102 102 def create_or_update(self, username, password, email, firstname='',
103 103 lastname='', active=True, admin=False, ldap_dn=None):
104 104 """
105 105 Creates a new instance if not found, or updates current one
106 106
107 107 :param username:
108 108 :param password:
109 109 :param email:
110 110 :param active:
111 111 :param firstname:
112 112 :param lastname:
113 113 :param active:
114 114 :param admin:
115 115 :param ldap_dn:
116 116 """
117 117
118 118 from rhodecode.lib.auth import get_crypt_password
119 119
120 120 log.debug('Checking for %s account in RhodeCode database' % username)
121 121 user = User.get_by_username(username, case_insensitive=True)
122 122 if user is None:
123 123 log.debug('creating new user %s' % username)
124 124 new_user = User()
125 125 edit = False
126 126 else:
127 127 log.debug('updating user %s' % username)
128 128 new_user = user
129 129 edit = True
130 130
131 131 try:
132 132 new_user.username = username
133 133 new_user.admin = admin
134 134 # set password only if creating an user or password is changed
135 135 if edit is False or user.password != password:
136 136 new_user.password = get_crypt_password(password)
137 137 new_user.api_key = generate_api_key(username)
138 138 new_user.email = email
139 139 new_user.active = active
140 140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 141 new_user.name = firstname
142 142 new_user.lastname = lastname
143 143 self.sa.add(new_user)
144 144 return new_user
145 145 except (DatabaseError,):
146 146 log.error(traceback.format_exc())
147 147 raise
148 148
149 149 def create_for_container_auth(self, username, attrs):
150 150 """
151 151 Creates the given user if it's not already in the database
152 152
153 153 :param username:
154 154 :param attrs:
155 155 """
156 156 if self.get_by_username(username, case_insensitive=True) is None:
157 157
158 158 # autogenerate email for container account without one
159 159 generate_email = lambda usr: '%s@container_auth.account' % usr
160 160
161 161 try:
162 162 new_user = User()
163 163 new_user.username = username
164 164 new_user.password = None
165 165 new_user.api_key = generate_api_key(username)
166 166 new_user.email = attrs['email']
167 167 new_user.active = attrs.get('active', True)
168 168 new_user.name = attrs['name'] or generate_email(username)
169 169 new_user.lastname = attrs['lastname']
170 170
171 171 self.sa.add(new_user)
172 172 return new_user
173 173 except (DatabaseError,):
174 174 log.error(traceback.format_exc())
175 175 self.sa.rollback()
176 176 raise
177 177 log.debug('User %s already exists. Skipping creation of account'
178 178 ' for container auth.', username)
179 179 return None
180 180
181 181 def create_ldap(self, username, password, user_dn, attrs):
182 182 """
183 183 Checks if user is in database, if not creates this user marked
184 184 as ldap user
185 185
186 186 :param username:
187 187 :param password:
188 188 :param user_dn:
189 189 :param attrs:
190 190 """
191 191 from rhodecode.lib.auth import get_crypt_password
192 192 log.debug('Checking for such ldap account in RhodeCode database')
193 193 if self.get_by_username(username, case_insensitive=True) is None:
194 194
195 195 # autogenerate email for ldap account without one
196 196 generate_email = lambda usr: '%s@ldap.account' % usr
197 197
198 198 try:
199 199 new_user = User()
200 200 username = username.lower()
201 201 # add ldap account always lowercase
202 202 new_user.username = username
203 203 new_user.password = get_crypt_password(password)
204 204 new_user.api_key = generate_api_key(username)
205 205 new_user.email = attrs['email'] or generate_email(username)
206 206 new_user.active = attrs.get('active', True)
207 207 new_user.ldap_dn = safe_unicode(user_dn)
208 208 new_user.name = attrs['name']
209 209 new_user.lastname = attrs['lastname']
210 210
211 211 self.sa.add(new_user)
212 212 return new_user
213 213 except (DatabaseError,):
214 214 log.error(traceback.format_exc())
215 215 self.sa.rollback()
216 216 raise
217 217 log.debug('this %s user exists skipping creation of ldap account',
218 218 username)
219 219 return None
220 220
221 221 def create_registration(self, form_data):
222 222 from rhodecode.model.notification import NotificationModel
223 223
224 224 try:
225 225 form_data['admin'] = False
226 226 new_user = self.create(form_data)
227 227
228 228 self.sa.add(new_user)
229 229 self.sa.flush()
230 230
231 231 # notification to admins
232 232 subject = _('new user registration')
233 233 body = ('New user registration\n'
234 234 '---------------------\n'
235 235 '- Username: %s\n'
236 236 '- Full Name: %s\n'
237 237 '- Email: %s\n')
238 238 body = body % (new_user.username, new_user.full_name,
239 239 new_user.email)
240 240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 241 kw = {'registered_user_url': edit_url}
242 242 NotificationModel().create(created_by=new_user, subject=subject,
243 243 body=body, recipients=None,
244 244 type_=Notification.TYPE_REGISTRATION,
245 245 email_kwargs=kw)
246 246
247 247 except:
248 248 log.error(traceback.format_exc())
249 249 raise
250 250
251 251 def update(self, user_id, form_data, skip_attrs=[]):
252 252 from rhodecode.lib.auth import get_crypt_password
253 253 try:
254 254 user = self.get(user_id, cache=False)
255 255 if user.username == 'default':
256 256 raise DefaultUserException(
257 257 _("You can't Edit this user since it's"
258 258 " crucial for entire application"))
259 259
260 260 for k, v in form_data.items():
261 261 if k in skip_attrs:
262 262 continue
263 263 if k == 'new_password' and v:
264 264 user.password = get_crypt_password(v)
265 265 user.api_key = generate_api_key(user.username)
266 266 else:
267 267 if k == 'firstname':
268 268 k = 'name'
269 269 setattr(user, k, v)
270 270 self.sa.add(user)
271 271 except:
272 272 log.error(traceback.format_exc())
273 273 raise
274 274
275 275 def update_user(self, user, **kwargs):
276 276 from rhodecode.lib.auth import get_crypt_password
277 277 try:
278 278 user = self._get_user(user)
279 279 if user.username == 'default':
280 280 raise DefaultUserException(
281 281 _("You can't Edit this user since it's"
282 282 " crucial for entire application")
283 283 )
284 284
285 285 for k, v in kwargs.items():
286 286 if k == 'password' and v:
287 287 v = get_crypt_password(v)
288 288 user.api_key = generate_api_key(user.username)
289 289
290 290 setattr(user, k, v)
291 291 self.sa.add(user)
292 292 return user
293 293 except:
294 294 log.error(traceback.format_exc())
295 295 raise
296 296
297 297 def delete(self, user):
298 298 user = self._get_user(user)
299 299
300 300 try:
301 301 if user.username == 'default':
302 302 raise DefaultUserException(
303 303 _(u"You can't remove this user since it's"
304 304 " crucial for entire application")
305 305 )
306 306 if user.repositories:
307 307 repos = [x.repo_name for x in user.repositories]
308 308 raise UserOwnsReposException(
309 309 _(u'user "%s" still owns %s repositories and cannot be '
310 310 'removed. Switch owners or remove those repositories. %s')
311 311 % (user.username, len(repos), ', '.join(repos))
312 312 )
313 313 self.sa.delete(user)
314 314 except:
315 315 log.error(traceback.format_exc())
316 316 raise
317 317
318 318 def reset_password_link(self, data):
319 319 from rhodecode.lib.celerylib import tasks, run_task
320 320 from rhodecode.model.notification import EmailNotificationModel
321 321 user_email = data['email']
322 322 try:
323 323 user = User.get_by_email(user_email)
324 324 if user:
325 325 log.debug('password reset user found %s' % user)
326 326 link = url('reset_password_confirmation', key=user.api_key,
327 327 qualified=True)
328 328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
329 329 body = EmailNotificationModel().get_email_tmpl(reg_type,
330 330 **{'user': user.short_contact,
331 331 'reset_url': link})
332 332 log.debug('sending email')
333 333 run_task(tasks.send_email, user_email,
334 334 _("password reset link"), body, body)
335 335 log.info('send new password mail to %s' % user_email)
336 336 else:
337 337 log.debug("password reset email %s not found" % user_email)
338 338 except:
339 339 log.error(traceback.format_exc())
340 340 return False
341 341
342 342 return True
343 343
344 344 def reset_password(self, data):
345 345 from rhodecode.lib.celerylib import tasks, run_task
346 346 from rhodecode.lib import auth
347 347 user_email = data['email']
348 348 try:
349 349 try:
350 350 user = User.get_by_email(user_email)
351 351 new_passwd = auth.PasswordGenerator().gen_password(8,
352 352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
353 353 if user:
354 354 user.password = auth.get_crypt_password(new_passwd)
355 355 user.api_key = auth.generate_api_key(user.username)
356 356 Session().add(user)
357 357 Session().commit()
358 358 log.info('change password for %s' % user_email)
359 359 if new_passwd is None:
360 360 raise Exception('unable to generate new password')
361 361 except:
362 362 log.error(traceback.format_exc())
363 363 Session().rollback()
364 364
365 365 run_task(tasks.send_email, user_email,
366 366 _('Your new password'),
367 367 _('Your new RhodeCode password:%s') % (new_passwd))
368 368 log.info('send new password mail to %s' % user_email)
369 369
370 370 except:
371 371 log.error('Failed to update user password')
372 372 log.error(traceback.format_exc())
373 373
374 374 return True
375 375
376 376 def fill_data(self, auth_user, user_id=None, api_key=None):
377 377 """
378 378 Fetches auth_user by user_id,or api_key if present.
379 379 Fills auth_user attributes with those taken from database.
380 380 Additionally set's is_authenitated if lookup fails
381 381 present in database
382 382
383 383 :param auth_user: instance of user to set attributes
384 384 :param user_id: user id to fetch by
385 385 :param api_key: api key to fetch by
386 386 """
387 387 if user_id is None and api_key is None:
388 388 raise Exception('You need to pass user_id or api_key')
389 389
390 390 try:
391 391 if api_key:
392 392 dbuser = self.get_by_api_key(api_key)
393 393 else:
394 394 dbuser = self.get(user_id)
395 395
396 396 if dbuser is not None and dbuser.active:
397 397 log.debug('filling %s data' % dbuser)
398 398 for k, v in dbuser.get_dict().items():
399 399 setattr(auth_user, k, v)
400 400 else:
401 401 return False
402 402
403 403 except:
404 404 log.error(traceback.format_exc())
405 405 auth_user.is_authenticated = False
406 406 return False
407 407
408 408 return True
409 409
410 410 def fill_perms(self, user, explicit=True, algo='higherwin'):
411 411 """
412 412 Fills user permission attribute with permissions taken from database
413 413 works for permissions given for repositories, and for permissions that
414 414 are granted to groups
415 415
416 416 :param user: user instance to fill his perms
417 417 :param explicit: In case there are permissions both for user and a group
418 418 that user is part of, explicit flag will defiine if user will
419 419 explicitly override permissions from group, if it's False it will
420 420 make decision based on the algo
421 421 :param algo: algorithm to decide what permission should be choose if
422 422 it's multiple defined, eg user in two different groups. It also
423 423 decides if explicit flag is turned off how to specify the permission
424 424 for case when user is in a group + have defined separate permission
425 425 """
426 426 RK = 'repositories'
427 427 GK = 'repositories_groups'
428 428 GLOBAL = 'global'
429 429 user.permissions[RK] = {}
430 430 user.permissions[GK] = {}
431 431 user.permissions[GLOBAL] = set()
432 432
433 433 def _choose_perm(new_perm, cur_perm):
434 434 new_perm_val = PERM_WEIGHTS[new_perm]
435 435 cur_perm_val = PERM_WEIGHTS[cur_perm]
436 436 if algo == 'higherwin':
437 437 if new_perm_val > cur_perm_val:
438 438 return new_perm
439 439 return cur_perm
440 440 elif algo == 'lowerwin':
441 441 if new_perm_val < cur_perm_val:
442 442 return new_perm
443 443 return cur_perm
444 444
445 445 #======================================================================
446 446 # fetch default permissions
447 447 #======================================================================
448 448 default_user = User.get_by_username('default', cache=True)
449 449 default_user_id = default_user.user_id
450 450
451 451 default_repo_perms = Permission.get_default_perms(default_user_id)
452 452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
453 453
454 454 if user.is_admin:
455 455 #==================================================================
456 456 # admin user have all default rights for repositories
457 457 # and groups set to admin
458 458 #==================================================================
459 459 user.permissions[GLOBAL].add('hg.admin')
460 460
461 461 # repositories
462 462 for perm in default_repo_perms:
463 463 r_k = perm.UserRepoToPerm.repository.repo_name
464 464 p = 'repository.admin'
465 465 user.permissions[RK][r_k] = p
466 466
467 # repositories groups
467 # repository groups
468 468 for perm in default_repo_groups_perms:
469 469 rg_k = perm.UserRepoGroupToPerm.group.group_name
470 470 p = 'group.admin'
471 471 user.permissions[GK][rg_k] = p
472 472 return user
473 473
474 474 #==================================================================
475 475 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
476 476 #==================================================================
477 477 uid = user.user_id
478 478
479 479 # default global permissions taken fron the default user
480 480 default_global_perms = self.sa.query(UserToPerm)\
481 481 .filter(UserToPerm.user_id == default_user_id)
482 482
483 483 for perm in default_global_perms:
484 484 user.permissions[GLOBAL].add(perm.permission.permission_name)
485 485
486 486 # defaults for repositories, taken from default user
487 487 for perm in default_repo_perms:
488 488 r_k = perm.UserRepoToPerm.repository.repo_name
489 489 if perm.Repository.private and not (perm.Repository.user_id == uid):
490 490 # disable defaults for private repos,
491 491 p = 'repository.none'
492 492 elif perm.Repository.user_id == uid:
493 493 # set admin if owner
494 494 p = 'repository.admin'
495 495 else:
496 496 p = perm.Permission.permission_name
497 497
498 498 user.permissions[RK][r_k] = p
499 499
500 # defaults for repositories groups taken from default user permission
500 # defaults for repository groups taken from default user permission
501 501 # on given group
502 502 for perm in default_repo_groups_perms:
503 503 rg_k = perm.UserRepoGroupToPerm.group.group_name
504 504 p = perm.Permission.permission_name
505 505 user.permissions[GK][rg_k] = p
506 506
507 507 #======================================================================
508 508 # !! OVERRIDE GLOBALS !! with user permissions if any found
509 509 #======================================================================
510 510 # those can be configured from groups or users explicitly
511 511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
512 512 'hg.create.none', 'hg.create.repository'])
513 513
514 514 # USER GROUPS comes first
515 515 # users group global permissions
516 516 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
517 517 .options(joinedload(UsersGroupToPerm.permission))\
518 518 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
519 519 UsersGroupMember.users_group_id))\
520 520 .filter(UsersGroupMember.user_id == uid)\
521 521 .order_by(UsersGroupToPerm.users_group_id)\
522 522 .all()
523 523 #need to group here by groups since user can be in more than one group
524 524 _grouped = [[x, list(y)] for x, y in
525 525 itertools.groupby(user_perms_from_users_groups,
526 526 lambda x:x.users_group)]
527 527 for gr, perms in _grouped:
528 528 # since user can be in multiple groups iterate over them and
529 529 # select the lowest permissions first (more explicit)
530 530 ##TODO: do this^^
531 531 if not gr.inherit_default_permissions:
532 532 # NEED TO IGNORE all configurable permissions and
533 533 # replace them with explicitly set
534 534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
535 535 .difference(_configurable)
536 536 for perm in perms:
537 537 user.permissions[GLOBAL].add(perm.permission.permission_name)
538 538
539 539 # user specific global permissions
540 540 user_perms = self.sa.query(UserToPerm)\
541 541 .options(joinedload(UserToPerm.permission))\
542 542 .filter(UserToPerm.user_id == uid).all()
543 543
544 544 if not user.inherit_default_permissions:
545 545 # NEED TO IGNORE all configurable permissions and
546 546 # replace them with explicitly set
547 547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
548 548 .difference(_configurable)
549 549
550 550 for perm in user_perms:
551 551 user.permissions[GLOBAL].add(perm.permission.permission_name)
552 552
553 553 #======================================================================
554 554 # !! PERMISSIONS FOR REPOSITORIES !!
555 555 #======================================================================
556 556 #======================================================================
557 557 # check if user is part of user groups for this repository and
558 558 # fill in his permission from it. _choose_perm decides of which
559 559 # permission should be selected based on selected method
560 560 #======================================================================
561 561
562 562 # users group for repositories permissions
563 563 user_repo_perms_from_users_groups = \
564 564 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
565 565 .join((Repository, UsersGroupRepoToPerm.repository_id ==
566 566 Repository.repo_id))\
567 567 .join((Permission, UsersGroupRepoToPerm.permission_id ==
568 568 Permission.permission_id))\
569 569 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
570 570 UsersGroupMember.users_group_id))\
571 571 .filter(UsersGroupMember.user_id == uid)\
572 572 .all()
573 573
574 574 multiple_counter = collections.defaultdict(int)
575 575 for perm in user_repo_perms_from_users_groups:
576 576 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
577 577 multiple_counter[r_k] += 1
578 578 p = perm.Permission.permission_name
579 579 cur_perm = user.permissions[RK][r_k]
580 580
581 581 if perm.Repository.user_id == uid:
582 582 # set admin if owner
583 583 p = 'repository.admin'
584 584 else:
585 585 if multiple_counter[r_k] > 1:
586 586 p = _choose_perm(p, cur_perm)
587 587 user.permissions[RK][r_k] = p
588 588
589 589 # user explicit permissions for repositories, overrides any specified
590 590 # by the group permission
591 591 user_repo_perms = \
592 592 self.sa.query(UserRepoToPerm, Permission, Repository)\
593 593 .join((Repository, UserRepoToPerm.repository_id ==
594 594 Repository.repo_id))\
595 595 .join((Permission, UserRepoToPerm.permission_id ==
596 596 Permission.permission_id))\
597 597 .filter(UserRepoToPerm.user_id == uid)\
598 598 .all()
599 599
600 600 for perm in user_repo_perms:
601 601 r_k = perm.UserRepoToPerm.repository.repo_name
602 602 cur_perm = user.permissions[RK][r_k]
603 603 # set admin if owner
604 604 if perm.Repository.user_id == uid:
605 605 p = 'repository.admin'
606 606 else:
607 607 p = perm.Permission.permission_name
608 608 if not explicit:
609 609 p = _choose_perm(p, cur_perm)
610 610 user.permissions[RK][r_k] = p
611 611
612 612 #======================================================================
613 # !! PERMISSIONS FOR REPOSITORIES GROUPS !!
613 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
614 614 #======================================================================
615 615 #======================================================================
616 616 # check if user is part of user groups for this repository groups and
617 617 # fill in his permission from it. _choose_perm decides of which
618 618 # permission should be selected based on selected method
619 619 #======================================================================
620 620 # users group for repo groups permissions
621 621 user_repo_group_perms_from_users_groups = \
622 622 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
623 623 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
624 624 .join((Permission, UsersGroupRepoGroupToPerm.permission_id
625 625 == Permission.permission_id))\
626 626 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id
627 627 == UsersGroupMember.users_group_id))\
628 628 .filter(UsersGroupMember.user_id == uid)\
629 629 .all()
630 630
631 631 multiple_counter = collections.defaultdict(int)
632 632 for perm in user_repo_group_perms_from_users_groups:
633 633 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
634 634 multiple_counter[g_k] += 1
635 635 p = perm.Permission.permission_name
636 636 cur_perm = user.permissions[GK][g_k]
637 637 if multiple_counter[g_k] > 1:
638 638 p = _choose_perm(p, cur_perm)
639 639 user.permissions[GK][g_k] = p
640 640
641 641 # user explicit permissions for repository groups
642 642 user_repo_groups_perms = \
643 643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
644 644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
645 645 .join((Permission, UserRepoGroupToPerm.permission_id
646 646 == Permission.permission_id))\
647 647 .filter(UserRepoGroupToPerm.user_id == uid)\
648 648 .all()
649 649
650 650 for perm in user_repo_groups_perms:
651 651 rg_k = perm.UserRepoGroupToPerm.group.group_name
652 652 p = perm.Permission.permission_name
653 653 cur_perm = user.permissions[GK][rg_k]
654 654 if not explicit:
655 655 p = _choose_perm(p, cur_perm)
656 656 user.permissions[GK][rg_k] = p
657 657
658 658 return user
659 659
660 660 def has_perm(self, user, perm):
661 661 perm = self._get_perm(perm)
662 662 user = self._get_user(user)
663 663
664 664 return UserToPerm.query().filter(UserToPerm.user == user)\
665 665 .filter(UserToPerm.permission == perm).scalar() is not None
666 666
667 667 def grant_perm(self, user, perm):
668 668 """
669 669 Grant user global permissions
670 670
671 671 :param user:
672 672 :param perm:
673 673 """
674 674 user = self._get_user(user)
675 675 perm = self._get_perm(perm)
676 676 # if this permission is already granted skip it
677 677 _perm = UserToPerm.query()\
678 678 .filter(UserToPerm.user == user)\
679 679 .filter(UserToPerm.permission == perm)\
680 680 .scalar()
681 681 if _perm:
682 682 return
683 683 new = UserToPerm()
684 684 new.user = user
685 685 new.permission = perm
686 686 self.sa.add(new)
687 687
688 688 def revoke_perm(self, user, perm):
689 689 """
690 690 Revoke users global permissions
691 691
692 692 :param user:
693 693 :param perm:
694 694 """
695 695 user = self._get_user(user)
696 696 perm = self._get_perm(perm)
697 697
698 698 obj = UserToPerm.query()\
699 699 .filter(UserToPerm.user == user)\
700 700 .filter(UserToPerm.permission == perm)\
701 701 .scalar()
702 702 if obj:
703 703 self.sa.delete(obj)
704 704
705 705 def add_extra_email(self, user, email):
706 706 """
707 707 Adds email address to UserEmailMap
708 708
709 709 :param user:
710 710 :param email:
711 711 """
712 712 from rhodecode.model import forms
713 713 form = forms.UserExtraEmailForm()()
714 714 data = form.to_python(dict(email=email))
715 715 user = self._get_user(user)
716 716
717 717 obj = UserEmailMap()
718 718 obj.user = user
719 719 obj.email = data['email']
720 720 self.sa.add(obj)
721 721 return obj
722 722
723 723 def delete_extra_email(self, user, email_id):
724 724 """
725 725 Removes email address from UserEmailMap
726 726
727 727 :param user:
728 728 :param email_id:
729 729 """
730 730 user = self._get_user(user)
731 731 obj = UserEmailMap.query().get(email_id)
732 732 if obj:
733 733 self.sa.delete(obj)
734 734
735 735 def add_extra_ip(self, user, ip):
736 736 """
737 737 Adds ip address to UserIpMap
738 738
739 739 :param user:
740 740 :param ip:
741 741 """
742 742 from rhodecode.model import forms
743 743 form = forms.UserExtraIpForm()()
744 744 data = form.to_python(dict(ip=ip))
745 745 user = self._get_user(user)
746 746
747 747 obj = UserIpMap()
748 748 obj.user = user
749 749 obj.ip_addr = data['ip']
750 750 self.sa.add(obj)
751 751 return obj
752 752
753 753 def delete_extra_ip(self, user, ip_id):
754 754 """
755 755 Removes ip address from UserIpMap
756 756
757 757 :param user:
758 758 :param ip_id:
759 759 """
760 760 user = self._get_user(user)
761 761 obj = UserIpMap.query().get(ip_id)
762 762 if obj:
763 763 self.sa.delete(obj)
@@ -1,192 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.users_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 users group model for RhodeCode
6 user group model for RhodeCode
7 7
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import logging
28 28 import traceback
29 29
30 30 from rhodecode.model import BaseModel
31 31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
32 32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm, User
33 33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class UsersGroupModel(BaseModel):
39 39
40 40 cls = UsersGroup
41 41
42 42 def __get_users_group(self, users_group):
43 43 return self._get_instance(UsersGroup, users_group,
44 44 callback=UsersGroup.get_by_group_name)
45 45
46 46 def get(self, users_group_id, cache=False):
47 47 return UsersGroup.get(users_group_id)
48 48
49 49 def get_group(self, users_group):
50 50 return self.__get_users_group(users_group)
51 51
52 52 def get_by_name(self, name, cache=False, case_insensitive=False):
53 53 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
54 54
55 55 def create(self, name, active=True):
56 56 try:
57 57 new = UsersGroup()
58 58 new.users_group_name = name
59 59 new.users_group_active = active
60 60 self.sa.add(new)
61 61 return new
62 62 except:
63 63 log.error(traceback.format_exc())
64 64 raise
65 65
66 66 def update(self, users_group, form_data):
67 67
68 68 try:
69 69 users_group = self.__get_users_group(users_group)
70 70
71 71 for k, v in form_data.items():
72 72 if k == 'users_group_members':
73 73 users_group.members = []
74 74 self.sa.flush()
75 75 members_list = []
76 76 if v:
77 77 v = [v] if isinstance(v, basestring) else v
78 78 for u_id in set(v):
79 79 member = UsersGroupMember(users_group.users_group_id, u_id)
80 80 members_list.append(member)
81 81 setattr(users_group, 'members', members_list)
82 82 setattr(users_group, k, v)
83 83
84 84 self.sa.add(users_group)
85 85 except:
86 86 log.error(traceback.format_exc())
87 87 raise
88 88
89 89 def delete(self, users_group, force=False):
90 90 """
91 91 Deletes repos group, unless force flag is used
92 92 raises exception if there are members in that group, else deletes
93 93 group and users
94 94
95 95 :param users_group:
96 96 :param force:
97 97 """
98 98 try:
99 99 users_group = self.__get_users_group(users_group)
100 100
101 101 # check if this group is not assigned to repo
102 102 assigned_groups = UsersGroupRepoToPerm.query()\
103 103 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
104 104
105 105 if assigned_groups and force is False:
106 106 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
107 107 assigned_groups)
108 108
109 109 self.sa.delete(users_group)
110 110 except:
111 111 log.error(traceback.format_exc())
112 112 raise
113 113
114 114 def add_user_to_group(self, users_group, user):
115 115 users_group = self.__get_users_group(users_group)
116 116 user = self._get_user(user)
117 117
118 118 for m in users_group.members:
119 119 u = m.user
120 120 if u.user_id == user.user_id:
121 121 return True
122 122
123 123 try:
124 124 users_group_member = UsersGroupMember()
125 125 users_group_member.user = user
126 126 users_group_member.users_group = users_group
127 127
128 128 users_group.members.append(users_group_member)
129 129 user.group_member.append(users_group_member)
130 130
131 131 self.sa.add(users_group_member)
132 132 return users_group_member
133 133 except:
134 134 log.error(traceback.format_exc())
135 135 raise
136 136
137 137 def remove_user_from_group(self, users_group, user):
138 138 users_group = self.__get_users_group(users_group)
139 139 user = self._get_user(user)
140 140
141 141 users_group_member = None
142 142 for m in users_group.members:
143 143 if m.user.user_id == user.user_id:
144 144 # Found this user's membership row
145 145 users_group_member = m
146 146 break
147 147
148 148 if users_group_member:
149 149 try:
150 150 self.sa.delete(users_group_member)
151 151 return True
152 152 except:
153 153 log.error(traceback.format_exc())
154 154 raise
155 155 else:
156 156 # User isn't in that group
157 157 return False
158 158
159 159 def has_perm(self, users_group, perm):
160 160 users_group = self.__get_users_group(users_group)
161 161 perm = self._get_perm(perm)
162 162
163 163 return UsersGroupToPerm.query()\
164 164 .filter(UsersGroupToPerm.users_group == users_group)\
165 165 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
166 166
167 167 def grant_perm(self, users_group, perm):
168 168 users_group = self.__get_users_group(users_group)
169 169 perm = self._get_perm(perm)
170 170
171 171 # if this permission is already granted skip it
172 172 _perm = UsersGroupToPerm.query()\
173 173 .filter(UsersGroupToPerm.users_group == users_group)\
174 174 .filter(UsersGroupToPerm.permission == perm)\
175 175 .scalar()
176 176 if _perm:
177 177 return
178 178
179 179 new = UsersGroupToPerm()
180 180 new.users_group = users_group
181 181 new.permission = perm
182 182 self.sa.add(new)
183 183
184 184 def revoke_perm(self, users_group, perm):
185 185 users_group = self.__get_users_group(users_group)
186 186 perm = self._get_perm(perm)
187 187
188 188 obj = UsersGroupToPerm.query()\
189 189 .filter(UsersGroupToPerm.users_group == users_group)\
190 190 .filter(UsersGroupToPerm.permission == perm).scalar()
191 191 if obj:
192 192 self.sa.delete(obj)
@@ -1,128 +1,128 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repo_info.repo_to_perm:
12 12 %if r2p.user.username =='default' and c.repo_info.private:
13 13 <tr>
14 14 <td colspan="4">
15 15 <span class="private_repo_msg">
16 16 ${_('private repository')}
17 17 </span>
18 18 </td>
19 19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
20 20 </tr>
21 21 %else:
22 22 <tr id="id${id(r2p.user.username)}">
23 23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
24 24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
25 25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 27 <td style="white-space: nowrap;">
28 28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
29 29 </td>
30 30 <td>
31 31 %if r2p.user.username !='default':
32 32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
33 33 ${_('revoke')}
34 34 </span>
35 35 %endif
36 36 </td>
37 37 </tr>
38 38 %endif
39 39 %endfor
40 40
41 ## USERS GROUPS
41 ## USER GROUPS
42 42 %for g2p in c.repo_info.users_group_to_perm:
43 43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
45 45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 48 <td style="white-space: nowrap;">
49 49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
50 50 %if h.HasPermissionAny('hg.admin')():
51 51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
52 52 %else:
53 53 ${g2p.users_group.users_group_name}
54 54 %endif
55 55 </td>
56 56 <td>
57 57 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
58 58 ${_('revoke')}
59 59 </span>
60 60 </td>
61 61 </tr>
62 62 %endfor
63 63 <%
64 64 _tmpl = h.literal("""' \
65 65 <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 66 <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
67 67 <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
68 68 <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
69 69 <td class="ac"> \
70 70 <div class="perm_ac" id="perm_ac_{0}"> \
71 71 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
72 72 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
73 73 <div id="perm_container_{0}"></div> \
74 74 </div> \
75 75 </td> \
76 76 <td></td>'""")
77 77 %>
78 78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
79 79 <tr class="new_members last_new_member" id="add_perm_input"></tr>
80 80 <tr>
81 81 <td colspan="6">
82 82 <span id="add_perm" class="add_icon" style="cursor: pointer;">
83 83 ${_('Add another member')}
84 84 </span>
85 85 </td>
86 86 </tr>
87 87 </table>
88 88 <script type="text/javascript">
89 89 function ajaxActionUser(user_id, field_id) {
90 90 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
91 91 var callback = {
92 92 success: function (o) {
93 93 var tr = YUD.get(String(field_id));
94 94 tr.parentNode.removeChild(tr);
95 95 },
96 96 failure: function (o) {
97 97 alert("${_('Failed to remove user')}");
98 98 },
99 99 };
100 100 var postData = '_method=delete&user_id=' + user_id;
101 101 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
102 102 };
103 103
104 104 function ajaxActionUsersGroup(users_group_id,field_id){
105 105 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
106 106 var callback = {
107 107 success:function(o){
108 108 var tr = YUD.get(String(field_id));
109 109 tr.parentNode.removeChild(tr);
110 110 },
111 111 failure:function(o){
112 alert("${_('Failed to remove users group')}");
112 alert("${_('Failed to remove user group')}");
113 113 },
114 114 };
115 115 var postData = '_method=delete&users_group_id='+users_group_id;
116 116 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
117 117 };
118 118
119 119 YUE.onDOMReady(function () {
120 120 if (!YUD.hasClass('perm_new_member_name', 'error')) {
121 121 YUD.setStyle('add_perm_input', 'display', 'none');
122 122 }
123 123 YAHOO.util.Event.addListener('add_perm', 'click', function () {
124 124 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
125 125 });
126 126 });
127 127
128 128 </script>
@@ -1,133 +1,133 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repos_group.repo_group_to_perm:
12 12 ##forbid revoking permission from yourself
13 13 <tr id="id${id(r2p.user.username)}">
14 14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
17 17 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
18 18 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
19 19 <td style="white-space: nowrap;">
20 20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
21 21 </td>
22 22 <td>
23 23 %if r2p.user.username !='default':
24 24 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
25 25 ${_('revoke')}
26 26 </span>
27 27 %endif
28 28 </td>
29 29 %else:
30 30 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
31 31 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
32 32 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
33 33 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
34 34 <td style="white-space: nowrap;">
35 35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
36 36 </td>
37 37 <td>
38 38 </td>
39 39 %endif
40 40 </tr>
41 41 %endfor
42 42
43 ## USERS GROUPS
43 ## USER GROUPS
44 44 %for g2p in c.repos_group.users_group_to_perm:
45 45 <tr id="id${id(g2p.users_group.users_group_name)}">
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
48 48 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
49 49 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
50 50 <td style="white-space: nowrap;">
51 51 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
52 52 </td>
53 53 <td>
54 54 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
55 55 ${_('revoke')}
56 56 </span>
57 57 </td>
58 58 </tr>
59 59 %endfor
60 60 <%
61 61 _tmpl = h.literal("""' \
62 62 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
63 63 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
64 64 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
65 65 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 66 <td class="ac"> \
67 67 <div class="perm_ac" id="perm_ac_{0}"> \
68 68 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
69 69 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
70 70 <div id="perm_container_{0}"></div> \
71 71 </div> \
72 72 </td> \
73 73 <td></td>'""")
74 74 %>
75 75 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
76 76 <tr class="new_members last_new_member" id="add_perm_input"></tr>
77 77 <tr>
78 78 <td colspan="6">
79 79 <span id="add_perm" class="add_icon" style="cursor: pointer;">
80 80 ${_('Add another member')}
81 81 </span>
82 82 </td>
83 83 </tr>
84 84 <tr>
85 85 <td colspan="6">
86 86 ${h.checkbox('recursive',value="True", label=_('apply to children'))}
87 87 <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')}</span>
88 88 </td>
89 89 </tr>
90 90 </table>
91 91 <script type="text/javascript">
92 92 function ajaxActionUser(user_id, field_id) {
93 93 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
94 94 var callback = {
95 95 success: function (o) {
96 96 var tr = YUD.get(String(field_id));
97 97 tr.parentNode.removeChild(tr);
98 98 },
99 99 failure: function (o) {
100 100 alert("${_('Failed to remove user')}");
101 101 },
102 102 };
103 103 var recursive = YUD.get('recursive').checked;
104 104 var postData = '_method=delete&recursive={0}&user_id={1}'.format(recursive,user_id);
105 105 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
106 106 };
107 107
108 108 function ajaxActionUsersGroup(users_group_id,field_id){
109 109 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
110 110 var callback = {
111 111 success:function(o){
112 112 var tr = YUD.get(String(field_id));
113 113 tr.parentNode.removeChild(tr);
114 114 },
115 115 failure:function(o){
116 alert("${_('Failed to remove users group')}");
116 alert("${_('Failed to remove user group')}");
117 117 },
118 118 };
119 119 var recursive = YUD.get('recursive').checked;
120 120 var postData = '_method=delete&recursive={0}&users_group_id={1}'.format(recursive,users_group_id);
121 121 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
122 122 };
123 123
124 124 YUE.onDOMReady(function () {
125 125 if (!YUD.hasClass('perm_new_member_name', 'error')) {
126 126 YUD.setStyle('add_perm_input', 'display', 'none');
127 127 }
128 128 YAHOO.util.Event.addListener('add_perm', 'click', function () {
129 129 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
130 130 });
131 131 });
132 132
133 133 </script>
@@ -1,78 +1,78 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Repositories groups administration')} - ${c.rhodecode_name}
5 ${_('Repository groups administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 11 &raquo;
12 12 ${_('repositories groups')}
13 13 </%def>
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17 <%def name="main()">
18 18 <div class="box">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 <ul class="links">
23 23 <li>
24 24 %if h.HasPermissionAny('hg.admin')():
25 25 <span>${h.link_to(_(u'Add group'),h.url('new_repos_group'))}</span>
26 26 %endif
27 27 </li>
28 28 </ul>
29 29 </div>
30 30 <!-- end box / title -->
31 31 <div class="table">
32 32 % if c.groups:
33 33 <table class="table_disp">
34 34
35 35 <thead>
36 36 <tr>
37 37 <th class="left"><a href="#">${_('Group name')}</a></th>
38 38 <th class="left"><a href="#">${_('Description')}</a></th>
39 39 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
40 40 <th class="left" colspan="2">${_('action')}</th>
41 41 </tr>
42 42 </thead>
43 43
44 44 ## REPO GROUPS
45 45
46 46 % for gr in c.groups:
47 47 <% gr_cn = gr.repositories.count() %>
48 48 <tr>
49 49 <td>
50 50 <div style="white-space: nowrap">
51 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
51 <img class="icon" alt="${_('Repository group')}" src="${h.url('/images/icons/database_link.png')}"/>
52 52 ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))}
53 53 </div>
54 54 </td>
55 55 <td>${gr.group_description}</td>
56 56 <td><b>${gr_cn}</b></td>
57 57 <td>
58 58 <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('edit')}">
59 59 ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
60 60 </a>
61 61 </td>
62 62 <td>
63 63 ${h.form(url('repos_group', group_name=gr.group_name),method='delete')}
64 64 ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")}
65 65 ${h.end_form()}
66 66 </td>
67 67 </tr>
68 68 % endfor
69 69
70 70 </table>
71 71 % else:
72 ${_('There are no repositories groups yet')}
72 ${_('There are no repository groups yet')}
73 73 % endif
74 74
75 75 </div>
76 76 </div>
77 77
78 78 </%def>
@@ -1,55 +1,55 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Add users group')} - ${c.rhodecode_name}
5 ${_('Add user group')} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 ${h.link_to(_('Users groups'),h.url('users_groups'))}
10 ${h.link_to(_('User groups'),h.url('users_groups'))}
11 11 &raquo;
12 ${_('add new users group')}
12 ${_('add new user group')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('users_groups'))}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="users_group_name">${_('Group name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('users_group_name',class_='small')}
36 36 </div>
37 37 </div>
38 38
39 39 <div class="field">
40 40 <div class="label label-checkbox">
41 41 <label for="users_group_active">${_('Active')}:</label>
42 42 </div>
43 43 <div class="checkboxes">
44 44 ${h.checkbox('users_group_active',value=True, checked='checked')}
45 45 </div>
46 46 </div>
47 47
48 48 <div class="buttons">
49 49 ${h.submit('save',_('save'),class_="ui-btn large")}
50 50 </div>
51 51 </div>
52 52 </div>
53 53 ${h.end_form()}
54 54 </div>
55 55 </%def>
@@ -1,55 +1,55 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Users groups administration')} - ${c.rhodecode_name}
5 ${_('User groups administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 ${_('users groups')}
11 ${_('user groups')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 <ul class="links">
24 24 <li>
25 25 <span>${h.link_to(_(u'Add new user group'),h.url('new_users_group'))}</span>
26 26 </li>
27 27
28 28 </ul>
29 29 </div>
30 30 <!-- end box / title -->
31 31 <div class="table">
32 32 <table class="table_disp">
33 33 <tr class="header">
34 34 <th class="left">${_('group name')}</th>
35 35 <th class="left">${_('members')}</th>
36 36 <th class="left">${_('active')}</th>
37 37 <th class="left">${_('action')}</th>
38 38 </tr>
39 39 %for cnt,u_group in enumerate(c.users_groups_list):
40 40 <tr class="parity${cnt%2}">
41 41 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
42 42 <td><span class="tooltip" title="${h.tooltip(', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]])))}">${len(u_group.members)}</span></td>
43 43 <td>${h.bool2icon(u_group.users_group_active)}</td>
44 44 <td>
45 45 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
46 46 ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id,
47 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this users group: %s') % u_group.users_group_name+"');")}
47 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user group: %s') % u_group.users_group_name+"');")}
48 48 ${h.end_form()}
49 49 </td>
50 50 </tr>
51 51 %endfor
52 52 </table>
53 53 </div>
54 54 </div>
55 55 </%def>
@@ -1,390 +1,390 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <!-- HEADER -->
5 5 <div id="header-dd"></div>
6 6 <div id="header">
7 7 <div id="header-inner" class="title">
8 8 <div id="logo">
9 9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
10 10 </div>
11 11 <!-- MENU -->
12 12 ${self.page_nav()}
13 13 <!-- END MENU -->
14 14 ${self.body()}
15 15 </div>
16 16 </div>
17 17 <!-- END HEADER -->
18 18
19 19 <!-- CONTENT -->
20 20 <div id="content">
21 21 <div class="flash_msg">
22 22 <% messages = h.flash.pop_messages() %>
23 23 % if messages:
24 24 <ul id="flash-messages">
25 25 % for message in messages:
26 26 <li class="${message.category}_msg">${message}</li>
27 27 % endfor
28 28 </ul>
29 29 % endif
30 30 </div>
31 31 <div id="main">
32 32 ${next.main()}
33 33 </div>
34 34 </div>
35 35 <!-- END CONTENT -->
36 36
37 37 <!-- FOOTER -->
38 38 <div id="footer">
39 39 <div id="footer-inner" class="title">
40 40 <div>
41 41 <p class="footer-link">
42 42 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
43 43 </p>
44 44 <p class="footer-link-right">
45 45 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
46 46 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
47 47 </p>
48 48 </div>
49 49 </div>
50 50 </div>
51 51 <!-- END FOOTER -->
52 52
53 53 ### MAKO DEFS ###
54 54 <%def name="page_nav()">
55 55 ${self.menu()}
56 56 </%def>
57 57
58 58 <%def name="breadcrumbs()">
59 59 <div class="breadcrumbs">
60 60 ${self.breadcrumbs_links()}
61 61 </div>
62 62 </%def>
63 63
64 64 <%def name="usermenu()">
65 65 ## USER MENU
66 66 <li>
67 67 <a class="menu_link" id="quick_login_link">
68 68 <span class="icon" style="padding:5px 5px 0px 5px">
69 69 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
70 70 </span>
71 71 %if c.rhodecode_user.username != 'default':
72 72 <span class="menu_link_user">${c.rhodecode_user.username}</span>
73 73 %if c.unread_notifications != 0:
74 74 <span class="menu_link_notifications">${c.unread_notifications}</span>
75 75 %endif
76 76 %else:
77 77 <span>${_('Not logged in')}</span>
78 78 %endif
79 79 </a>
80 80
81 81 <div class="user-menu">
82 82 <div id="quick_login">
83 83 %if c.rhodecode_user.username == 'default':
84 84 <h4>${_('Login to your account')}</h4>
85 85 ${h.form(h.url('login_home',came_from=h.url.current()))}
86 86 <div class="form">
87 87 <div class="fields">
88 88 <div class="field">
89 89 <div class="label">
90 90 <label for="username">${_('Username')}:</label>
91 91 </div>
92 92 <div class="input">
93 93 ${h.text('username',class_='focus',size=40)}
94 94 </div>
95 95
96 96 </div>
97 97 <div class="field">
98 98 <div class="label">
99 99 <label for="password">${_('Password')}:</label>
100 100 </div>
101 101 <div class="input">
102 102 ${h.password('password',class_='focus',size=40)}
103 103 </div>
104 104
105 105 </div>
106 106 <div class="buttons">
107 107 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
108 108 <div class="register">
109 109 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
110 110 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
111 111 %endif
112 112 </div>
113 113 <div class="submit">
114 114 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
115 115 </div>
116 116 </div>
117 117 </div>
118 118 </div>
119 119 ${h.end_form()}
120 120 %else:
121 121 <div class="links_left">
122 122 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
123 123 <div class="email">${c.rhodecode_user.email}</div>
124 124 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
125 125 <div class="notifications"><a href="${h.url('notifications')}">${_('Notifications')}</a></div>
126 126 <div class="unread"><a href="${h.url('notifications')}">${_('Unread')}: ${c.unread_notifications}</a></div>
127 127 </div>
128 128 <div class="links_right">
129 129 <ol class="links">
130 130 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
131 131 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
132 132 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
133 133 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
134 134 </ol>
135 135 </div>
136 136 %endif
137 137 </div>
138 138 </div>
139 139
140 140 </li>
141 141 </%def>
142 142
143 143 <%def name="menu(current=None)">
144 144 <%
145 145 def is_current(selected):
146 146 if selected == current:
147 147 return h.literal('class="current"')
148 148 %>
149 149 <ul id="quick">
150 150 <!-- repo switcher -->
151 151 <li ${is_current('home')}>
152 152 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
153 153 <span class="icon">
154 154 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
155 155 </span>
156 156 <span>${_('Repositories')}</span>
157 157 </a>
158 158 <ul id="repo_switcher_list" class="repo_switcher">
159 159 <li>
160 160 <a href="#">${_('loading...')}</a>
161 161 </li>
162 162 </ul>
163 163 </li>
164 164 ## we render this menu only not for those pages
165 165 %if current not in ['home','admin', 'search', 'journal']:
166 166 ##REGULAR MENU
167 167 <li ${is_current('summary')}>
168 168 <a class="menu_link" title="${_('Summary page')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
169 169 <span class="icon">
170 170 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
171 171 </span>
172 172 <span>${_('Summary')}</span>
173 173 </a>
174 174 </li>
175 175 <li ${is_current('changelog')}>
176 176 <a class="menu_link" title="${_('Changeset list')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
177 177 <span class="icon">
178 178 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
179 179 </span>
180 180 <span>${_('Changelog')}</span>
181 181 </a>
182 182 </li>
183 183 <li ${is_current('switch_to')}>
184 184 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
185 185 <span class="icon">
186 186 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
187 187 </span>
188 188 <span>${_('Switch to')}</span>
189 189 </a>
190 190 <ul id="switch_to_list" class="switch_to">
191 191 <li><a href="#">${_('loading...')}</a></li>
192 192 </ul>
193 193 </li>
194 194 <li ${is_current('files')}>
195 195 <a class="menu_link" title="${_('Show repository content')}" href="${h.url('files_home',repo_name=c.repo_name)}">
196 196 <span class="icon">
197 197 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
198 198 </span>
199 199 <span>${_('Files')}</span>
200 200 </a>
201 201 </li>
202 202 <li ${is_current('options')}>
203 203 <a class="menu_link" title="${_('Options')}" href="#">
204 204 <span class="icon">
205 205 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
206 206 </span>
207 207 <span>${_('Options')}</span>
208 208 </a>
209 209 <ul>
210 210 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
211 211 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
212 212 <li>${h.link_to(_('repository settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
213 213 %else:
214 214 <li>${h.link_to(_('repository settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
215 215 %endif
216 216 %endif
217 217
218 218 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
219 219 %if h.is_hg(c.rhodecode_repo):
220 220 <li>${h.link_to(_('open new pull request'),h.url('pullrequest_home',repo_name=c.repo_name),class_='pull_request')}</li>
221 221 %endif
222 222 %if c.rhodecode_db_repo.fork:
223 223 <li>${h.link_to(_('compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default'),class_='compare_request')}</li>
224 224 %endif
225 225 <li>${h.link_to(_('lightweight changelog'),h.url('shortlog_home',repo_name=c.repo_name),class_='shortlog')}</li>
226 226 <li>${h.link_to(_('search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
227 227
228 228 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
229 229 %if c.rhodecode_db_repo.locked[0]:
230 230 <li>${h.link_to(_('unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
231 231 %else:
232 232 <li>${h.link_to(_('lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
233 233 %endif
234 234 %endif
235 235
236 236 % if h.HasPermissionAll('hg.admin')('access admin main page'):
237 237 <li>
238 238 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
239 239 <%def name="admin_menu()">
240 240 <ul>
241 241 <li>${h.link_to(_('admin journal'),h.url('admin_home'),class_='journal')}</li>
242 242 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
243 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
243 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
244 244 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
245 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
245 <li>${h.link_to(_('user groups'),h.url('users_groups'),class_='groups')}</li>
246 246 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
247 247 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
248 248 <li>${h.link_to(_('defaults'),h.url('defaults'),class_='defaults')}</li>
249 249 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
250 250 </ul>
251 251 </%def>
252 252 ## ADMIN MENU
253 253 ${admin_menu()}
254 254 </li>
255 255 ## if you're a admin of any groups, show admin menu for it
256 256 % elif c.rhodecode_user.groups_admin:
257 257 <li>
258 258 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
259 259 <%def name="admin_menu_simple()">
260 260 <ul>
261 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
261 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
262 262 </ul>
263 263 </%def>
264 264 ## ADMIN MENU
265 265 ${admin_menu_simple()}
266 266 </li>
267 267 % endif
268 268 </ul>
269 269 </li>
270 270 <li>
271 271 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
272 272 <span class="icon_short">
273 273 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
274 274 </span>
275 275 <span id="current_followers_count" class="short">${c.repository_followers}</span>
276 276 </a>
277 277 </li>
278 278 <li>
279 279 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
280 280 <span class="icon_short">
281 281 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
282 282 </span>
283 283 <span class="short">${c.repository_forks}</span>
284 284 </a>
285 285 </li>
286 286 <li>
287 287 <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}">
288 288 <span class="icon_short">
289 289 <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" />
290 290 </span>
291 291 <span class="short">${c.repository_pull_requests}</span>
292 292 </a>
293 293 </li>
294 294 ${usermenu()}
295 295 <script type="text/javascript">
296 296 YUE.on('branch_tag_switcher','mouseover',function(){
297 297 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
298 298 if(!loaded){
299 299 YUD.addClass('branch_tag_switcher','loaded');
300 300 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
301 301 function(o){},
302 302 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
303 303 ,null);
304 304 }
305 305 return false;
306 306 });
307 307 </script>
308 308 %else:
309 309 ##ROOT MENU
310 310 %if c.rhodecode_user.username != 'default':
311 311 <li ${is_current('journal')}>
312 312 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
313 313 <span class="icon">
314 314 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
315 315 </span>
316 316 <span>${_('Journal')}</span>
317 317 </a>
318 318 </li>
319 319 %else:
320 320 <li ${is_current('journal')}>
321 321 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
322 322 <span class="icon">
323 323 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
324 324 </span>
325 325 <span>${_('Public journal')}</span>
326 326 </a>
327 327 </li>
328 328 %endif
329 329 <li ${is_current('search')}>
330 330 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
331 331 <span class="icon">
332 332 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
333 333 </span>
334 334 <span>${_('Search')}</span>
335 335 </a>
336 336 </li>
337 337 % if h.HasPermissionAll('hg.admin')('access admin main page'):
338 338 <li ${is_current('admin')}>
339 339 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
340 340 <span class="icon">
341 341 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
342 342 </span>
343 343 <span>${_('Admin')}</span>
344 344 </a>
345 345 ${admin_menu()}
346 346 </li>
347 347 % elif c.rhodecode_user.groups_admin:
348 348 <li ${is_current('admin')}>
349 349 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
350 350 <span class="icon">
351 351 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
352 352 </span>
353 353 <span>${_('Admin')}</span>
354 354 </a>
355 355 ${admin_menu_simple()}
356 356 </li>
357 357 % endif
358 358 ${usermenu()}
359 359 %endif
360 360 <script type="text/javascript">
361 361 YUE.on('repo_switcher','mouseover',function(){
362 362 var target = 'q_filter_rs';
363 363 var qfilter_activate = function(){
364 364 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
365 365 var func = function(node){
366 366 return node.parentNode;
367 367 }
368 368 q_filter(target,nodes,func);
369 369 }
370 370
371 371 var loaded = YUD.hasClass('repo_switcher','loaded');
372 372 if(!loaded){
373 373 YUD.addClass('repo_switcher','loaded');
374 374 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
375 375 function(o){qfilter_activate();YUD.get(target).focus()},
376 376 function(o){YUD.removeClass('repo_switcher','loaded');}
377 377 ,null);
378 378 }else{
379 379 YUD.get(target).focus();
380 380 }
381 381 return false;
382 382 });
383 383
384 384 YUE.on('header-dd', 'click',function(e){
385 385 YUD.addClass('header-inner', 'hover');
386 386 YUD.addClass('content', 'hover');
387 387 });
388 388
389 389 </script>
390 390 </%def>
@@ -1,51 +1,51 b''
1 1 from rhodecode.tests import *
2 2
3 3
4 4 class TestReposGroupsController(TestController):
5 5
6 6 def test_index(self):
7 7 self.log_user()
8 8 response = self.app.get(url('repos_groups'))
9 response.mustcontain('There are no repositories groups yet')
9 response.mustcontain('There are no repository groups yet')
10 10
11 11 # def test_index_as_xml(self):
12 12 # response = self.app.get(url('formatted_repos_groups', format='xml'))
13 13 #
14 14 # def test_create(self):
15 15 # response = self.app.post(url('repos_groups'))
16 16
17 17 def test_new(self):
18 18 self.log_user()
19 19 response = self.app.get(url('new_repos_group'))
20 20
21 21 def test_new_by_regular_user(self):
22 22 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
23 23 response = self.app.get(url('new_repos_group'), status=403)
24 24 #
25 25 # def test_new_as_xml(self):
26 26 # response = self.app.get(url('formatted_new_repos_group', format='xml'))
27 27 #
28 28 # def test_update(self):
29 29 # response = self.app.put(url('repos_group', group_name=1))
30 30 #
31 31 # def test_update_browser_fakeout(self):
32 32 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='put'))
33 33 #
34 34 # def test_delete(self):
35 35 # self.log_user()
36 36 # response = self.app.delete(url('repos_group', group_name=1))
37 37 #
38 38 # def test_delete_browser_fakeout(self):
39 39 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='delete'))
40 40 #
41 41 # def test_show(self):
42 42 # response = self.app.get(url('repos_group', group_name=1))
43 43 #
44 44 # def test_show_as_xml(self):
45 45 # response = self.app.get(url('formatted_repos_group', group_name=1, format='xml'))
46 46 #
47 47 # def test_edit(self):
48 48 # response = self.app.get(url('edit_repos_group', group_name=1))
49 49 #
50 50 # def test_edit_as_xml(self):
51 51 # response = self.app.get(url('formatted_edit_repos_group', group_name=1, format='xml'))
General Comments 0
You need to be logged in to leave comments. Login now