##// END OF EJS Templates
#344 optional firstname lastname on user creation...
marcink -
r1950:4ae17f81 beta
parent child Browse files
Show More
@@ -1,473 +1,473 b''
1 .. _api:
1 .. _api:
2
2
3
3
4 API
4 API
5 ===
5 ===
6
6
7
7
8 Starting from RhodeCode version 1.2 a simple API was implemented.
8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's a single schema for calling all api methods. API is implemented
9 There's a single schema for calling all api methods. API is implemented
10 with JSON protocol both ways. An url to send API request in RhodeCode is
10 with JSON protocol both ways. An url to send API request in RhodeCode is
11 <your_server>/_admin/api
11 <your_server>/_admin/api
12
12
13 API ACCESS FOR WEB VIEWS
13 API ACCESS FOR WEB VIEWS
14 ++++++++++++++++++++++++
14 ++++++++++++++++++++++++
15
15
16 API access can also be turned on for each web view in RhodeCode that is
16 API access can also be turned on for each web view in RhodeCode that is
17 decorated with `@LoginRequired` decorator. To enable API access simple change
17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 the standard login decorator to `@LoginRequired(api_access=True)`.
18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 After this change, a rhodecode view can be accessed without login by adding a
19 After this change, a rhodecode view can be accessed without login by adding a
20 GET parameter `?api_key=<api_key>` to url. By default this is only
20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 enabled on RSS/ATOM feed views.
21 enabled on RSS/ATOM feed views.
22
22
23
23
24 API ACCESS
24 API ACCESS
25 ++++++++++
25 ++++++++++
26
26
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:<id>,
30 "id:<id>,
31 "api_key":"<api_key>",
31 "api_key":"<api_key>",
32 "method":"<method_name>",
32 "method":"<method_name>",
33 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
34 }
34 }
35
35
36 Example call for autopulling remotes repos using curl::
36 Example call for autopulling remotes repos using curl::
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"}}'
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 Simply provide
39 Simply provide
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 - *api_key* for access and permission validation.
41 - *api_key* for access and permission validation.
42 - *method* is name of method to call
42 - *method* is name of method to call
43 - *args* is an key:value list of arguments to pass to method
43 - *args* is an key:value list of arguments to pass to method
44
44
45 .. note::
45 .. note::
46
46
47 api_key can be found in your user account page
47 api_key can be found in your user account page
48
48
49
49
50 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
51
51
52 {
52 {
53 "id":<id>,
53 "id":<id>,
54 "result": "<result>",
54 "result": "<result>",
55 "error": null
55 "error": null
56 }
56 }
57
57
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 calling api *error* key from response will contain failure description
59 calling api *error* key from response will contain failure description
60 and result will be null.
60 and result will be null.
61
61
62 API METHODS
62 API METHODS
63 +++++++++++
63 +++++++++++
64
64
65
65
66 pull
66 pull
67 ----
67 ----
68
68
69 Pulls given repo from remote location. Can be used to automatically keep
69 Pulls given repo from remote location. Can be used to automatically keep
70 remote repos up to date. This command can be executed only using api_key
70 remote repos up to date. This command can be executed only using api_key
71 belonging to user with admin rights
71 belonging to user with admin rights
72
72
73 INPUT::
73 INPUT::
74
74
75 api_key : "<api_key>"
75 api_key : "<api_key>"
76 method : "pull"
76 method : "pull"
77 args : {
77 args : {
78 "repo_name" : "<reponame>"
78 "repo_name" : "<reponame>"
79 }
79 }
80
80
81 OUTPUT::
81 OUTPUT::
82
82
83 result : "Pulled from <reponame>"
83 result : "Pulled from <reponame>"
84 error : null
84 error : null
85
85
86
86
87 get_user
87 get_user
88 --------
88 --------
89
89
90 Get's an user by username, Returns empty result if user is not found.
90 Get's an user by username, Returns empty result if user is not found.
91 This command can be executed only using api_key belonging to user with admin
91 This command can be executed only using api_key belonging to user with admin
92 rights.
92 rights.
93
93
94 INPUT::
94 INPUT::
95
95
96 api_key : "<api_key>"
96 api_key : "<api_key>"
97 method : "get_user"
97 method : "get_user"
98 args : {
98 args : {
99 "username" : "<username>"
99 "username" : "<username>"
100 }
100 }
101
101
102 OUTPUT::
102 OUTPUT::
103
103
104 result: None if user does not exist or
104 result: None if user does not exist or
105 {
105 {
106 "id" : "<id>",
106 "id" : "<id>",
107 "username" : "<username>",
107 "username" : "<username>",
108 "firstname": "<firstname>",
108 "firstname": "<firstname>",
109 "lastname" : "<lastname>",
109 "lastname" : "<lastname>",
110 "email" : "<email>",
110 "email" : "<email>",
111 "active" : "<bool>",
111 "active" : "<bool>",
112 "admin" :Β  "<bool>",
112 "admin" :Β  "<bool>",
113 "ldap" : "<ldap_dn>"
113 "ldap" : "<ldap_dn>"
114 }
114 }
115
115
116 error: null
116 error: null
117
117
118
118
119 get_users
119 get_users
120 ---------
120 ---------
121
121
122 Lists all existing users. This command can be executed only using api_key
122 Lists all existing users. This command can be executed only using api_key
123 belonging to user with admin rights.
123 belonging to user with admin rights.
124
124
125 INPUT::
125 INPUT::
126
126
127 api_key : "<api_key>"
127 api_key : "<api_key>"
128 method : "get_users"
128 method : "get_users"
129 args : { }
129 args : { }
130
130
131 OUTPUT::
131 OUTPUT::
132
132
133 result: [
133 result: [
134 {
134 {
135 "id" : "<id>",
135 "id" : "<id>",
136 "username" : "<username>",
136 "username" : "<username>",
137 "firstname": "<firstname>",
137 "firstname": "<firstname>",
138 "lastname" : "<lastname>",
138 "lastname" : "<lastname>",
139 "email" : "<email>",
139 "email" : "<email>",
140 "active" : "<bool>",
140 "active" : "<bool>",
141 "admin" :Β  "<bool>",
141 "admin" :Β  "<bool>",
142 "ldap" : "<ldap_dn>"
142 "ldap" : "<ldap_dn>"
143 },
143 },
144 …
144 …
145 ]
145 ]
146 error: null
146 error: null
147
147
148 create_user
148 create_user
149 -----------
149 -----------
150
150
151 Creates new user or updates current one if such user exists. This command can
151 Creates new user or updates current one if such user exists. This command can
152 be executed only using api_key belonging to user with admin rights.
152 be executed only using api_key belonging to user with admin rights.
153
153
154 INPUT::
154 INPUT::
155
155
156 api_key : "<api_key>"
156 api_key : "<api_key>"
157 method : "create_user"
157 method : "create_user"
158 args : {
158 args : {
159 "username" : "<username>",
159 "username" : "<username>",
160 "password" : "<password>",
160 "password" : "<password>",
161 "firstname" : "<firstname>",
161 "email" : "<useremail>",
162 "lastname" : "<lastname>",
162 "firstname" : "<firstname> = None",
163 "email" : "<useremail>"
163 "lastname" : "<lastname> = None",
164 "active" : "<bool> = True",
164 "active" : "<bool> = True",
165 "admin" : "<bool> = False",
165 "admin" : "<bool> = False",
166 "ldap_dn" : "<ldap_dn> = None"
166 "ldap_dn" : "<ldap_dn> = None"
167 }
167 }
168
168
169 OUTPUT::
169 OUTPUT::
170
170
171 result: {
171 result: {
172 "id" : "<new_user_id>",
172 "id" : "<new_user_id>",
173 "msg" : "created new user <username>"
173 "msg" : "created new user <username>"
174 }
174 }
175 error: null
175 error: null
176
176
177 get_users_group
177 get_users_group
178 ---------------
178 ---------------
179
179
180 Gets an existing users group. This command can be executed only using api_key
180 Gets an existing users group. This command can be executed only using api_key
181 belonging to user with admin rights.
181 belonging to user with admin rights.
182
182
183 INPUT::
183 INPUT::
184
184
185 api_key : "<api_key>"
185 api_key : "<api_key>"
186 method : "get_users_group"
186 method : "get_users_group"
187 args : {
187 args : {
188 "group_name" : "<name>"
188 "group_name" : "<name>"
189 }
189 }
190
190
191 OUTPUT::
191 OUTPUT::
192
192
193 result : None if group not exist
193 result : None if group not exist
194 {
194 {
195 "id" : "<id>",
195 "id" : "<id>",
196 "group_name" : "<groupname>",
196 "group_name" : "<groupname>",
197 "active": "<bool>",
197 "active": "<bool>",
198 "members" : [
198 "members" : [
199 { "id" : "<userid>",
199 { "id" : "<userid>",
200 "username" : "<username>",
200 "username" : "<username>",
201 "firstname": "<firstname>",
201 "firstname": "<firstname>",
202 "lastname" : "<lastname>",
202 "lastname" : "<lastname>",
203 "email" : "<email>",
203 "email" : "<email>",
204 "active" : "<bool>",
204 "active" : "<bool>",
205 "admin" :Β  "<bool>",
205 "admin" :Β  "<bool>",
206 "ldap" : "<ldap_dn>"
206 "ldap" : "<ldap_dn>"
207 },
207 },
208 …
208 …
209 ]
209 ]
210 }
210 }
211 error : null
211 error : null
212
212
213 get_users_groups
213 get_users_groups
214 ----------------
214 ----------------
215
215
216 Lists all existing users groups. This command can be executed only using
216 Lists all existing users groups. This command can be executed only using
217 api_key belonging to user with admin rights.
217 api_key belonging to user with admin rights.
218
218
219 INPUT::
219 INPUT::
220
220
221 api_key : "<api_key>"
221 api_key : "<api_key>"
222 method : "get_users_groups"
222 method : "get_users_groups"
223 args : { }
223 args : { }
224
224
225 OUTPUT::
225 OUTPUT::
226
226
227 result : [
227 result : [
228 {
228 {
229 "id" : "<id>",
229 "id" : "<id>",
230 "group_name" : "<groupname>",
230 "group_name" : "<groupname>",
231 "active": "<bool>",
231 "active": "<bool>",
232 "members" : [
232 "members" : [
233 {
233 {
234 "id" : "<userid>",
234 "id" : "<userid>",
235 "username" : "<username>",
235 "username" : "<username>",
236 "firstname": "<firstname>",
236 "firstname": "<firstname>",
237 "lastname" : "<lastname>",
237 "lastname" : "<lastname>",
238 "email" : "<email>",
238 "email" : "<email>",
239 "active" : "<bool>",
239 "active" : "<bool>",
240 "admin" :Β  "<bool>",
240 "admin" :Β  "<bool>",
241 "ldap" : "<ldap_dn>"
241 "ldap" : "<ldap_dn>"
242 },
242 },
243 …
243 …
244 ]
244 ]
245 }
245 }
246 ]
246 ]
247 error : null
247 error : null
248
248
249
249
250 create_users_group
250 create_users_group
251 ------------------
251 ------------------
252
252
253 Creates new users group. This command can be executed only using api_key
253 Creates new users group. This command can be executed only using api_key
254 belonging to user with admin rights
254 belonging to user with admin rights
255
255
256 INPUT::
256 INPUT::
257
257
258 api_key : "<api_key>"
258 api_key : "<api_key>"
259 method : "create_users_group"
259 method : "create_users_group"
260 args: {
260 args: {
261 "group_name": "<groupname>",
261 "group_name": "<groupname>",
262 "active":"<bool> = True"
262 "active":"<bool> = True"
263 }
263 }
264
264
265 OUTPUT::
265 OUTPUT::
266
266
267 result: {
267 result: {
268 "id": "<newusersgroupid>",
268 "id": "<newusersgroupid>",
269 "msg": "created new users group <groupname>"
269 "msg": "created new users group <groupname>"
270 }
270 }
271 error: null
271 error: null
272
272
273 add_user_to_users_group
273 add_user_to_users_group
274 -----------------------
274 -----------------------
275
275
276 Adds a user to a users group. This command can be executed only using api_key
276 Adds a user to a users group. This command can be executed only using api_key
277 belonging to user with admin rights
277 belonging to user with admin rights
278
278
279 INPUT::
279 INPUT::
280
280
281 api_key : "<api_key>"
281 api_key : "<api_key>"
282 method : "add_user_users_group"
282 method : "add_user_users_group"
283 args: {
283 args: {
284 "group_name" : "<groupname>",
284 "group_name" : "<groupname>",
285 "username" : "<username>"
285 "username" : "<username>"
286 }
286 }
287
287
288 OUTPUT::
288 OUTPUT::
289
289
290 result: {
290 result: {
291 "id": "<newusersgroupmemberid>",
291 "id": "<newusersgroupmemberid>",
292 "msg": "created new users group member"
292 "msg": "created new users group member"
293 }
293 }
294 error: null
294 error: null
295
295
296 get_repo
296 get_repo
297 --------
297 --------
298
298
299 Gets an existing repository. This command can be executed only using api_key
299 Gets an existing repository. This command can be executed only using api_key
300 belonging to user with admin rights
300 belonging to user with admin rights
301
301
302 INPUT::
302 INPUT::
303
303
304 api_key : "<api_key>"
304 api_key : "<api_key>"
305 method : "get_repo"
305 method : "get_repo"
306 args: {
306 args: {
307 "repo_name" : "<reponame>"
307 "repo_name" : "<reponame>"
308 }
308 }
309
309
310 OUTPUT::
310 OUTPUT::
311
311
312 result: None if repository does not exist or
312 result: None if repository does not exist or
313 {
313 {
314 "id" : "<id>",
314 "id" : "<id>",
315 "repo_name" : "<reponame>"
315 "repo_name" : "<reponame>"
316 "type" : "<type>",
316 "type" : "<type>",
317 "description" : "<description>",
317 "description" : "<description>",
318 "members" : [
318 "members" : [
319 { "id" : "<userid>",
319 { "id" : "<userid>",
320 "username" : "<username>",
320 "username" : "<username>",
321 "firstname": "<firstname>",
321 "firstname": "<firstname>",
322 "lastname" : "<lastname>",
322 "lastname" : "<lastname>",
323 "email" : "<email>",
323 "email" : "<email>",
324 "active" : "<bool>",
324 "active" : "<bool>",
325 "admin" :Β  "<bool>",
325 "admin" :Β  "<bool>",
326 "ldap" : "<ldap_dn>",
326 "ldap" : "<ldap_dn>",
327 "permission" : "repository.(read|write|admin)"
327 "permission" : "repository.(read|write|admin)"
328 },
328 },
329 …
329 …
330 {
330 {
331 "id" : "<usersgroupid>",
331 "id" : "<usersgroupid>",
332 "name" : "<usersgroupname>",
332 "name" : "<usersgroupname>",
333 "active": "<bool>",
333 "active": "<bool>",
334 "permission" : "repository.(read|write|admin)"
334 "permission" : "repository.(read|write|admin)"
335 },
335 },
336 …
336 …
337 ]
337 ]
338 }
338 }
339 error: null
339 error: null
340
340
341 get_repos
341 get_repos
342 ---------
342 ---------
343
343
344 Lists all existing repositories. This command can be executed only using api_key
344 Lists all existing repositories. This command can be executed only using api_key
345 belonging to user with admin rights
345 belonging to user with admin rights
346
346
347 INPUT::
347 INPUT::
348
348
349 api_key : "<api_key>"
349 api_key : "<api_key>"
350 method : "get_repos"
350 method : "get_repos"
351 args: { }
351 args: { }
352
352
353 OUTPUT::
353 OUTPUT::
354
354
355 result: [
355 result: [
356 {
356 {
357 "id" : "<id>",
357 "id" : "<id>",
358 "repo_name" : "<reponame>"
358 "repo_name" : "<reponame>"
359 "type" : "<type>",
359 "type" : "<type>",
360 "description" : "<description>"
360 "description" : "<description>"
361 },
361 },
362 …
362 …
363 ]
363 ]
364 error: null
364 error: null
365
365
366
366
367 get_repo_nodes
367 get_repo_nodes
368 --------------
368 --------------
369
369
370 returns a list of nodes and it's children in a flat list for a given path
370 returns a list of nodes and it's children in a flat list for a given path
371 at given revision. It's possible to specify ret_type to show only `files` or
371 at given revision. It's possible to specify ret_type to show only `files` or
372 `dirs`. This command can be executed only using api_key belonging to user
372 `dirs`. This command can be executed only using api_key belonging to user
373 with admin rights
373 with admin rights
374
374
375 INPUT::
375 INPUT::
376
376
377 api_key : "<api_key>"
377 api_key : "<api_key>"
378 method : "get_repo_nodes"
378 method : "get_repo_nodes"
379 args: {
379 args: {
380 "repo_name" : "<reponame>",
380 "repo_name" : "<reponame>",
381 "revision" : "<revision>",
381 "revision" : "<revision>",
382 "root_path" : "<root_path>",
382 "root_path" : "<root_path>",
383 "ret_type" : "<ret_type>" = 'all'
383 "ret_type" : "<ret_type>" = 'all'
384 }
384 }
385
385
386 OUTPUT::
386 OUTPUT::
387
387
388 result: [
388 result: [
389 {
389 {
390 "name" : "<name>"
390 "name" : "<name>"
391 "type" : "<type>",
391 "type" : "<type>",
392 },
392 },
393 …
393 …
394 ]
394 ]
395 error: null
395 error: null
396
396
397
397
398
398
399 create_repo
399 create_repo
400 -----------
400 -----------
401
401
402 Creates a repository. This command can be executed only using api_key
402 Creates a repository. This command can be executed only using api_key
403 belonging to user with admin rights.
403 belonging to user with admin rights.
404 If repository name contains "/", all needed repository groups will be created.
404 If repository name contains "/", all needed repository groups will be created.
405 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
405 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
406 and create "baz" repository with "bar" as group.
406 and create "baz" repository with "bar" as group.
407
407
408 INPUT::
408 INPUT::
409
409
410 api_key : "<api_key>"
410 api_key : "<api_key>"
411 method : "create_repo"
411 method : "create_repo"
412 args: {
412 args: {
413 "repo_name" : "<reponame>",
413 "repo_name" : "<reponame>",
414 "owner_name" : "<ownername>",
414 "owner_name" : "<ownername>",
415 "description" : "<description> = ''",
415 "description" : "<description> = ''",
416 "repo_type" : "<type> = 'hg'",
416 "repo_type" : "<type> = 'hg'",
417 "private" : "<bool> = False"
417 "private" : "<bool> = False"
418 }
418 }
419
419
420 OUTPUT::
420 OUTPUT::
421
421
422 result: {
422 result: {
423 "id": "<newrepoid>",
423 "id": "<newrepoid>",
424 "msg": "Created new repository <reponame>",
424 "msg": "Created new repository <reponame>",
425 }
425 }
426 error: null
426 error: null
427
427
428 add_user_to_repo
428 add_user_to_repo
429 ----------------
429 ----------------
430
430
431 Add a user to a repository. This command can be executed only using api_key
431 Add a user to a repository. This command can be executed only using api_key
432 belonging to user with admin rights.
432 belonging to user with admin rights.
433 If "perm" is None, user will be removed from the repository.
433 If "perm" is None, user will be removed from the repository.
434
434
435 INPUT::
435 INPUT::
436
436
437 api_key : "<api_key>"
437 api_key : "<api_key>"
438 method : "add_user_to_repo"
438 method : "add_user_to_repo"
439 args: {
439 args: {
440 "repo_name" : "<reponame>",
440 "repo_name" : "<reponame>",
441 "username" : "<username>",
441 "username" : "<username>",
442 "perm" : "(None|repository.(read|write|admin))",
442 "perm" : "(None|repository.(read|write|admin))",
443 }
443 }
444
444
445 OUTPUT::
445 OUTPUT::
446
446
447 result: {
447 result: {
448 "msg" : "Added perm: <perm> for <username> in repo: <reponame>"
448 "msg" : "Added perm: <perm> for <username> in repo: <reponame>"
449 }
449 }
450 error: null
450 error: null
451
451
452 add_users_group_to_repo
452 add_users_group_to_repo
453 -----------------------
453 -----------------------
454
454
455 Add a users group to a repository. This command can be executed only using
455 Add a users group to a repository. This command can be executed only using
456 api_key belonging to user with admin rights. If "perm" is None, group will
456 api_key belonging to user with admin rights. If "perm" is None, group will
457 be removed from the repository.
457 be removed from the repository.
458
458
459 INPUT::
459 INPUT::
460
460
461 api_key : "<api_key>"
461 api_key : "<api_key>"
462 method : "add_users_group_to_repo"
462 method : "add_users_group_to_repo"
463 args: {
463 args: {
464 "repo_name" : "<reponame>",
464 "repo_name" : "<reponame>",
465 "group_name" : "<groupname>",
465 "group_name" : "<groupname>",
466 "perm" : "(None|repository.(read|write|admin))",
466 "perm" : "(None|repository.(read|write|admin))",
467 }
467 }
468 OUTPUT::
468 OUTPUT::
469
469
470 result: {
470 result: {
471 "msg" : Added perm: <perm> for <groupname> in repo: <reponame>"
471 "msg" : Added perm: <perm> for <groupname> in repo: <reponame>"
472 }
472 }
473
473
@@ -1,478 +1,479 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6
6
7 1.3.0 (**XXXX-XX-XX**)
7 1.3.0 (**XXXX-XX-XX**)
8 ======================
8 ======================
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ----
14 ----
15
15
16 - code review, inspired by github code-comments
16 - code review, inspired by github code-comments
17 - #215 rst and markdown README files support
17 - #215 rst and markdown README files support
18 - #252 Container-based and proxy pass-through authentication support
18 - #252 Container-based and proxy pass-through authentication support
19 - #44 branch browser. Filtering of changelog by branches
19 - #44 branch browser. Filtering of changelog by branches
20 - mercurial bookmarks support
20 - mercurial bookmarks support
21 - new hover top menu, optimized to add maximum size for important views
21 - new hover top menu, optimized to add maximum size for important views
22 - configurable clone url template with possibility to specify protocol like
22 - configurable clone url template with possibility to specify protocol like
23 ssh:// or http:// and also manually alter other parts of clone_url.
23 ssh:// or http:// and also manually alter other parts of clone_url.
24 - enabled largefiles extension by default
24 - enabled largefiles extension by default
25 - optimized summary file pages and saved a lot of unused space in them
25 - optimized summary file pages and saved a lot of unused space in them
26 - #239 option to manually mark repository as fork
26 - #239 option to manually mark repository as fork
27 - #320 mapping of commit authors to RhodeCode users
27 - #320 mapping of commit authors to RhodeCode users
28 - #304 hashes are displayed using monospace font
28 - #304 hashes are displayed using monospace font
29 - diff configuration, toggle white lines and context lines
29 - diff configuration, toggle white lines and context lines
30 - #307 configurable diffs, whitespace toggle, increasing context lines
30 - #307 configurable diffs, whitespace toggle, increasing context lines
31 - sorting on branches, tags and bookmarks using YUI datatable
31 - sorting on branches, tags and bookmarks using YUI datatable
32 - improved file filter on files page
32 - improved file filter on files page
33 - implements #330 api method for listing nodes ar particular revision
33 - implements #330 api method for listing nodes ar particular revision
34 - #73 added linking issues in commit messages to chosen issue tracker url
34 - #73 added linking issues in commit messages to chosen issue tracker url
35 based on user defined regular expression
35 based on user defined regular expression
36 - added linking of changesets in commit messages
36 - added linking of changesets in commit messages
37 - new compact changelog with expandable commit messages
37 - new compact changelog with expandable commit messages
38 - firstname and lastname are optional in user creation
38
39
39 fixes
40 fixes
40 -----
41 -----
41
42
42 - rewrote dbsession management for atomic operations, and better error handling
43 - rewrote dbsession management for atomic operations, and better error handling
43 - fixed sorting of repo tables
44 - fixed sorting of repo tables
44 - #326 escape of special html entities in diffs
45 - #326 escape of special html entities in diffs
45 - normalized user_name => username in api attributes
46 - normalized user_name => username in api attributes
46 - fixes #298 ldap created users with mixed case emails created conflicts
47 - fixes #298 ldap created users with mixed case emails created conflicts
47 on saving a form
48 on saving a form
48 - fixes issue when owner of a repo couldn't revoke permissions for users
49 - fixes issue when owner of a repo couldn't revoke permissions for users
49 and groups
50 and groups
50 - fixes #271 rare JSON serialization problem with statistics
51 - fixes #271 rare JSON serialization problem with statistics
51 - fixes #337 missing validation check for conflicting names of a group with a
52 - fixes #337 missing validation check for conflicting names of a group with a
52 repositories group
53 repositories group
53 - #340 fixed session problem for mysql and celery tasks
54 - #340 fixed session problem for mysql and celery tasks
54 - fixed #331 RhodeCode mangles repository names if the a repository group
55 - fixed #331 RhodeCode mangles repository names if the a repository group
55 contains the "full path" to the repositories
56 contains the "full path" to the repositories
56
57
57
58
58 1.2.4 (**2012-01-19**)
59 1.2.4 (**2012-01-19**)
59 ======================
60 ======================
60
61
61 news
62 news
62 ----
63 ----
63
64
64 - RhodeCode is bundled with mercurial series 2.0.X by default, with
65 - RhodeCode is bundled with mercurial series 2.0.X by default, with
65 full support to largefiles extension. Enabled by default in new installations
66 full support to largefiles extension. Enabled by default in new installations
66 - #329 Ability to Add/Remove Groups to/from a Repository via AP
67 - #329 Ability to Add/Remove Groups to/from a Repository via AP
67 - added requires.txt file with requirements
68 - added requires.txt file with requirements
68
69
69 fixes
70 fixes
70 -----
71 -----
71
72
72 - fixes db session issues with celery when emailing admins
73 - fixes db session issues with celery when emailing admins
73 - #331 RhodeCode mangles repository names if the a repository group
74 - #331 RhodeCode mangles repository names if the a repository group
74 contains the "full path" to the repositories
75 contains the "full path" to the repositories
75 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
76 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
76 - DB session cleanup after hg protocol operations, fixes issues with
77 - DB session cleanup after hg protocol operations, fixes issues with
77 `mysql has gone away` errors
78 `mysql has gone away` errors
78 - #333 doc fixes for get_repo api function
79 - #333 doc fixes for get_repo api function
79 - #271 rare JSON serialization problem with statistics enabled
80 - #271 rare JSON serialization problem with statistics enabled
80 - #337 Fixes issues with validation of repository name conflicting with
81 - #337 Fixes issues with validation of repository name conflicting with
81 a group name. A proper message is now displayed.
82 a group name. A proper message is now displayed.
82 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
83 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
83 doesn't work
84 doesn't work
84 - #316 fixes issues with web description in hgrc files
85 - #316 fixes issues with web description in hgrc files
85
86
86 1.2.3 (**2011-11-02**)
87 1.2.3 (**2011-11-02**)
87 ======================
88 ======================
88
89
89 news
90 news
90 ----
91 ----
91
92
92 - added option to manage repos group for non admin users
93 - added option to manage repos group for non admin users
93 - added following API methods for get_users, create_user, get_users_groups,
94 - added following API methods for get_users, create_user, get_users_groups,
94 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
95 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
95 get_repo, create_repo, add_user_to_repo
96 get_repo, create_repo, add_user_to_repo
96 - implements #237 added password confirmation for my account
97 - implements #237 added password confirmation for my account
97 and admin edit user.
98 and admin edit user.
98 - implements #291 email notification for global events are now sent to all
99 - implements #291 email notification for global events are now sent to all
99 administrator users, and global config email.
100 administrator users, and global config email.
100
101
101 fixes
102 fixes
102 -----
103 -----
103
104
104 - added option for passing auth method for smtp mailer
105 - added option for passing auth method for smtp mailer
105 - #276 issue with adding a single user with id>10 to usergroups
106 - #276 issue with adding a single user with id>10 to usergroups
106 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
107 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
107 - #288 fixes managing of repos in a group for non admin user
108 - #288 fixes managing of repos in a group for non admin user
108
109
109 1.2.2 (**2011-10-17**)
110 1.2.2 (**2011-10-17**)
110 ======================
111 ======================
111
112
112 news
113 news
113 ----
114 ----
114
115
115 - #226 repo groups are available by path instead of numerical id
116 - #226 repo groups are available by path instead of numerical id
116
117
117 fixes
118 fixes
118 -----
119 -----
119
120
120 - #259 Groups with the same name but with different parent group
121 - #259 Groups with the same name but with different parent group
121 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
122 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
122 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
123 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
123 - #265 ldap save fails sometimes on converting attributes to booleans,
124 - #265 ldap save fails sometimes on converting attributes to booleans,
124 added getter and setter into model that will prevent from this on db model level
125 added getter and setter into model that will prevent from this on db model level
125 - fixed problems with timestamps issues #251 and #213
126 - fixed problems with timestamps issues #251 and #213
126 - fixes #266 RhodeCode allows to create repo with the same name and in
127 - fixes #266 RhodeCode allows to create repo with the same name and in
127 the same parent as group
128 the same parent as group
128 - fixes #245 Rescan of the repositories on Windows
129 - fixes #245 Rescan of the repositories on Windows
129 - fixes #248 cannot edit repos inside a group on windows
130 - fixes #248 cannot edit repos inside a group on windows
130 - fixes #219 forking problems on windows
131 - fixes #219 forking problems on windows
131
132
132 1.2.1 (**2011-10-08**)
133 1.2.1 (**2011-10-08**)
133 ======================
134 ======================
134
135
135 news
136 news
136 ----
137 ----
137
138
138
139
139 fixes
140 fixes
140 -----
141 -----
141
142
142 - fixed problems with basic auth and push problems
143 - fixed problems with basic auth and push problems
143 - gui fixes
144 - gui fixes
144 - fixed logger
145 - fixed logger
145
146
146 1.2.0 (**2011-10-07**)
147 1.2.0 (**2011-10-07**)
147 ======================
148 ======================
148
149
149 news
150 news
150 ----
151 ----
151
152
152 - implemented #47 repository groups
153 - implemented #47 repository groups
153 - implemented #89 Can setup google analytics code from settings menu
154 - implemented #89 Can setup google analytics code from settings menu
154 - implemented #91 added nicer looking archive urls with more download options
155 - implemented #91 added nicer looking archive urls with more download options
155 like tags, branches
156 like tags, branches
156 - implemented #44 into file browsing, and added follow branch option
157 - implemented #44 into file browsing, and added follow branch option
157 - implemented #84 downloads can be enabled/disabled for each repository
158 - implemented #84 downloads can be enabled/disabled for each repository
158 - anonymous repository can be cloned without having to pass default:default
159 - anonymous repository can be cloned without having to pass default:default
159 into clone url
160 into clone url
160 - fixed #90 whoosh indexer can index chooses repositories passed in command
161 - fixed #90 whoosh indexer can index chooses repositories passed in command
161 line
162 line
162 - extended journal with day aggregates and paging
163 - extended journal with day aggregates and paging
163 - implemented #107 source code lines highlight ranges
164 - implemented #107 source code lines highlight ranges
164 - implemented #93 customizable changelog on combined revision ranges -
165 - implemented #93 customizable changelog on combined revision ranges -
165 equivalent of githubs compare view
166 equivalent of githubs compare view
166 - implemented #108 extended and more powerful LDAP configuration
167 - implemented #108 extended and more powerful LDAP configuration
167 - implemented #56 users groups
168 - implemented #56 users groups
168 - major code rewrites optimized codes for speed and memory usage
169 - major code rewrites optimized codes for speed and memory usage
169 - raw and diff downloads are now in git format
170 - raw and diff downloads are now in git format
170 - setup command checks for write access to given path
171 - setup command checks for write access to given path
171 - fixed many issues with international characters and unicode. It uses utf8
172 - fixed many issues with international characters and unicode. It uses utf8
172 decode with replace to provide less errors even with non utf8 encoded strings
173 decode with replace to provide less errors even with non utf8 encoded strings
173 - #125 added API KEY access to feeds
174 - #125 added API KEY access to feeds
174 - #109 Repository can be created from external Mercurial link (aka. remote
175 - #109 Repository can be created from external Mercurial link (aka. remote
175 repository, and manually updated (via pull) from admin panel
176 repository, and manually updated (via pull) from admin panel
176 - beta git support - push/pull server + basic view for git repos
177 - beta git support - push/pull server + basic view for git repos
177 - added followers page and forks page
178 - added followers page and forks page
178 - server side file creation (with binary file upload interface)
179 - server side file creation (with binary file upload interface)
179 and edition with commits powered by codemirror
180 and edition with commits powered by codemirror
180 - #111 file browser file finder, quick lookup files on whole file tree
181 - #111 file browser file finder, quick lookup files on whole file tree
181 - added quick login sliding menu into main page
182 - added quick login sliding menu into main page
182 - changelog uses lazy loading of affected files details, in some scenarios
183 - changelog uses lazy loading of affected files details, in some scenarios
183 this can improve speed of changelog page dramatically especially for
184 this can improve speed of changelog page dramatically especially for
184 larger repositories.
185 larger repositories.
185 - implements #214 added support for downloading subrepos in download menu.
186 - implements #214 added support for downloading subrepos in download menu.
186 - Added basic API for direct operations on rhodecode via JSON
187 - Added basic API for direct operations on rhodecode via JSON
187 - Implemented advanced hook management
188 - Implemented advanced hook management
188
189
189 fixes
190 fixes
190 -----
191 -----
191
192
192 - fixed file browser bug, when switching into given form revision the url was
193 - fixed file browser bug, when switching into given form revision the url was
193 not changing
194 not changing
194 - fixed propagation to error controller on simplehg and simplegit middlewares
195 - fixed propagation to error controller on simplehg and simplegit middlewares
195 - fixed error when trying to make a download on empty repository
196 - fixed error when trying to make a download on empty repository
196 - fixed problem with '[' chars in commit messages in journal
197 - fixed problem with '[' chars in commit messages in journal
197 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
198 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
198 - journal fork fixes
199 - journal fork fixes
199 - removed issue with space inside renamed repository after deletion
200 - removed issue with space inside renamed repository after deletion
200 - fixed strange issue on formencode imports
201 - fixed strange issue on formencode imports
201 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
202 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
202 - #150 fixes for errors on repositories mapped in db but corrupted in
203 - #150 fixes for errors on repositories mapped in db but corrupted in
203 filesystem
204 filesystem
204 - fixed problem with ascendant characters in realm #181
205 - fixed problem with ascendant characters in realm #181
205 - fixed problem with sqlite file based database connection pool
206 - fixed problem with sqlite file based database connection pool
206 - whoosh indexer and code stats share the same dynamic extensions map
207 - whoosh indexer and code stats share the same dynamic extensions map
207 - fixes #188 - relationship delete of repo_to_perm entry on user removal
208 - fixes #188 - relationship delete of repo_to_perm entry on user removal
208 - fixes issue #189 Trending source files shows "show more" when no more exist
209 - fixes issue #189 Trending source files shows "show more" when no more exist
209 - fixes issue #197 Relative paths for pidlocks
210 - fixes issue #197 Relative paths for pidlocks
210 - fixes issue #198 password will require only 3 chars now for login form
211 - fixes issue #198 password will require only 3 chars now for login form
211 - fixes issue #199 wrong redirection for non admin users after creating a repository
212 - fixes issue #199 wrong redirection for non admin users after creating a repository
212 - fixes issues #202, bad db constraint made impossible to attach same group
213 - fixes issues #202, bad db constraint made impossible to attach same group
213 more than one time. Affects only mysql/postgres
214 more than one time. Affects only mysql/postgres
214 - fixes #218 os.kill patch for windows was missing sig param
215 - fixes #218 os.kill patch for windows was missing sig param
215 - improved rendering of dag (they are not trimmed anymore when number of
216 - improved rendering of dag (they are not trimmed anymore when number of
216 heads exceeds 5)
217 heads exceeds 5)
217
218
218 1.1.8 (**2011-04-12**)
219 1.1.8 (**2011-04-12**)
219 ======================
220 ======================
220
221
221 news
222 news
222 ----
223 ----
223
224
224 - improved windows support
225 - improved windows support
225
226
226 fixes
227 fixes
227 -----
228 -----
228
229
229 - fixed #140 freeze of python dateutil library, since new version is python2.x
230 - fixed #140 freeze of python dateutil library, since new version is python2.x
230 incompatible
231 incompatible
231 - setup-app will check for write permission in given path
232 - setup-app will check for write permission in given path
232 - cleaned up license info issue #149
233 - cleaned up license info issue #149
233 - fixes for issues #137,#116 and problems with unicode and accented characters.
234 - fixes for issues #137,#116 and problems with unicode and accented characters.
234 - fixes crashes on gravatar, when passed in email as unicode
235 - fixes crashes on gravatar, when passed in email as unicode
235 - fixed tooltip flickering problems
236 - fixed tooltip flickering problems
236 - fixed came_from redirection on windows
237 - fixed came_from redirection on windows
237 - fixed logging modules, and sql formatters
238 - fixed logging modules, and sql formatters
238 - windows fixes for os.kill issue #133
239 - windows fixes for os.kill issue #133
239 - fixes path splitting for windows issues #148
240 - fixes path splitting for windows issues #148
240 - fixed issue #143 wrong import on migration to 1.1.X
241 - fixed issue #143 wrong import on migration to 1.1.X
241 - fixed problems with displaying binary files, thanks to Thomas Waldmann
242 - fixed problems with displaying binary files, thanks to Thomas Waldmann
242 - removed name from archive files since it's breaking ui for long repo names
243 - removed name from archive files since it's breaking ui for long repo names
243 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
244 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
244 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
245 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
245 Thomas Waldmann
246 Thomas Waldmann
246 - fixed issue #166 summary pager was skipping 10 revisions on second page
247 - fixed issue #166 summary pager was skipping 10 revisions on second page
247
248
248
249
249 1.1.7 (**2011-03-23**)
250 1.1.7 (**2011-03-23**)
250 ======================
251 ======================
251
252
252 news
253 news
253 ----
254 ----
254
255
255 fixes
256 fixes
256 -----
257 -----
257
258
258 - fixed (again) #136 installation support for FreeBSD
259 - fixed (again) #136 installation support for FreeBSD
259
260
260
261
261 1.1.6 (**2011-03-21**)
262 1.1.6 (**2011-03-21**)
262 ======================
263 ======================
263
264
264 news
265 news
265 ----
266 ----
266
267
267 fixes
268 fixes
268 -----
269 -----
269
270
270 - fixed #136 installation support for FreeBSD
271 - fixed #136 installation support for FreeBSD
271 - RhodeCode will check for python version during installation
272 - RhodeCode will check for python version during installation
272
273
273 1.1.5 (**2011-03-17**)
274 1.1.5 (**2011-03-17**)
274 ======================
275 ======================
275
276
276 news
277 news
277 ----
278 ----
278
279
279 - basic windows support, by exchanging pybcrypt into sha256 for windows only
280 - basic windows support, by exchanging pybcrypt into sha256 for windows only
280 highly inspired by idea of mantis406
281 highly inspired by idea of mantis406
281
282
282 fixes
283 fixes
283 -----
284 -----
284
285
285 - fixed sorting by author in main page
286 - fixed sorting by author in main page
286 - fixed crashes with diffs on binary files
287 - fixed crashes with diffs on binary files
287 - fixed #131 problem with boolean values for LDAP
288 - fixed #131 problem with boolean values for LDAP
288 - fixed #122 mysql problems thanks to striker69
289 - fixed #122 mysql problems thanks to striker69
289 - fixed problem with errors on calling raw/raw_files/annotate functions
290 - fixed problem with errors on calling raw/raw_files/annotate functions
290 with unknown revisions
291 with unknown revisions
291 - fixed returned rawfiles attachment names with international character
292 - fixed returned rawfiles attachment names with international character
292 - cleaned out docs, big thanks to Jason Harris
293 - cleaned out docs, big thanks to Jason Harris
293
294
294 1.1.4 (**2011-02-19**)
295 1.1.4 (**2011-02-19**)
295 ======================
296 ======================
296
297
297 news
298 news
298 ----
299 ----
299
300
300 fixes
301 fixes
301 -----
302 -----
302
303
303 - fixed formencode import problem on settings page, that caused server crash
304 - fixed formencode import problem on settings page, that caused server crash
304 when that page was accessed as first after server start
305 when that page was accessed as first after server start
305 - journal fixes
306 - journal fixes
306 - fixed option to access repository just by entering http://server/<repo_name>
307 - fixed option to access repository just by entering http://server/<repo_name>
307
308
308 1.1.3 (**2011-02-16**)
309 1.1.3 (**2011-02-16**)
309 ======================
310 ======================
310
311
311 news
312 news
312 ----
313 ----
313
314
314 - implemented #102 allowing the '.' character in username
315 - implemented #102 allowing the '.' character in username
315 - added option to access repository just by entering http://server/<repo_name>
316 - added option to access repository just by entering http://server/<repo_name>
316 - celery task ignores result for better performance
317 - celery task ignores result for better performance
317
318
318 fixes
319 fixes
319 -----
320 -----
320
321
321 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
322 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
322 apollo13 and Johan Walles
323 apollo13 and Johan Walles
323 - small fixes in journal
324 - small fixes in journal
324 - fixed problems with getting setting for celery from .ini files
325 - fixed problems with getting setting for celery from .ini files
325 - registration, password reset and login boxes share the same title as main
326 - registration, password reset and login boxes share the same title as main
326 application now
327 application now
327 - fixed #113: to high permissions to fork repository
328 - fixed #113: to high permissions to fork repository
328 - fixed problem with '[' chars in commit messages in journal
329 - fixed problem with '[' chars in commit messages in journal
329 - removed issue with space inside renamed repository after deletion
330 - removed issue with space inside renamed repository after deletion
330 - db transaction fixes when filesystem repository creation failed
331 - db transaction fixes when filesystem repository creation failed
331 - fixed #106 relation issues on databases different than sqlite
332 - fixed #106 relation issues on databases different than sqlite
332 - fixed static files paths links to use of url() method
333 - fixed static files paths links to use of url() method
333
334
334 1.1.2 (**2011-01-12**)
335 1.1.2 (**2011-01-12**)
335 ======================
336 ======================
336
337
337 news
338 news
338 ----
339 ----
339
340
340
341
341 fixes
342 fixes
342 -----
343 -----
343
344
344 - fixes #98 protection against float division of percentage stats
345 - fixes #98 protection against float division of percentage stats
345 - fixed graph bug
346 - fixed graph bug
346 - forced webhelpers version since it was making troubles during installation
347 - forced webhelpers version since it was making troubles during installation
347
348
348 1.1.1 (**2011-01-06**)
349 1.1.1 (**2011-01-06**)
349 ======================
350 ======================
350
351
351 news
352 news
352 ----
353 ----
353
354
354 - added force https option into ini files for easier https usage (no need to
355 - added force https option into ini files for easier https usage (no need to
355 set server headers with this options)
356 set server headers with this options)
356 - small css updates
357 - small css updates
357
358
358 fixes
359 fixes
359 -----
360 -----
360
361
361 - fixed #96 redirect loop on files view on repositories without changesets
362 - fixed #96 redirect loop on files view on repositories without changesets
362 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
363 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
363 and server crashed with errors
364 and server crashed with errors
364 - fixed large tooltips problems on main page
365 - fixed large tooltips problems on main page
365 - fixed #92 whoosh indexer is more error proof
366 - fixed #92 whoosh indexer is more error proof
366
367
367 1.1.0 (**2010-12-18**)
368 1.1.0 (**2010-12-18**)
368 ======================
369 ======================
369
370
370 news
371 news
371 ----
372 ----
372
373
373 - rewrite of internals for vcs >=0.1.10
374 - rewrite of internals for vcs >=0.1.10
374 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
375 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
375 with older clients
376 with older clients
376 - anonymous access, authentication via ldap
377 - anonymous access, authentication via ldap
377 - performance upgrade for cached repos list - each repository has its own
378 - performance upgrade for cached repos list - each repository has its own
378 cache that's invalidated when needed.
379 cache that's invalidated when needed.
379 - performance upgrades on repositories with large amount of commits (20K+)
380 - performance upgrades on repositories with large amount of commits (20K+)
380 - main page quick filter for filtering repositories
381 - main page quick filter for filtering repositories
381 - user dashboards with ability to follow chosen repositories actions
382 - user dashboards with ability to follow chosen repositories actions
382 - sends email to admin on new user registration
383 - sends email to admin on new user registration
383 - added cache/statistics reset options into repository settings
384 - added cache/statistics reset options into repository settings
384 - more detailed action logger (based on hooks) with pushed changesets lists
385 - more detailed action logger (based on hooks) with pushed changesets lists
385 and options to disable those hooks from admin panel
386 and options to disable those hooks from admin panel
386 - introduced new enhanced changelog for merges that shows more accurate results
387 - introduced new enhanced changelog for merges that shows more accurate results
387 - new improved and faster code stats (based on pygments lexers mapping tables,
388 - new improved and faster code stats (based on pygments lexers mapping tables,
388 showing up to 10 trending sources for each repository. Additionally stats
389 showing up to 10 trending sources for each repository. Additionally stats
389 can be disabled in repository settings.
390 can be disabled in repository settings.
390 - gui optimizations, fixed application width to 1024px
391 - gui optimizations, fixed application width to 1024px
391 - added cut off (for large files/changesets) limit into config files
392 - added cut off (for large files/changesets) limit into config files
392 - whoosh, celeryd, upgrade moved to paster command
393 - whoosh, celeryd, upgrade moved to paster command
393 - other than sqlite database backends can be used
394 - other than sqlite database backends can be used
394
395
395 fixes
396 fixes
396 -----
397 -----
397
398
398 - fixes #61 forked repo was showing only after cache expired
399 - fixes #61 forked repo was showing only after cache expired
399 - fixes #76 no confirmation on user deletes
400 - fixes #76 no confirmation on user deletes
400 - fixes #66 Name field misspelled
401 - fixes #66 Name field misspelled
401 - fixes #72 block user removal when he owns repositories
402 - fixes #72 block user removal when he owns repositories
402 - fixes #69 added password confirmation fields
403 - fixes #69 added password confirmation fields
403 - fixes #87 RhodeCode crashes occasionally on updating repository owner
404 - fixes #87 RhodeCode crashes occasionally on updating repository owner
404 - fixes #82 broken annotations on files with more than 1 blank line at the end
405 - fixes #82 broken annotations on files with more than 1 blank line at the end
405 - a lot of fixes and tweaks for file browser
406 - a lot of fixes and tweaks for file browser
406 - fixed detached session issues
407 - fixed detached session issues
407 - fixed when user had no repos he would see all repos listed in my account
408 - fixed when user had no repos he would see all repos listed in my account
408 - fixed ui() instance bug when global hgrc settings was loaded for server
409 - fixed ui() instance bug when global hgrc settings was loaded for server
409 instance and all hgrc options were merged with our db ui() object
410 instance and all hgrc options were merged with our db ui() object
410 - numerous small bugfixes
411 - numerous small bugfixes
411
412
412 (special thanks for TkSoh for detailed feedback)
413 (special thanks for TkSoh for detailed feedback)
413
414
414
415
415 1.0.2 (**2010-11-12**)
416 1.0.2 (**2010-11-12**)
416 ======================
417 ======================
417
418
418 news
419 news
419 ----
420 ----
420
421
421 - tested under python2.7
422 - tested under python2.7
422 - bumped sqlalchemy and celery versions
423 - bumped sqlalchemy and celery versions
423
424
424 fixes
425 fixes
425 -----
426 -----
426
427
427 - fixed #59 missing graph.js
428 - fixed #59 missing graph.js
428 - fixed repo_size crash when repository had broken symlinks
429 - fixed repo_size crash when repository had broken symlinks
429 - fixed python2.5 crashes.
430 - fixed python2.5 crashes.
430
431
431
432
432 1.0.1 (**2010-11-10**)
433 1.0.1 (**2010-11-10**)
433 ======================
434 ======================
434
435
435 news
436 news
436 ----
437 ----
437
438
438 - small css updated
439 - small css updated
439
440
440 fixes
441 fixes
441 -----
442 -----
442
443
443 - fixed #53 python2.5 incompatible enumerate calls
444 - fixed #53 python2.5 incompatible enumerate calls
444 - fixed #52 disable mercurial extension for web
445 - fixed #52 disable mercurial extension for web
445 - fixed #51 deleting repositories don't delete it's dependent objects
446 - fixed #51 deleting repositories don't delete it's dependent objects
446
447
447
448
448 1.0.0 (**2010-11-02**)
449 1.0.0 (**2010-11-02**)
449 ======================
450 ======================
450
451
451 - security bugfix simplehg wasn't checking for permissions on commands
452 - security bugfix simplehg wasn't checking for permissions on commands
452 other than pull or push.
453 other than pull or push.
453 - fixed doubled messages after push or pull in admin journal
454 - fixed doubled messages after push or pull in admin journal
454 - templating and css corrections, fixed repo switcher on chrome, updated titles
455 - templating and css corrections, fixed repo switcher on chrome, updated titles
455 - admin menu accessible from options menu on repository view
456 - admin menu accessible from options menu on repository view
456 - permissions cached queries
457 - permissions cached queries
457
458
458 1.0.0rc4 (**2010-10-12**)
459 1.0.0rc4 (**2010-10-12**)
459 ==========================
460 ==========================
460
461
461 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
462 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
462 - removed cache_manager settings from sqlalchemy meta
463 - removed cache_manager settings from sqlalchemy meta
463 - added sqlalchemy cache settings to ini files
464 - added sqlalchemy cache settings to ini files
464 - validated password length and added second try of failure on paster setup-app
465 - validated password length and added second try of failure on paster setup-app
465 - fixed setup database destroy prompt even when there was no db
466 - fixed setup database destroy prompt even when there was no db
466
467
467
468
468 1.0.0rc3 (**2010-10-11**)
469 1.0.0rc3 (**2010-10-11**)
469 =========================
470 =========================
470
471
471 - fixed i18n during installation.
472 - fixed i18n during installation.
472
473
473 1.0.0rc2 (**2010-10-11**)
474 1.0.0rc2 (**2010-10-11**)
474 =========================
475 =========================
475
476
476 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
477 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
477 occure. After vcs is fixed it'll be put back again.
478 occure. After vcs is fixed it'll be put back again.
478 - templating/css rewrites, optimized css. No newline at end of file
479 - templating/css rewrites, optimized css.
@@ -1,510 +1,510 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 API controller for RhodeCode
6 API controller for RhodeCode
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import traceback
28 import traceback
29 import logging
29 import logging
30
30
31 from sqlalchemy.orm.exc import NoResultFound
31 from sqlalchemy.orm.exc import NoResultFound
32
32
33 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
33 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
34 from rhodecode.lib.auth import HasPermissionAllDecorator, \
34 from rhodecode.lib.auth import HasPermissionAllDecorator, \
35 HasPermissionAnyDecorator
35 HasPermissionAnyDecorator
36
36
37 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.user import UserModel
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.repo_permission import RepositoryPermissionModel
42 from rhodecode.model.repo_permission import RepositoryPermissionModel
43 from rhodecode.model.users_group import UsersGroupModel
43 from rhodecode.model.users_group import UsersGroupModel
44 from rhodecode.model.repos_group import ReposGroupModel
44 from rhodecode.model.repos_group import ReposGroupModel
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class ApiController(JSONRPCController):
50 class ApiController(JSONRPCController):
51 """
51 """
52 API Controller
52 API Controller
53
53
54
54
55 Each method needs to have USER as argument this is then based on given
55 Each method needs to have USER as argument this is then based on given
56 API_KEY propagated as instance of user object
56 API_KEY propagated as instance of user object
57
57
58 Preferably this should be first argument also
58 Preferably this should be first argument also
59
59
60
60
61 Each function should also **raise** JSONRPCError for any
61 Each function should also **raise** JSONRPCError for any
62 errors that happens
62 errors that happens
63
63
64 """
64 """
65
65
66 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
67 def pull(self, apiuser, repo_name):
67 def pull(self, apiuser, repo_name):
68 """
68 """
69 Dispatch pull action on given repo
69 Dispatch pull action on given repo
70
70
71
71
72 :param user:
72 :param user:
73 :param repo_name:
73 :param repo_name:
74 """
74 """
75
75
76 if Repository.is_valid(repo_name) is False:
76 if Repository.is_valid(repo_name) is False:
77 raise JSONRPCError('Unknown repo "%s"' % repo_name)
77 raise JSONRPCError('Unknown repo "%s"' % repo_name)
78
78
79 try:
79 try:
80 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
80 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
81 return 'Pulled from %s' % repo_name
81 return 'Pulled from %s' % repo_name
82 except Exception:
82 except Exception:
83 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
83 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
84
84
85 @HasPermissionAllDecorator('hg.admin')
85 @HasPermissionAllDecorator('hg.admin')
86 def get_user(self, apiuser, username):
86 def get_user(self, apiuser, username):
87 """"
87 """"
88 Get a user by username
88 Get a user by username
89
89
90 :param apiuser:
90 :param apiuser:
91 :param username:
91 :param username:
92 """
92 """
93
93
94 user = User.get_by_username(username)
94 user = User.get_by_username(username)
95 if not user:
95 if not user:
96 return None
96 return None
97
97
98 return dict(
98 return dict(
99 id=user.user_id,
99 id=user.user_id,
100 username=user.username,
100 username=user.username,
101 firstname=user.name,
101 firstname=user.name,
102 lastname=user.lastname,
102 lastname=user.lastname,
103 email=user.email,
103 email=user.email,
104 active=user.active,
104 active=user.active,
105 admin=user.admin,
105 admin=user.admin,
106 ldap=user.ldap_dn
106 ldap=user.ldap_dn
107 )
107 )
108
108
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def get_users(self, apiuser):
110 def get_users(self, apiuser):
111 """"
111 """"
112 Get all users
112 Get all users
113
113
114 :param apiuser:
114 :param apiuser:
115 """
115 """
116
116
117 result = []
117 result = []
118 for user in User.getAll():
118 for user in User.getAll():
119 result.append(
119 result.append(
120 dict(
120 dict(
121 id=user.user_id,
121 id=user.user_id,
122 username=user.username,
122 username=user.username,
123 firstname=user.name,
123 firstname=user.name,
124 lastname=user.lastname,
124 lastname=user.lastname,
125 email=user.email,
125 email=user.email,
126 active=user.active,
126 active=user.active,
127 admin=user.admin,
127 admin=user.admin,
128 ldap=user.ldap_dn
128 ldap=user.ldap_dn
129 )
129 )
130 )
130 )
131 return result
131 return result
132
132
133 @HasPermissionAllDecorator('hg.admin')
133 @HasPermissionAllDecorator('hg.admin')
134 def create_user(self, apiuser, username, password, firstname,
134 def create_user(self, apiuser, username, password, email, firstname=None,
135 lastname, email, active=True, admin=False, ldap_dn=None):
135 lastname=None, active=True, admin=False, ldap_dn=None):
136 """
136 """
137 Create new user or updates current one
137 Create new user or updates current one
138
138
139 :param apiuser:
139 :param apiuser:
140 :param username:
140 :param username:
141 :param password:
141 :param password:
142 :param email:
142 :param name:
143 :param name:
143 :param lastname:
144 :param lastname:
144 :param email:
145 :param active:
145 :param active:
146 :param admin:
146 :param admin:
147 :param ldap_dn:
147 :param ldap_dn:
148 """
148 """
149
149
150 if User.get_by_username(username):
150 if User.get_by_username(username):
151 raise JSONRPCError("user %s already exist" % username)
151 raise JSONRPCError("user %s already exist" % username)
152
152
153 try:
153 try:
154 usr = UserModel().create_or_update(
154 usr = UserModel().create_or_update(
155 username, password, email, firstname,
155 username, password, email, firstname,
156 lastname, active, admin, ldap_dn
156 lastname, active, admin, ldap_dn
157 )
157 )
158 Session.commit()
158 Session.commit()
159 return dict(
159 return dict(
160 id=usr.user_id,
160 id=usr.user_id,
161 msg='created new user %s' % username
161 msg='created new user %s' % username
162 )
162 )
163 except Exception:
163 except Exception:
164 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
165 raise JSONRPCError('failed to create user %s' % username)
165 raise JSONRPCError('failed to create user %s' % username)
166
166
167 @HasPermissionAllDecorator('hg.admin')
167 @HasPermissionAllDecorator('hg.admin')
168 def get_users_group(self, apiuser, group_name):
168 def get_users_group(self, apiuser, group_name):
169 """"
169 """"
170 Get users group by name
170 Get users group by name
171
171
172 :param apiuser:
172 :param apiuser:
173 :param group_name:
173 :param group_name:
174 """
174 """
175
175
176 users_group = UsersGroup.get_by_group_name(group_name)
176 users_group = UsersGroup.get_by_group_name(group_name)
177 if not users_group:
177 if not users_group:
178 return None
178 return None
179
179
180 members = []
180 members = []
181 for user in users_group.members:
181 for user in users_group.members:
182 user = user.user
182 user = user.user
183 members.append(dict(id=user.user_id,
183 members.append(dict(id=user.user_id,
184 username=user.username,
184 username=user.username,
185 firstname=user.name,
185 firstname=user.name,
186 lastname=user.lastname,
186 lastname=user.lastname,
187 email=user.email,
187 email=user.email,
188 active=user.active,
188 active=user.active,
189 admin=user.admin,
189 admin=user.admin,
190 ldap=user.ldap_dn))
190 ldap=user.ldap_dn))
191
191
192 return dict(id=users_group.users_group_id,
192 return dict(id=users_group.users_group_id,
193 group_name=users_group.users_group_name,
193 group_name=users_group.users_group_name,
194 active=users_group.users_group_active,
194 active=users_group.users_group_active,
195 members=members)
195 members=members)
196
196
197 @HasPermissionAllDecorator('hg.admin')
197 @HasPermissionAllDecorator('hg.admin')
198 def get_users_groups(self, apiuser):
198 def get_users_groups(self, apiuser):
199 """"
199 """"
200 Get all users groups
200 Get all users groups
201
201
202 :param apiuser:
202 :param apiuser:
203 """
203 """
204
204
205 result = []
205 result = []
206 for users_group in UsersGroup.getAll():
206 for users_group in UsersGroup.getAll():
207 members = []
207 members = []
208 for user in users_group.members:
208 for user in users_group.members:
209 user = user.user
209 user = user.user
210 members.append(dict(id=user.user_id,
210 members.append(dict(id=user.user_id,
211 username=user.username,
211 username=user.username,
212 firstname=user.name,
212 firstname=user.name,
213 lastname=user.lastname,
213 lastname=user.lastname,
214 email=user.email,
214 email=user.email,
215 active=user.active,
215 active=user.active,
216 admin=user.admin,
216 admin=user.admin,
217 ldap=user.ldap_dn))
217 ldap=user.ldap_dn))
218
218
219 result.append(dict(id=users_group.users_group_id,
219 result.append(dict(id=users_group.users_group_id,
220 group_name=users_group.users_group_name,
220 group_name=users_group.users_group_name,
221 active=users_group.users_group_active,
221 active=users_group.users_group_active,
222 members=members))
222 members=members))
223 return result
223 return result
224
224
225 @HasPermissionAllDecorator('hg.admin')
225 @HasPermissionAllDecorator('hg.admin')
226 def create_users_group(self, apiuser, group_name, active=True):
226 def create_users_group(self, apiuser, group_name, active=True):
227 """
227 """
228 Creates an new usergroup
228 Creates an new usergroup
229
229
230 :param group_name:
230 :param group_name:
231 :param active:
231 :param active:
232 """
232 """
233
233
234 if self.get_users_group(apiuser, group_name):
234 if self.get_users_group(apiuser, group_name):
235 raise JSONRPCError("users group %s already exist" % group_name)
235 raise JSONRPCError("users group %s already exist" % group_name)
236
236
237 try:
237 try:
238 ug = UsersGroupModel().create(name=group_name, active=active)
238 ug = UsersGroupModel().create(name=group_name, active=active)
239 Session.commit()
239 Session.commit()
240 return dict(id=ug.users_group_id,
240 return dict(id=ug.users_group_id,
241 msg='created new users group %s' % group_name)
241 msg='created new users group %s' % group_name)
242 except Exception:
242 except Exception:
243 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
244 raise JSONRPCError('failed to create group %s' % group_name)
244 raise JSONRPCError('failed to create group %s' % group_name)
245
245
246 @HasPermissionAllDecorator('hg.admin')
246 @HasPermissionAllDecorator('hg.admin')
247 def add_user_to_users_group(self, apiuser, group_name, username):
247 def add_user_to_users_group(self, apiuser, group_name, username):
248 """"
248 """"
249 Add a user to a group
249 Add a user to a group
250
250
251 :param apiuser:
251 :param apiuser:
252 :param group_name:
252 :param group_name:
253 :param username:
253 :param username:
254 """
254 """
255
255
256 try:
256 try:
257 users_group = UsersGroup.get_by_group_name(group_name)
257 users_group = UsersGroup.get_by_group_name(group_name)
258 if not users_group:
258 if not users_group:
259 raise JSONRPCError('unknown users group %s' % group_name)
259 raise JSONRPCError('unknown users group %s' % group_name)
260
260
261 try:
261 try:
262 user = User.get_by_username(username)
262 user = User.get_by_username(username)
263 except NoResultFound:
263 except NoResultFound:
264 raise JSONRPCError('unknown user %s' % username)
264 raise JSONRPCError('unknown user %s' % username)
265
265
266 ugm = UsersGroupModel().add_user_to_group(users_group, user)
266 ugm = UsersGroupModel().add_user_to_group(users_group, user)
267 Session.commit()
267 Session.commit()
268 return dict(id=ugm.users_group_member_id,
268 return dict(id=ugm.users_group_member_id,
269 msg='created new users group member')
269 msg='created new users group member')
270 except Exception:
270 except Exception:
271 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
272 raise JSONRPCError('failed to create users group member')
272 raise JSONRPCError('failed to create users group member')
273
273
274 @HasPermissionAnyDecorator('hg.admin')
274 @HasPermissionAnyDecorator('hg.admin')
275 def get_repo(self, apiuser, repo_name):
275 def get_repo(self, apiuser, repo_name):
276 """"
276 """"
277 Get repository by name
277 Get repository by name
278
278
279 :param apiuser:
279 :param apiuser:
280 :param repo_name:
280 :param repo_name:
281 """
281 """
282
282
283 repo = Repository.get_by_repo_name(repo_name)
283 repo = Repository.get_by_repo_name(repo_name)
284 if repo is None:
284 if repo is None:
285 raise JSONRPCError('unknown repository %s' % repo)
285 raise JSONRPCError('unknown repository %s' % repo)
286
286
287 members = []
287 members = []
288 for user in repo.repo_to_perm:
288 for user in repo.repo_to_perm:
289 perm = user.permission.permission_name
289 perm = user.permission.permission_name
290 user = user.user
290 user = user.user
291 members.append(
291 members.append(
292 dict(
292 dict(
293 type_="user",
293 type_="user",
294 id=user.user_id,
294 id=user.user_id,
295 username=user.username,
295 username=user.username,
296 firstname=user.name,
296 firstname=user.name,
297 lastname=user.lastname,
297 lastname=user.lastname,
298 email=user.email,
298 email=user.email,
299 active=user.active,
299 active=user.active,
300 admin=user.admin,
300 admin=user.admin,
301 ldap=user.ldap_dn,
301 ldap=user.ldap_dn,
302 permission=perm
302 permission=perm
303 )
303 )
304 )
304 )
305 for users_group in repo.users_group_to_perm:
305 for users_group in repo.users_group_to_perm:
306 perm = users_group.permission.permission_name
306 perm = users_group.permission.permission_name
307 users_group = users_group.users_group
307 users_group = users_group.users_group
308 members.append(
308 members.append(
309 dict(
309 dict(
310 type_="users_group",
310 type_="users_group",
311 id=users_group.users_group_id,
311 id=users_group.users_group_id,
312 name=users_group.users_group_name,
312 name=users_group.users_group_name,
313 active=users_group.users_group_active,
313 active=users_group.users_group_active,
314 permission=perm
314 permission=perm
315 )
315 )
316 )
316 )
317
317
318 return dict(
318 return dict(
319 id=repo.repo_id,
319 id=repo.repo_id,
320 repo_name=repo.repo_name,
320 repo_name=repo.repo_name,
321 type=repo.repo_type,
321 type=repo.repo_type,
322 description=repo.description,
322 description=repo.description,
323 members=members
323 members=members
324 )
324 )
325
325
326 @HasPermissionAnyDecorator('hg.admin')
326 @HasPermissionAnyDecorator('hg.admin')
327 def get_repos(self, apiuser):
327 def get_repos(self, apiuser):
328 """"
328 """"
329 Get all repositories
329 Get all repositories
330
330
331 :param apiuser:
331 :param apiuser:
332 """
332 """
333
333
334 result = []
334 result = []
335 for repository in Repository.getAll():
335 for repository in Repository.getAll():
336 result.append(
336 result.append(
337 dict(
337 dict(
338 id=repository.repo_id,
338 id=repository.repo_id,
339 repo_name=repository.repo_name,
339 repo_name=repository.repo_name,
340 type=repository.repo_type,
340 type=repository.repo_type,
341 description=repository.description
341 description=repository.description
342 )
342 )
343 )
343 )
344 return result
344 return result
345
345
346 @HasPermissionAnyDecorator('hg.admin')
346 @HasPermissionAnyDecorator('hg.admin')
347 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
347 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
348 ret_type='all'):
348 ret_type='all'):
349 """
349 """
350 returns a list of nodes and it's children
350 returns a list of nodes and it's children
351 for a given path at given revision. It's possible to specify ret_type
351 for a given path at given revision. It's possible to specify ret_type
352 to show only files or dirs
352 to show only files or dirs
353
353
354 :param apiuser:
354 :param apiuser:
355 :param repo_name: name of repository
355 :param repo_name: name of repository
356 :param revision: revision for which listing should be done
356 :param revision: revision for which listing should be done
357 :param root_path: path from which start displaying
357 :param root_path: path from which start displaying
358 :param ret_type: return type 'all|files|dirs' nodes
358 :param ret_type: return type 'all|files|dirs' nodes
359 """
359 """
360 try:
360 try:
361 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
361 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
362 flat=False)
362 flat=False)
363 _map = {
363 _map = {
364 'all': _d + _f,
364 'all': _d + _f,
365 'files': _f,
365 'files': _f,
366 'dirs': _d,
366 'dirs': _d,
367 }
367 }
368 return _map[ret_type]
368 return _map[ret_type]
369 except KeyError:
369 except KeyError:
370 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
370 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
371 except Exception, e:
371 except Exception, e:
372 raise JSONRPCError(e)
372 raise JSONRPCError(e)
373
373
374 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
374 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
375 def create_repo(self, apiuser, repo_name, owner_name, description='',
375 def create_repo(self, apiuser, repo_name, owner_name, description='',
376 repo_type='hg', private=False):
376 repo_type='hg', private=False):
377 """
377 """
378 Create a repository
378 Create a repository
379
379
380 :param apiuser:
380 :param apiuser:
381 :param repo_name:
381 :param repo_name:
382 :param description:
382 :param description:
383 :param type:
383 :param type:
384 :param private:
384 :param private:
385 :param owner_name:
385 :param owner_name:
386 """
386 """
387
387
388 try:
388 try:
389 try:
389 try:
390 owner = User.get_by_username(owner_name)
390 owner = User.get_by_username(owner_name)
391 except NoResultFound:
391 except NoResultFound:
392 raise JSONRPCError('unknown user %s' % owner)
392 raise JSONRPCError('unknown user %s' % owner)
393
393
394 if Repository.get_by_repo_name(repo_name):
394 if Repository.get_by_repo_name(repo_name):
395 raise JSONRPCError("repo %s already exist" % repo_name)
395 raise JSONRPCError("repo %s already exist" % repo_name)
396
396
397 groups = repo_name.split('/')
397 groups = repo_name.split('/')
398 real_name = groups[-1]
398 real_name = groups[-1]
399 groups = groups[:-1]
399 groups = groups[:-1]
400 parent_id = None
400 parent_id = None
401 for g in groups:
401 for g in groups:
402 group = RepoGroup.get_by_group_name(g)
402 group = RepoGroup.get_by_group_name(g)
403 if not group:
403 if not group:
404 group = ReposGroupModel().create(
404 group = ReposGroupModel().create(
405 dict(
405 dict(
406 group_name=g,
406 group_name=g,
407 group_description='',
407 group_description='',
408 group_parent_id=parent_id
408 group_parent_id=parent_id
409 )
409 )
410 )
410 )
411 parent_id = group.group_id
411 parent_id = group.group_id
412
412
413 repo = RepoModel().create(
413 repo = RepoModel().create(
414 dict(
414 dict(
415 repo_name=real_name,
415 repo_name=real_name,
416 repo_name_full=repo_name,
416 repo_name_full=repo_name,
417 description=description,
417 description=description,
418 private=private,
418 private=private,
419 repo_type=repo_type,
419 repo_type=repo_type,
420 repo_group=parent_id,
420 repo_group=parent_id,
421 clone_uri=None
421 clone_uri=None
422 ),
422 ),
423 owner
423 owner
424 )
424 )
425 Session.commit()
425 Session.commit()
426
426
427 return dict(
427 return dict(
428 id=repo.repo_id,
428 id=repo.repo_id,
429 msg="Created new repository %s" % repo.repo_name
429 msg="Created new repository %s" % repo.repo_name
430 )
430 )
431
431
432 except Exception:
432 except Exception:
433 log.error(traceback.format_exc())
433 log.error(traceback.format_exc())
434 raise JSONRPCError('failed to create repository %s' % repo_name)
434 raise JSONRPCError('failed to create repository %s' % repo_name)
435
435
436 @HasPermissionAnyDecorator('hg.admin')
436 @HasPermissionAnyDecorator('hg.admin')
437 def add_user_to_repo(self, apiuser, repo_name, username, perm):
437 def add_user_to_repo(self, apiuser, repo_name, username, perm):
438 """
438 """
439 Add permission for a user to a repository
439 Add permission for a user to a repository
440
440
441 :param apiuser:
441 :param apiuser:
442 :param repo_name:
442 :param repo_name:
443 :param username:
443 :param username:
444 :param perm:
444 :param perm:
445 """
445 """
446
446
447 try:
447 try:
448 repo = Repository.get_by_repo_name(repo_name)
448 repo = Repository.get_by_repo_name(repo_name)
449 if repo is None:
449 if repo is None:
450 raise JSONRPCError('unknown repository %s' % repo)
450 raise JSONRPCError('unknown repository %s' % repo)
451
451
452 try:
452 try:
453 user = User.get_by_username(username)
453 user = User.get_by_username(username)
454 except NoResultFound:
454 except NoResultFound:
455 raise JSONRPCError('unknown user %s' % user)
455 raise JSONRPCError('unknown user %s' % user)
456
456
457 RepositoryPermissionModel()\
457 RepositoryPermissionModel()\
458 .update_or_delete_user_permission(repo, user, perm)
458 .update_or_delete_user_permission(repo, user, perm)
459 Session.commit()
459 Session.commit()
460
460
461 return dict(
461 return dict(
462 msg='Added perm: %s for %s in repo: %s' % (
462 msg='Added perm: %s for %s in repo: %s' % (
463 perm, username, repo_name
463 perm, username, repo_name
464 )
464 )
465 )
465 )
466 except Exception:
466 except Exception:
467 log.error(traceback.format_exc())
467 log.error(traceback.format_exc())
468 raise JSONRPCError(
468 raise JSONRPCError(
469 'failed to edit permission %(repo)s for %(user)s' % dict(
469 'failed to edit permission %(repo)s for %(user)s' % dict(
470 user=username, repo=repo_name
470 user=username, repo=repo_name
471 )
471 )
472 )
472 )
473
473
474 @HasPermissionAnyDecorator('hg.admin')
474 @HasPermissionAnyDecorator('hg.admin')
475 def add_users_group_to_repo(self, apiuser, repo_name, group_name, perm):
475 def add_users_group_to_repo(self, apiuser, repo_name, group_name, perm):
476 """
476 """
477 Add permission for a users group to a repository
477 Add permission for a users group to a repository
478
478
479 :param apiuser:
479 :param apiuser:
480 :param repo_name:
480 :param repo_name:
481 :param group_name:
481 :param group_name:
482 :param perm:
482 :param perm:
483 """
483 """
484
484
485 try:
485 try:
486 repo = Repository.get_by_repo_name(repo_name)
486 repo = Repository.get_by_repo_name(repo_name)
487 if repo is None:
487 if repo is None:
488 raise JSONRPCError('unknown repository %s' % repo)
488 raise JSONRPCError('unknown repository %s' % repo)
489
489
490 try:
490 try:
491 user_group = UsersGroup.get_by_group_name(group_name)
491 user_group = UsersGroup.get_by_group_name(group_name)
492 except NoResultFound:
492 except NoResultFound:
493 raise JSONRPCError('unknown users group %s' % user_group)
493 raise JSONRPCError('unknown users group %s' % user_group)
494
494
495 RepositoryPermissionModel()\
495 RepositoryPermissionModel()\
496 .update_or_delete_users_group_permission(repo, user_group,
496 .update_or_delete_users_group_permission(repo, user_group,
497 perm)
497 perm)
498 Session.commit()
498 Session.commit()
499 return dict(
499 return dict(
500 msg='Added perm: %s for %s in repo: %s' % (
500 msg='Added perm: %s for %s in repo: %s' % (
501 perm, group_name, repo_name
501 perm, group_name, repo_name
502 )
502 )
503 )
503 )
504 except Exception:
504 except Exception:
505 log.error(traceback.format_exc())
505 log.error(traceback.format_exc())
506 raise JSONRPCError(
506 raise JSONRPCError(
507 'failed to edit permission %(repo)s for %(usergr)s' % dict(
507 'failed to edit permission %(repo)s for %(usergr)s' % dict(
508 usergr=group_name, repo=repo_name
508 usergr=group_name, repo=repo_name
509 )
509 )
510 )
510 )
@@ -1,699 +1,701 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, session, url, request
34 from pylons import config, session, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40
40
41 if __platform__ in PLATFORM_WIN:
41 if __platform__ in PLATFORM_WIN:
42 from hashlib import sha256
42 from hashlib import sha256
43 if __platform__ in PLATFORM_OTHERS:
43 if __platform__ in PLATFORM_OTHERS:
44 import bcrypt
44 import bcrypt
45
45
46 from rhodecode.lib import str2bool, safe_unicode
46 from rhodecode.lib import str2bool, safe_unicode
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 from rhodecode.lib.utils import get_repo_slug
48 from rhodecode.lib.utils import get_repo_slug
49 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
50
50
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class PasswordGenerator(object):
58 class PasswordGenerator(object):
59 """
59 """
60 This is a simple class for generating password from different sets of
60 This is a simple class for generating password from different sets of
61 characters
61 characters
62 usage::
62 usage::
63
63
64 passwd_gen = PasswordGenerator()
64 passwd_gen = PasswordGenerator()
65 #print 8-letter password containing only big and small letters
65 #print 8-letter password containing only big and small letters
66 of alphabet
66 of alphabet
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 """
68 """
69 ALPHABETS_NUM = r'''1234567890'''
69 ALPHABETS_NUM = r'''1234567890'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79
79
80 def __init__(self, passwd=''):
80 def __init__(self, passwd=''):
81 self.passwd = passwd
81 self.passwd = passwd
82
82
83 def gen_password(self, len, type):
83 def gen_password(self, len, type):
84 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
84 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
85 return self.passwd
85 return self.passwd
86
86
87
87
88 class RhodeCodeCrypto(object):
88 class RhodeCodeCrypto(object):
89
89
90 @classmethod
90 @classmethod
91 def hash_string(cls, str_):
91 def hash_string(cls, str_):
92 """
92 """
93 Cryptographic function used for password hashing based on pybcrypt
93 Cryptographic function used for password hashing based on pybcrypt
94 or pycrypto in windows
94 or pycrypto in windows
95
95
96 :param password: password to hash
96 :param password: password to hash
97 """
97 """
98 if __platform__ in PLATFORM_WIN:
98 if __platform__ in PLATFORM_WIN:
99 return sha256(str_).hexdigest()
99 return sha256(str_).hexdigest()
100 elif __platform__ in PLATFORM_OTHERS:
100 elif __platform__ in PLATFORM_OTHERS:
101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 else:
102 else:
103 raise Exception('Unknown or unsupported platform %s' \
103 raise Exception('Unknown or unsupported platform %s' \
104 % __platform__)
104 % __platform__)
105
105
106 @classmethod
106 @classmethod
107 def hash_check(cls, password, hashed):
107 def hash_check(cls, password, hashed):
108 """
108 """
109 Checks matching password with it's hashed value, runs different
109 Checks matching password with it's hashed value, runs different
110 implementation based on platform it runs on
110 implementation based on platform it runs on
111
111
112 :param password: password
112 :param password: password
113 :param hashed: password in hashed form
113 :param hashed: password in hashed form
114 """
114 """
115
115
116 if __platform__ in PLATFORM_WIN:
116 if __platform__ in PLATFORM_WIN:
117 return sha256(password).hexdigest() == hashed
117 return sha256(password).hexdigest() == hashed
118 elif __platform__ in PLATFORM_OTHERS:
118 elif __platform__ in PLATFORM_OTHERS:
119 return bcrypt.hashpw(password, hashed) == hashed
119 return bcrypt.hashpw(password, hashed) == hashed
120 else:
120 else:
121 raise Exception('Unknown or unsupported platform %s' \
121 raise Exception('Unknown or unsupported platform %s' \
122 % __platform__)
122 % __platform__)
123
123
124
124
125 def get_crypt_password(password):
125 def get_crypt_password(password):
126 return RhodeCodeCrypto.hash_string(password)
126 return RhodeCodeCrypto.hash_string(password)
127
127
128
128
129 def check_password(password, hashed):
129 def check_password(password, hashed):
130 return RhodeCodeCrypto.hash_check(password, hashed)
130 return RhodeCodeCrypto.hash_check(password, hashed)
131
131
132
132 def generate_api_key(str_, salt=None):
133 def generate_api_key(str_, salt=None):
133 """
134 """
134 Generates API KEY from given string
135 Generates API KEY from given string
135
136
136 :param str_:
137 :param str_:
137 :param salt:
138 :param salt:
138 """
139 """
139
140
140 if salt is None:
141 if salt is None:
141 salt = _RandomNameSequence().next()
142 salt = _RandomNameSequence().next()
142
143
143 return hashlib.sha1(str_ + salt).hexdigest()
144 return hashlib.sha1(str_ + salt).hexdigest()
144
145
145
146
146 def authfunc(environ, username, password):
147 def authfunc(environ, username, password):
147 """
148 """
148 Dummy authentication wrapper function used in Mercurial and Git for
149 Dummy authentication wrapper function used in Mercurial and Git for
149 access control.
150 access control.
150
151
151 :param environ: needed only for using in Basic auth
152 :param environ: needed only for using in Basic auth
152 """
153 """
153 return authenticate(username, password)
154 return authenticate(username, password)
154
155
155
156
156 def authenticate(username, password):
157 def authenticate(username, password):
157 """
158 """
158 Authentication function used for access control,
159 Authentication function used for access control,
159 firstly checks for db authentication then if ldap is enabled for ldap
160 firstly checks for db authentication then if ldap is enabled for ldap
160 authentication, also creates ldap user if not in database
161 authentication, also creates ldap user if not in database
161
162
162 :param username: username
163 :param username: username
163 :param password: password
164 :param password: password
164 """
165 """
165
166
166 user_model = UserModel()
167 user_model = UserModel()
167 user = User.get_by_username(username)
168 user = User.get_by_username(username)
168
169
169 log.debug('Authenticating user using RhodeCode account')
170 log.debug('Authenticating user using RhodeCode account')
170 if user is not None and not user.ldap_dn:
171 if user is not None and not user.ldap_dn:
171 if user.active:
172 if user.active:
172 if user.username == 'default' and user.active:
173 if user.username == 'default' and user.active:
173 log.info('user %s authenticated correctly as anonymous user',
174 log.info('user %s authenticated correctly as anonymous user',
174 username)
175 username)
175 return True
176 return True
176
177
177 elif user.username == username and check_password(password,
178 elif user.username == username and check_password(password,
178 user.password):
179 user.password):
179 log.info('user %s authenticated correctly', username)
180 log.info('user %s authenticated correctly', username)
180 return True
181 return True
181 else:
182 else:
182 log.warning('user %s is disabled', username)
183 log.warning('user %s is disabled', username)
183
184
184 else:
185 else:
185 log.debug('Regular authentication failed')
186 log.debug('Regular authentication failed')
186 user_obj = User.get_by_username(username, case_insensitive=True)
187 user_obj = User.get_by_username(username, case_insensitive=True)
187
188
188 if user_obj is not None and not user_obj.ldap_dn:
189 if user_obj is not None and not user_obj.ldap_dn:
189 log.debug('this user already exists as non ldap')
190 log.debug('this user already exists as non ldap')
190 return False
191 return False
191
192
192 ldap_settings = RhodeCodeSetting.get_ldap_settings()
193 ldap_settings = RhodeCodeSetting.get_ldap_settings()
193 #======================================================================
194 #======================================================================
194 # FALLBACK TO LDAP AUTH IF ENABLE
195 # FALLBACK TO LDAP AUTH IF ENABLE
195 #======================================================================
196 #======================================================================
196 if str2bool(ldap_settings.get('ldap_active')):
197 if str2bool(ldap_settings.get('ldap_active')):
197 log.debug("Authenticating user using ldap")
198 log.debug("Authenticating user using ldap")
198 kwargs = {
199 kwargs = {
199 'server': ldap_settings.get('ldap_host', ''),
200 'server': ldap_settings.get('ldap_host', ''),
200 'base_dn': ldap_settings.get('ldap_base_dn', ''),
201 'base_dn': ldap_settings.get('ldap_base_dn', ''),
201 'port': ldap_settings.get('ldap_port'),
202 'port': ldap_settings.get('ldap_port'),
202 'bind_dn': ldap_settings.get('ldap_dn_user'),
203 'bind_dn': ldap_settings.get('ldap_dn_user'),
203 'bind_pass': ldap_settings.get('ldap_dn_pass'),
204 'bind_pass': ldap_settings.get('ldap_dn_pass'),
204 'tls_kind': ldap_settings.get('ldap_tls_kind'),
205 'tls_kind': ldap_settings.get('ldap_tls_kind'),
205 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
206 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
206 'ldap_filter': ldap_settings.get('ldap_filter'),
207 'ldap_filter': ldap_settings.get('ldap_filter'),
207 'search_scope': ldap_settings.get('ldap_search_scope'),
208 'search_scope': ldap_settings.get('ldap_search_scope'),
208 'attr_login': ldap_settings.get('ldap_attr_login'),
209 'attr_login': ldap_settings.get('ldap_attr_login'),
209 'ldap_version': 3,
210 'ldap_version': 3,
210 }
211 }
211 log.debug('Checking for ldap authentication')
212 log.debug('Checking for ldap authentication')
212 try:
213 try:
213 aldap = AuthLdap(**kwargs)
214 aldap = AuthLdap(**kwargs)
214 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
215 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
215 password)
216 password)
216 log.debug('Got ldap DN response %s', user_dn)
217 log.debug('Got ldap DN response %s', user_dn)
217
218
218 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
219 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
219 .get(k), [''])[0]
220 .get(k), [''])[0]
220
221
221 user_attrs = {
222 user_attrs = {
222 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
223 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
223 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
224 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
224 'email': get_ldap_attr('ldap_attr_email'),
225 'email': get_ldap_attr('ldap_attr_email'),
225 }
226 }
226
227
227 if user_model.create_ldap(username, password, user_dn,
228 if user_model.create_ldap(username, password, user_dn,
228 user_attrs):
229 user_attrs):
229 log.info('created new ldap user %s', username)
230 log.info('created new ldap user %s', username)
230
231
231 Session.commit()
232 Session.commit()
232 return True
233 return True
233 except (LdapUsernameError, LdapPasswordError,):
234 except (LdapUsernameError, LdapPasswordError,):
234 pass
235 pass
235 except (Exception,):
236 except (Exception,):
236 log.error(traceback.format_exc())
237 log.error(traceback.format_exc())
237 pass
238 pass
238 return False
239 return False
239
240
241
240 def login_container_auth(username):
242 def login_container_auth(username):
241 user = User.get_by_username(username)
243 user = User.get_by_username(username)
242 if user is None:
244 if user is None:
243 user_attrs = {
245 user_attrs = {
244 'name': username,
246 'name': username,
245 'lastname': None,
247 'lastname': None,
246 'email': None,
248 'email': None,
247 }
249 }
248 user = UserModel().create_for_container_auth(username, user_attrs)
250 user = UserModel().create_for_container_auth(username, user_attrs)
249 if not user:
251 if not user:
250 return None
252 return None
251 log.info('User %s was created by container authentication', username)
253 log.info('User %s was created by container authentication', username)
252
254
253 if not user.active:
255 if not user.active:
254 return None
256 return None
255
257
256 user.update_lastlogin()
258 user.update_lastlogin()
257 Session.commit()
259 Session.commit()
258
260
259 log.debug('User %s is now logged in by container authentication',
261 log.debug('User %s is now logged in by container authentication',
260 user.username)
262 user.username)
261 return user
263 return user
262
264
265
263 def get_container_username(environ, config):
266 def get_container_username(environ, config):
264 username = None
267 username = None
265
268
266 if str2bool(config.get('container_auth_enabled', False)):
269 if str2bool(config.get('container_auth_enabled', False)):
267 from paste.httpheaders import REMOTE_USER
270 from paste.httpheaders import REMOTE_USER
268 username = REMOTE_USER(environ)
271 username = REMOTE_USER(environ)
269
272
270 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
273 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
271 username = environ.get('HTTP_X_FORWARDED_USER')
274 username = environ.get('HTTP_X_FORWARDED_USER')
272
275
273 if username:
276 if username:
274 # Removing realm and domain from username
277 # Removing realm and domain from username
275 username = username.partition('@')[0]
278 username = username.partition('@')[0]
276 username = username.rpartition('\\')[2]
279 username = username.rpartition('\\')[2]
277 log.debug('Received username %s from container', username)
280 log.debug('Received username %s from container', username)
278
281
279 return username
282 return username
280
283
284
281 class AuthUser(object):
285 class AuthUser(object):
282 """
286 """
283 A simple object that handles all attributes of user in RhodeCode
287 A simple object that handles all attributes of user in RhodeCode
284
288
285 It does lookup based on API key,given user, or user present in session
289 It does lookup based on API key,given user, or user present in session
286 Then it fills all required information for such user. It also checks if
290 Then it fills all required information for such user. It also checks if
287 anonymous access is enabled and if so, it returns default user as logged
291 anonymous access is enabled and if so, it returns default user as logged
288 in
292 in
289 """
293 """
290
294
291 def __init__(self, user_id=None, api_key=None, username=None):
295 def __init__(self, user_id=None, api_key=None, username=None):
292
296
293 self.user_id = user_id
297 self.user_id = user_id
294 self.api_key = None
298 self.api_key = None
295 self.username = username
299 self.username = username
296
300
297 self.name = ''
301 self.name = ''
298 self.lastname = ''
302 self.lastname = ''
299 self.email = ''
303 self.email = ''
300 self.is_authenticated = False
304 self.is_authenticated = False
301 self.admin = False
305 self.admin = False
302 self.permissions = {}
306 self.permissions = {}
303 self._api_key = api_key
307 self._api_key = api_key
304 self.propagate_data()
308 self.propagate_data()
309 self._instance = None
305
310
306 def propagate_data(self):
311 def propagate_data(self):
307 user_model = UserModel()
312 user_model = UserModel()
308 self.anonymous_user = User.get_by_username('default', cache=True)
313 self.anonymous_user = User.get_by_username('default', cache=True)
309 is_user_loaded = False
314 is_user_loaded = False
310
315
311 # try go get user by api key
316 # try go get user by api key
312 if self._api_key and self._api_key != self.anonymous_user.api_key:
317 if self._api_key and self._api_key != self.anonymous_user.api_key:
313 log.debug('Auth User lookup by API KEY %s', self._api_key)
318 log.debug('Auth User lookup by API KEY %s', self._api_key)
314 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
319 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
315 # lookup by userid
320 # lookup by userid
316 elif (self.user_id is not None and
321 elif (self.user_id is not None and
317 self.user_id != self.anonymous_user.user_id):
322 self.user_id != self.anonymous_user.user_id):
318 log.debug('Auth User lookup by USER ID %s', self.user_id)
323 log.debug('Auth User lookup by USER ID %s', self.user_id)
319 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
324 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
320 # lookup by username
325 # lookup by username
321 elif self.username and \
326 elif self.username and \
322 str2bool(config.get('container_auth_enabled', False)):
327 str2bool(config.get('container_auth_enabled', False)):
323
328
324 log.debug('Auth User lookup by USER NAME %s', self.username)
329 log.debug('Auth User lookup by USER NAME %s', self.username)
325 dbuser = login_container_auth(self.username)
330 dbuser = login_container_auth(self.username)
326 if dbuser is not None:
331 if dbuser is not None:
327 for k, v in dbuser.get_dict().items():
332 for k, v in dbuser.get_dict().items():
328 setattr(self, k, v)
333 setattr(self, k, v)
329 self.set_authenticated()
334 self.set_authenticated()
330 is_user_loaded = True
335 is_user_loaded = True
331
336
332 if not is_user_loaded:
337 if not is_user_loaded:
333 # if we cannot authenticate user try anonymous
338 # if we cannot authenticate user try anonymous
334 if self.anonymous_user.active is True:
339 if self.anonymous_user.active is True:
335 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
340 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
336 # then we set this user is logged in
341 # then we set this user is logged in
337 self.is_authenticated = True
342 self.is_authenticated = True
338 else:
343 else:
339 self.user_id = None
344 self.user_id = None
340 self.username = None
345 self.username = None
341 self.is_authenticated = False
346 self.is_authenticated = False
342
347
343 if not self.username:
348 if not self.username:
344 self.username = 'None'
349 self.username = 'None'
345
350
346 log.debug('Auth User is now %s', self)
351 log.debug('Auth User is now %s', self)
347 user_model.fill_perms(self)
352 user_model.fill_perms(self)
348
353
349 @property
354 @property
350 def is_admin(self):
355 def is_admin(self):
351 return self.admin
356 return self.admin
352
357
353 @property
354 def full_contact(self):
355 return '%s %s <%s>' % (self.name, self.lastname, self.email)
356
357 def __repr__(self):
358 def __repr__(self):
358 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
359 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
359 self.is_authenticated)
360 self.is_authenticated)
360
361
361 def set_authenticated(self, authenticated=True):
362 def set_authenticated(self, authenticated=True):
362 if self.user_id != self.anonymous_user.user_id:
363 if self.user_id != self.anonymous_user.user_id:
363 self.is_authenticated = authenticated
364 self.is_authenticated = authenticated
364
365
365 def get_cookie_store(self):
366 def get_cookie_store(self):
366 return {'username':self.username,
367 return {'username': self.username,
367 'user_id': self.user_id,
368 'user_id': self.user_id,
368 'is_authenticated':self.is_authenticated}
369 'is_authenticated': self.is_authenticated}
369
370
370 @classmethod
371 @classmethod
371 def from_cookie_store(cls, cookie_store):
372 def from_cookie_store(cls, cookie_store):
372 user_id = cookie_store.get('user_id')
373 user_id = cookie_store.get('user_id')
373 username = cookie_store.get('username')
374 username = cookie_store.get('username')
374 api_key = cookie_store.get('api_key')
375 api_key = cookie_store.get('api_key')
375 return AuthUser(user_id, api_key, username)
376 return AuthUser(user_id, api_key, username)
376
377
378
377 def set_available_permissions(config):
379 def set_available_permissions(config):
378 """
380 """
379 This function will propagate pylons globals with all available defined
381 This function will propagate pylons globals with all available defined
380 permission given in db. We don't want to check each time from db for new
382 permission given in db. We don't want to check each time from db for new
381 permissions since adding a new permission also requires application restart
383 permissions since adding a new permission also requires application restart
382 ie. to decorate new views with the newly created permission
384 ie. to decorate new views with the newly created permission
383
385
384 :param config: current pylons config instance
386 :param config: current pylons config instance
385
387
386 """
388 """
387 log.info('getting information about all available permissions')
389 log.info('getting information about all available permissions')
388 try:
390 try:
389 sa = meta.Session
391 sa = meta.Session
390 all_perms = sa.query(Permission).all()
392 all_perms = sa.query(Permission).all()
391 except:
393 except Exception:
392 pass
394 pass
393 finally:
395 finally:
394 meta.Session.remove()
396 meta.Session.remove()
395
397
396 config['available_permissions'] = [x.permission_name for x in all_perms]
398 config['available_permissions'] = [x.permission_name for x in all_perms]
397
399
398
400
399 #==============================================================================
401 #==============================================================================
400 # CHECK DECORATORS
402 # CHECK DECORATORS
401 #==============================================================================
403 #==============================================================================
402 class LoginRequired(object):
404 class LoginRequired(object):
403 """
405 """
404 Must be logged in to execute this function else
406 Must be logged in to execute this function else
405 redirect to login page
407 redirect to login page
406
408
407 :param api_access: if enabled this checks only for valid auth token
409 :param api_access: if enabled this checks only for valid auth token
408 and grants access based on valid token
410 and grants access based on valid token
409 """
411 """
410
412
411 def __init__(self, api_access=False):
413 def __init__(self, api_access=False):
412 self.api_access = api_access
414 self.api_access = api_access
413
415
414 def __call__(self, func):
416 def __call__(self, func):
415 return decorator(self.__wrapper, func)
417 return decorator(self.__wrapper, func)
416
418
417 def __wrapper(self, func, *fargs, **fkwargs):
419 def __wrapper(self, func, *fargs, **fkwargs):
418 cls = fargs[0]
420 cls = fargs[0]
419 user = cls.rhodecode_user
421 user = cls.rhodecode_user
420
422
421 api_access_ok = False
423 api_access_ok = False
422 if self.api_access:
424 if self.api_access:
423 log.debug('Checking API KEY access for %s', cls)
425 log.debug('Checking API KEY access for %s', cls)
424 if user.api_key == request.GET.get('api_key'):
426 if user.api_key == request.GET.get('api_key'):
425 api_access_ok = True
427 api_access_ok = True
426 else:
428 else:
427 log.debug("API KEY token not valid")
429 log.debug("API KEY token not valid")
428
430
429 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
431 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
430 if user.is_authenticated or api_access_ok:
432 if user.is_authenticated or api_access_ok:
431 log.debug('user %s is authenticated', user.username)
433 log.debug('user %s is authenticated', user.username)
432 return func(*fargs, **fkwargs)
434 return func(*fargs, **fkwargs)
433 else:
435 else:
434 log.warn('user %s NOT authenticated', user.username)
436 log.warn('user %s NOT authenticated', user.username)
435 p = url.current()
437 p = url.current()
436
438
437 log.debug('redirecting to login page with %s', p)
439 log.debug('redirecting to login page with %s', p)
438 return redirect(url('login_home', came_from=p))
440 return redirect(url('login_home', came_from=p))
439
441
440
442
441 class NotAnonymous(object):
443 class NotAnonymous(object):
442 """
444 """
443 Must be logged in to execute this function else
445 Must be logged in to execute this function else
444 redirect to login page"""
446 redirect to login page"""
445
447
446 def __call__(self, func):
448 def __call__(self, func):
447 return decorator(self.__wrapper, func)
449 return decorator(self.__wrapper, func)
448
450
449 def __wrapper(self, func, *fargs, **fkwargs):
451 def __wrapper(self, func, *fargs, **fkwargs):
450 cls = fargs[0]
452 cls = fargs[0]
451 self.user = cls.rhodecode_user
453 self.user = cls.rhodecode_user
452
454
453 log.debug('Checking if user is not anonymous @%s', cls)
455 log.debug('Checking if user is not anonymous @%s', cls)
454
456
455 anonymous = self.user.username == 'default'
457 anonymous = self.user.username == 'default'
456
458
457 if anonymous:
459 if anonymous:
458 p = url.current()
460 p = url.current()
459
461
460 import rhodecode.lib.helpers as h
462 import rhodecode.lib.helpers as h
461 h.flash(_('You need to be a registered user to '
463 h.flash(_('You need to be a registered user to '
462 'perform this action'),
464 'perform this action'),
463 category='warning')
465 category='warning')
464 return redirect(url('login_home', came_from=p))
466 return redirect(url('login_home', came_from=p))
465 else:
467 else:
466 return func(*fargs, **fkwargs)
468 return func(*fargs, **fkwargs)
467
469
468
470
469 class PermsDecorator(object):
471 class PermsDecorator(object):
470 """Base class for controller decorators"""
472 """Base class for controller decorators"""
471
473
472 def __init__(self, *required_perms):
474 def __init__(self, *required_perms):
473 available_perms = config['available_permissions']
475 available_perms = config['available_permissions']
474 for perm in required_perms:
476 for perm in required_perms:
475 if perm not in available_perms:
477 if perm not in available_perms:
476 raise Exception("'%s' permission is not defined" % perm)
478 raise Exception("'%s' permission is not defined" % perm)
477 self.required_perms = set(required_perms)
479 self.required_perms = set(required_perms)
478 self.user_perms = None
480 self.user_perms = None
479
481
480 def __call__(self, func):
482 def __call__(self, func):
481 return decorator(self.__wrapper, func)
483 return decorator(self.__wrapper, func)
482
484
483 def __wrapper(self, func, *fargs, **fkwargs):
485 def __wrapper(self, func, *fargs, **fkwargs):
484 cls = fargs[0]
486 cls = fargs[0]
485 self.user = cls.rhodecode_user
487 self.user = cls.rhodecode_user
486 self.user_perms = self.user.permissions
488 self.user_perms = self.user.permissions
487 log.debug('checking %s permissions %s for %s %s',
489 log.debug('checking %s permissions %s for %s %s',
488 self.__class__.__name__, self.required_perms, cls,
490 self.__class__.__name__, self.required_perms, cls,
489 self.user)
491 self.user)
490
492
491 if self.check_permissions():
493 if self.check_permissions():
492 log.debug('Permission granted for %s %s', cls, self.user)
494 log.debug('Permission granted for %s %s', cls, self.user)
493 return func(*fargs, **fkwargs)
495 return func(*fargs, **fkwargs)
494
496
495 else:
497 else:
496 log.warning('Permission denied for %s %s', cls, self.user)
498 log.warning('Permission denied for %s %s', cls, self.user)
497 anonymous = self.user.username == 'default'
499 anonymous = self.user.username == 'default'
498
500
499 if anonymous:
501 if anonymous:
500 p = url.current()
502 p = url.current()
501
503
502 import rhodecode.lib.helpers as h
504 import rhodecode.lib.helpers as h
503 h.flash(_('You need to be a signed in to '
505 h.flash(_('You need to be a signed in to '
504 'view this page'),
506 'view this page'),
505 category='warning')
507 category='warning')
506 return redirect(url('login_home', came_from=p))
508 return redirect(url('login_home', came_from=p))
507
509
508 else:
510 else:
509 # redirect with forbidden ret code
511 # redirect with forbidden ret code
510 return abort(403)
512 return abort(403)
511
513
512 def check_permissions(self):
514 def check_permissions(self):
513 """Dummy function for overriding"""
515 """Dummy function for overriding"""
514 raise Exception('You have to write this function in child class')
516 raise Exception('You have to write this function in child class')
515
517
516
518
517 class HasPermissionAllDecorator(PermsDecorator):
519 class HasPermissionAllDecorator(PermsDecorator):
518 """
520 """
519 Checks for access permission for all given predicates. All of them
521 Checks for access permission for all given predicates. All of them
520 have to be meet in order to fulfill the request
522 have to be meet in order to fulfill the request
521 """
523 """
522
524
523 def check_permissions(self):
525 def check_permissions(self):
524 if self.required_perms.issubset(self.user_perms.get('global')):
526 if self.required_perms.issubset(self.user_perms.get('global')):
525 return True
527 return True
526 return False
528 return False
527
529
528
530
529 class HasPermissionAnyDecorator(PermsDecorator):
531 class HasPermissionAnyDecorator(PermsDecorator):
530 """
532 """
531 Checks for access permission for any of given predicates. In order to
533 Checks for access permission for any of given predicates. In order to
532 fulfill the request any of predicates must be meet
534 fulfill the request any of predicates must be meet
533 """
535 """
534
536
535 def check_permissions(self):
537 def check_permissions(self):
536 if self.required_perms.intersection(self.user_perms.get('global')):
538 if self.required_perms.intersection(self.user_perms.get('global')):
537 return True
539 return True
538 return False
540 return False
539
541
540
542
541 class HasRepoPermissionAllDecorator(PermsDecorator):
543 class HasRepoPermissionAllDecorator(PermsDecorator):
542 """
544 """
543 Checks for access permission for all given predicates for specific
545 Checks for access permission for all given predicates for specific
544 repository. All of them have to be meet in order to fulfill the request
546 repository. All of them have to be meet in order to fulfill the request
545 """
547 """
546
548
547 def check_permissions(self):
549 def check_permissions(self):
548 repo_name = get_repo_slug(request)
550 repo_name = get_repo_slug(request)
549 try:
551 try:
550 user_perms = set([self.user_perms['repositories'][repo_name]])
552 user_perms = set([self.user_perms['repositories'][repo_name]])
551 except KeyError:
553 except KeyError:
552 return False
554 return False
553 if self.required_perms.issubset(user_perms):
555 if self.required_perms.issubset(user_perms):
554 return True
556 return True
555 return False
557 return False
556
558
557
559
558 class HasRepoPermissionAnyDecorator(PermsDecorator):
560 class HasRepoPermissionAnyDecorator(PermsDecorator):
559 """
561 """
560 Checks for access permission for any of given predicates for specific
562 Checks for access permission for any of given predicates for specific
561 repository. In order to fulfill the request any of predicates must be meet
563 repository. In order to fulfill the request any of predicates must be meet
562 """
564 """
563
565
564 def check_permissions(self):
566 def check_permissions(self):
565 repo_name = get_repo_slug(request)
567 repo_name = get_repo_slug(request)
566
568
567 try:
569 try:
568 user_perms = set([self.user_perms['repositories'][repo_name]])
570 user_perms = set([self.user_perms['repositories'][repo_name]])
569 except KeyError:
571 except KeyError:
570 return False
572 return False
571 if self.required_perms.intersection(user_perms):
573 if self.required_perms.intersection(user_perms):
572 return True
574 return True
573 return False
575 return False
574
576
575
577
576 #==============================================================================
578 #==============================================================================
577 # CHECK FUNCTIONS
579 # CHECK FUNCTIONS
578 #==============================================================================
580 #==============================================================================
579 class PermsFunction(object):
581 class PermsFunction(object):
580 """Base function for other check functions"""
582 """Base function for other check functions"""
581
583
582 def __init__(self, *perms):
584 def __init__(self, *perms):
583 available_perms = config['available_permissions']
585 available_perms = config['available_permissions']
584
586
585 for perm in perms:
587 for perm in perms:
586 if perm not in available_perms:
588 if perm not in available_perms:
587 raise Exception("'%s' permission in not defined" % perm)
589 raise Exception("'%s' permission in not defined" % perm)
588 self.required_perms = set(perms)
590 self.required_perms = set(perms)
589 self.user_perms = None
591 self.user_perms = None
590 self.granted_for = ''
592 self.granted_for = ''
591 self.repo_name = None
593 self.repo_name = None
592
594
593 def __call__(self, check_Location=''):
595 def __call__(self, check_Location=''):
594 user = request.user
596 user = request.user
595 if not user:
597 if not user:
596 return False
598 return False
597 self.user_perms = user.permissions
599 self.user_perms = user.permissions
598 self.granted_for = user
600 self.granted_for = user
599 log.debug('checking %s %s %s', self.__class__.__name__,
601 log.debug('checking %s %s %s', self.__class__.__name__,
600 self.required_perms, user)
602 self.required_perms, user)
601
603
602 if self.check_permissions():
604 if self.check_permissions():
603 log.debug('Permission granted %s @ %s', self.granted_for,
605 log.debug('Permission granted %s @ %s', self.granted_for,
604 check_Location or 'unspecified location')
606 check_Location or 'unspecified location')
605 return True
607 return True
606
608
607 else:
609 else:
608 log.warning('Permission denied for %s @ %s', self.granted_for,
610 log.warning('Permission denied for %s @ %s', self.granted_for,
609 check_Location or 'unspecified location')
611 check_Location or 'unspecified location')
610 return False
612 return False
611
613
612 def check_permissions(self):
614 def check_permissions(self):
613 """Dummy function for overriding"""
615 """Dummy function for overriding"""
614 raise Exception('You have to write this function in child class')
616 raise Exception('You have to write this function in child class')
615
617
616
618
617 class HasPermissionAll(PermsFunction):
619 class HasPermissionAll(PermsFunction):
618 def check_permissions(self):
620 def check_permissions(self):
619 if self.required_perms.issubset(self.user_perms.get('global')):
621 if self.required_perms.issubset(self.user_perms.get('global')):
620 return True
622 return True
621 return False
623 return False
622
624
623
625
624 class HasPermissionAny(PermsFunction):
626 class HasPermissionAny(PermsFunction):
625 def check_permissions(self):
627 def check_permissions(self):
626 if self.required_perms.intersection(self.user_perms.get('global')):
628 if self.required_perms.intersection(self.user_perms.get('global')):
627 return True
629 return True
628 return False
630 return False
629
631
630
632
631 class HasRepoPermissionAll(PermsFunction):
633 class HasRepoPermissionAll(PermsFunction):
632
634
633 def __call__(self, repo_name=None, check_Location=''):
635 def __call__(self, repo_name=None, check_Location=''):
634 self.repo_name = repo_name
636 self.repo_name = repo_name
635 return super(HasRepoPermissionAll, self).__call__(check_Location)
637 return super(HasRepoPermissionAll, self).__call__(check_Location)
636
638
637 def check_permissions(self):
639 def check_permissions(self):
638 if not self.repo_name:
640 if not self.repo_name:
639 self.repo_name = get_repo_slug(request)
641 self.repo_name = get_repo_slug(request)
640
642
641 try:
643 try:
642 self.user_perms = set([self.user_perms['reposit'
644 self.user_perms = set([self.user_perms['reposit'
643 'ories'][self.repo_name]])
645 'ories'][self.repo_name]])
644 except KeyError:
646 except KeyError:
645 return False
647 return False
646 self.granted_for = self.repo_name
648 self.granted_for = self.repo_name
647 if self.required_perms.issubset(self.user_perms):
649 if self.required_perms.issubset(self.user_perms):
648 return True
650 return True
649 return False
651 return False
650
652
651
653
652 class HasRepoPermissionAny(PermsFunction):
654 class HasRepoPermissionAny(PermsFunction):
653
655
654 def __call__(self, repo_name=None, check_Location=''):
656 def __call__(self, repo_name=None, check_Location=''):
655 self.repo_name = repo_name
657 self.repo_name = repo_name
656 return super(HasRepoPermissionAny, self).__call__(check_Location)
658 return super(HasRepoPermissionAny, self).__call__(check_Location)
657
659
658 def check_permissions(self):
660 def check_permissions(self):
659 if not self.repo_name:
661 if not self.repo_name:
660 self.repo_name = get_repo_slug(request)
662 self.repo_name = get_repo_slug(request)
661
663
662 try:
664 try:
663 self.user_perms = set([self.user_perms['reposi'
665 self.user_perms = set([self.user_perms['reposi'
664 'tories'][self.repo_name]])
666 'tories'][self.repo_name]])
665 except KeyError:
667 except KeyError:
666 return False
668 return False
667 self.granted_for = self.repo_name
669 self.granted_for = self.repo_name
668 if self.required_perms.intersection(self.user_perms):
670 if self.required_perms.intersection(self.user_perms):
669 return True
671 return True
670 return False
672 return False
671
673
672
674
673 #==============================================================================
675 #==============================================================================
674 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
676 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
675 #==============================================================================
677 #==============================================================================
676 class HasPermissionAnyMiddleware(object):
678 class HasPermissionAnyMiddleware(object):
677 def __init__(self, *perms):
679 def __init__(self, *perms):
678 self.required_perms = set(perms)
680 self.required_perms = set(perms)
679
681
680 def __call__(self, user, repo_name):
682 def __call__(self, user, repo_name):
681 usr = AuthUser(user.user_id)
683 usr = AuthUser(user.user_id)
682 try:
684 try:
683 self.user_perms = set([usr.permissions['repositories'][repo_name]])
685 self.user_perms = set([usr.permissions['repositories'][repo_name]])
684 except:
686 except:
685 self.user_perms = set()
687 self.user_perms = set()
686 self.granted_for = ''
688 self.granted_for = ''
687 self.username = user.username
689 self.username = user.username
688 self.repo_name = repo_name
690 self.repo_name = repo_name
689 return self.check_permissions()
691 return self.check_permissions()
690
692
691 def check_permissions(self):
693 def check_permissions(self):
692 log.debug('checking mercurial protocol '
694 log.debug('checking mercurial protocol '
693 'permissions %s for user:%s repository:%s', self.user_perms,
695 'permissions %s for user:%s repository:%s', self.user_perms,
694 self.username, self.repo_name)
696 self.username, self.repo_name)
695 if self.required_perms.intersection(self.user_perms):
697 if self.required_perms.intersection(self.user_perms):
696 log.debug('permission granted')
698 log.debug('permission granted')
697 return True
699 return True
698 log.debug('permission denied')
700 log.debug('permission denied')
699 return False
701 return False
@@ -1,1136 +1,1146 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from collections import defaultdict
30 from collections import defaultdict
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 from vcs import get_backend
37 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
38 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import VCSError
39 from vcs.exceptions import VCSError
40 from vcs.utils.lazy import LazyProperty
40 from vcs.utils.lazy import LazyProperty
41
41
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
43 from rhodecode.lib.exceptions import UsersGroupsAssignedException
43 from rhodecode.lib.exceptions import UsersGroupsAssignedException
44 from rhodecode.lib.compat import json
44 from rhodecode.lib.compat import json
45 from rhodecode.lib.caching_query import FromCache
45 from rhodecode.lib.caching_query import FromCache
46
46
47 from rhodecode.model.meta import Base, Session
47 from rhodecode.model.meta import Base, Session
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 #==============================================================================
51 #==============================================================================
52 # BASE CLASSES
52 # BASE CLASSES
53 #==============================================================================
53 #==============================================================================
54
54
55
55
56 class ModelSerializer(json.JSONEncoder):
56 class ModelSerializer(json.JSONEncoder):
57 """
57 """
58 Simple Serializer for JSON,
58 Simple Serializer for JSON,
59
59
60 usage::
60 usage::
61
61
62 to make object customized for serialization implement a __json__
62 to make object customized for serialization implement a __json__
63 method that will return a dict for serialization into json
63 method that will return a dict for serialization into json
64
64
65 example::
65 example::
66
66
67 class Task(object):
67 class Task(object):
68
68
69 def __init__(self, name, value):
69 def __init__(self, name, value):
70 self.name = name
70 self.name = name
71 self.value = value
71 self.value = value
72
72
73 def __json__(self):
73 def __json__(self):
74 return dict(name=self.name,
74 return dict(name=self.name,
75 value=self.value)
75 value=self.value)
76
76
77 """
77 """
78
78
79 def default(self, obj):
79 def default(self, obj):
80
80
81 if hasattr(obj, '__json__'):
81 if hasattr(obj, '__json__'):
82 return obj.__json__()
82 return obj.__json__()
83 else:
83 else:
84 return json.JSONEncoder.default(self, obj)
84 return json.JSONEncoder.default(self, obj)
85
85
86
86
87 class BaseModel(object):
87 class BaseModel(object):
88 """
88 """
89 Base Model for all classess
89 Base Model for all classess
90 """
90 """
91
91
92 @classmethod
92 @classmethod
93 def _get_keys(cls):
93 def _get_keys(cls):
94 """return column names for this model """
94 """return column names for this model """
95 return class_mapper(cls).c.keys()
95 return class_mapper(cls).c.keys()
96
96
97 def get_dict(self):
97 def get_dict(self):
98 """
98 """
99 return dict with keys and values corresponding
99 return dict with keys and values corresponding
100 to this model data """
100 to this model data """
101
101
102 d = {}
102 d = {}
103 for k in self._get_keys():
103 for k in self._get_keys():
104 d[k] = getattr(self, k)
104 d[k] = getattr(self, k)
105
105
106 # also use __json__() if present to get additional fields
106 # also use __json__() if present to get additional fields
107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
108 d[k] = val
108 d[k] = val
109 return d
109 return d
110
110
111 def get_appstruct(self):
111 def get_appstruct(self):
112 """return list with keys and values tupples corresponding
112 """return list with keys and values tupples corresponding
113 to this model data """
113 to this model data """
114
114
115 l = []
115 l = []
116 for k in self._get_keys():
116 for k in self._get_keys():
117 l.append((k, getattr(self, k),))
117 l.append((k, getattr(self, k),))
118 return l
118 return l
119
119
120 def populate_obj(self, populate_dict):
120 def populate_obj(self, populate_dict):
121 """populate model with data from given populate_dict"""
121 """populate model with data from given populate_dict"""
122
122
123 for k in self._get_keys():
123 for k in self._get_keys():
124 if k in populate_dict:
124 if k in populate_dict:
125 setattr(self, k, populate_dict[k])
125 setattr(self, k, populate_dict[k])
126
126
127 @classmethod
127 @classmethod
128 def query(cls):
128 def query(cls):
129 return Session.query(cls)
129 return Session.query(cls)
130
130
131 @classmethod
131 @classmethod
132 def get(cls, id_):
132 def get(cls, id_):
133 if id_:
133 if id_:
134 return cls.query().get(id_)
134 return cls.query().get(id_)
135
135
136 @classmethod
136 @classmethod
137 def getAll(cls):
137 def getAll(cls):
138 return cls.query().all()
138 return cls.query().all()
139
139
140 @classmethod
140 @classmethod
141 def delete(cls, id_):
141 def delete(cls, id_):
142 obj = cls.query().get(id_)
142 obj = cls.query().get(id_)
143 Session.delete(obj)
143 Session.delete(obj)
144
144
145
145
146 class RhodeCodeSetting(Base, BaseModel):
146 class RhodeCodeSetting(Base, BaseModel):
147 __tablename__ = 'rhodecode_settings'
147 __tablename__ = 'rhodecode_settings'
148 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing': True})
148 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing': True})
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152
152
153 def __init__(self, k='', v=''):
153 def __init__(self, k='', v=''):
154 self.app_settings_name = k
154 self.app_settings_name = k
155 self.app_settings_value = v
155 self.app_settings_value = v
156
156
157
157
158 @validates('_app_settings_value')
158 @validates('_app_settings_value')
159 def validate_settings_value(self, key, val):
159 def validate_settings_value(self, key, val):
160 assert type(val) == unicode
160 assert type(val) == unicode
161 return val
161 return val
162
162
163 @hybrid_property
163 @hybrid_property
164 def app_settings_value(self):
164 def app_settings_value(self):
165 v = self._app_settings_value
165 v = self._app_settings_value
166 if v == 'ldap_active':
166 if v == 'ldap_active':
167 v = str2bool(v)
167 v = str2bool(v)
168 return v
168 return v
169
169
170 @app_settings_value.setter
170 @app_settings_value.setter
171 def app_settings_value(self, val):
171 def app_settings_value(self, val):
172 """
172 """
173 Setter that will always make sure we use unicode in app_settings_value
173 Setter that will always make sure we use unicode in app_settings_value
174
174
175 :param val:
175 :param val:
176 """
176 """
177 self._app_settings_value = safe_unicode(val)
177 self._app_settings_value = safe_unicode(val)
178
178
179 def __repr__(self):
179 def __repr__(self):
180 return "<%s('%s:%s')>" % (self.__class__.__name__,
180 return "<%s('%s:%s')>" % (self.__class__.__name__,
181 self.app_settings_name, self.app_settings_value)
181 self.app_settings_name, self.app_settings_value)
182
182
183 @classmethod
183 @classmethod
184 def get_by_name(cls, ldap_key):
184 def get_by_name(cls, ldap_key):
185 return cls.query()\
185 return cls.query()\
186 .filter(cls.app_settings_name == ldap_key).scalar()
186 .filter(cls.app_settings_name == ldap_key).scalar()
187
187
188 @classmethod
188 @classmethod
189 def get_app_settings(cls, cache=False):
189 def get_app_settings(cls, cache=False):
190
190
191 ret = cls.query()
191 ret = cls.query()
192
192
193 if cache:
193 if cache:
194 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
194 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
195
195
196 if not ret:
196 if not ret:
197 raise Exception('Could not get application settings !')
197 raise Exception('Could not get application settings !')
198 settings = {}
198 settings = {}
199 for each in ret:
199 for each in ret:
200 settings['rhodecode_' + each.app_settings_name] = \
200 settings['rhodecode_' + each.app_settings_name] = \
201 each.app_settings_value
201 each.app_settings_value
202
202
203 return settings
203 return settings
204
204
205 @classmethod
205 @classmethod
206 def get_ldap_settings(cls, cache=False):
206 def get_ldap_settings(cls, cache=False):
207 ret = cls.query()\
207 ret = cls.query()\
208 .filter(cls.app_settings_name.startswith('ldap_')).all()
208 .filter(cls.app_settings_name.startswith('ldap_')).all()
209 fd = {}
209 fd = {}
210 for row in ret:
210 for row in ret:
211 fd.update({row.app_settings_name:row.app_settings_value})
211 fd.update({row.app_settings_name:row.app_settings_value})
212
212
213 return fd
213 return fd
214
214
215
215
216 class RhodeCodeUi(Base, BaseModel):
216 class RhodeCodeUi(Base, BaseModel):
217 __tablename__ = 'rhodecode_ui'
217 __tablename__ = 'rhodecode_ui'
218 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing': True})
218 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing': True})
219
219
220 HOOK_UPDATE = 'changegroup.update'
220 HOOK_UPDATE = 'changegroup.update'
221 HOOK_REPO_SIZE = 'changegroup.repo_size'
221 HOOK_REPO_SIZE = 'changegroup.repo_size'
222 HOOK_PUSH = 'pretxnchangegroup.push_logger'
222 HOOK_PUSH = 'pretxnchangegroup.push_logger'
223 HOOK_PULL = 'preoutgoing.pull_logger'
223 HOOK_PULL = 'preoutgoing.pull_logger'
224
224
225 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
225 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
226 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
226 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
227 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
227 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
228 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
228 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
229 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
229 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
230
230
231 @classmethod
231 @classmethod
232 def get_by_key(cls, key):
232 def get_by_key(cls, key):
233 return cls.query().filter(cls.ui_key == key)
233 return cls.query().filter(cls.ui_key == key)
234
234
235 @classmethod
235 @classmethod
236 def get_builtin_hooks(cls):
236 def get_builtin_hooks(cls):
237 q = cls.query()
237 q = cls.query()
238 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
238 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
239 cls.HOOK_REPO_SIZE,
239 cls.HOOK_REPO_SIZE,
240 cls.HOOK_PUSH, cls.HOOK_PULL]))
240 cls.HOOK_PUSH, cls.HOOK_PULL]))
241 return q.all()
241 return q.all()
242
242
243 @classmethod
243 @classmethod
244 def get_custom_hooks(cls):
244 def get_custom_hooks(cls):
245 q = cls.query()
245 q = cls.query()
246 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
246 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
247 cls.HOOK_REPO_SIZE,
247 cls.HOOK_REPO_SIZE,
248 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 cls.HOOK_PUSH, cls.HOOK_PULL]))
249 q = q.filter(cls.ui_section == 'hooks')
249 q = q.filter(cls.ui_section == 'hooks')
250 return q.all()
250 return q.all()
251
251
252 @classmethod
252 @classmethod
253 def create_or_update_hook(cls, key, val):
253 def create_or_update_hook(cls, key, val):
254 new_ui = cls.get_by_key(key).scalar() or cls()
254 new_ui = cls.get_by_key(key).scalar() or cls()
255 new_ui.ui_section = 'hooks'
255 new_ui.ui_section = 'hooks'
256 new_ui.ui_active = True
256 new_ui.ui_active = True
257 new_ui.ui_key = key
257 new_ui.ui_key = key
258 new_ui.ui_value = val
258 new_ui.ui_value = val
259
259
260 Session.add(new_ui)
260 Session.add(new_ui)
261
261
262
262
263 class User(Base, BaseModel):
263 class User(Base, BaseModel):
264 __tablename__ = 'users'
264 __tablename__ = 'users'
265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing': True})
265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing': True})
266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277
277
278 user_log = relationship('UserLog', cascade='all')
278 user_log = relationship('UserLog', cascade='all')
279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
280
280
281 repositories = relationship('Repository')
281 repositories = relationship('Repository')
282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
283 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
283 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
284
284
285 group_member = relationship('UsersGroupMember', cascade='all')
285 group_member = relationship('UsersGroupMember', cascade='all')
286
286
287 notifications = relationship('UserNotification',)
287 notifications = relationship('UserNotification',)
288
288
289 @hybrid_property
289 @hybrid_property
290 def email(self):
290 def email(self):
291 return self._email
291 return self._email
292
292
293 @email.setter
293 @email.setter
294 def email(self, val):
294 def email(self, val):
295 self._email = val.lower() if val else None
295 self._email = val.lower() if val else None
296
296
297 @property
297 @property
298 def full_name(self):
298 def full_name(self):
299 return '%s %s' % (self.name, self.lastname)
299 return '%s %s' % (self.name, self.lastname)
300
300
301 @property
301 @property
302 def full_name_or_username(self):
303 return ('%s %s' % (self.name, self.lastname)
304 if (self.name and self.lastname) else self.username)
305
306 @property
302 def full_contact(self):
307 def full_contact(self):
303 return '%s %s <%s>' % (self.name, self.lastname, self.email)
308 return '%s %s <%s>' % (self.name, self.lastname, self.email)
304
309
305 @property
310 @property
306 def short_contact(self):
311 def short_contact(self):
307 return '%s %s' % (self.name, self.lastname)
312 return '%s %s' % (self.name, self.lastname)
308
313
309 @property
314 @property
310 def is_admin(self):
315 def is_admin(self):
311 return self.admin
316 return self.admin
312
317
313 def __repr__(self):
318 def __repr__(self):
314 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
319 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
315 self.user_id, self.username)
320 self.user_id, self.username)
316
321
317 @classmethod
322 @classmethod
318 def get_by_username(cls, username, case_insensitive=False, cache=False):
323 def get_by_username(cls, username, case_insensitive=False, cache=False):
319 if case_insensitive:
324 if case_insensitive:
320 q = cls.query().filter(cls.username.ilike(username))
325 q = cls.query().filter(cls.username.ilike(username))
321 else:
326 else:
322 q = cls.query().filter(cls.username == username)
327 q = cls.query().filter(cls.username == username)
323
328
324 if cache:
329 if cache:
325 q = q.options(FromCache("sql_cache_short",
330 q = q.options(FromCache("sql_cache_short",
326 "get_user_%s" % username))
331 "get_user_%s" % username))
327 return q.scalar()
332 return q.scalar()
328
333
329 @classmethod
334 @classmethod
330 def get_by_api_key(cls, api_key, cache=False):
335 def get_by_api_key(cls, api_key, cache=False):
331 q = cls.query().filter(cls.api_key == api_key)
336 q = cls.query().filter(cls.api_key == api_key)
332
337
333 if cache:
338 if cache:
334 q = q.options(FromCache("sql_cache_short",
339 q = q.options(FromCache("sql_cache_short",
335 "get_api_key_%s" % api_key))
340 "get_api_key_%s" % api_key))
336 return q.scalar()
341 return q.scalar()
337
342
338 @classmethod
343 @classmethod
339 def get_by_email(cls, email, case_insensitive=False, cache=False):
344 def get_by_email(cls, email, case_insensitive=False, cache=False):
340 if case_insensitive:
345 if case_insensitive:
341 q = cls.query().filter(cls.email.ilike(email))
346 q = cls.query().filter(cls.email.ilike(email))
342 else:
347 else:
343 q = cls.query().filter(cls.email == email)
348 q = cls.query().filter(cls.email == email)
344
349
345 if cache:
350 if cache:
346 q = q.options(FromCache("sql_cache_short",
351 q = q.options(FromCache("sql_cache_short",
347 "get_api_key_%s" % email))
352 "get_api_key_%s" % email))
348 return q.scalar()
353 return q.scalar()
349
354
350 def update_lastlogin(self):
355 def update_lastlogin(self):
351 """Update user lastlogin"""
356 """Update user lastlogin"""
352 self.last_login = datetime.datetime.now()
357 self.last_login = datetime.datetime.now()
353 Session.add(self)
358 Session.add(self)
354 log.debug('updated user %s lastlogin', self.username)
359 log.debug('updated user %s lastlogin', self.username)
355
360
356 def __json__(self):
361 def __json__(self):
357 return dict(email=self.email,
362 return dict(
358 full_name=self.full_name)
363 email=self.email,
364 full_name=self.full_name,
365 full_name_or_username=self.full_name_or_username,
366 short_contact=self.short_contact,
367 full_contact=self.full_contact
368 )
359
369
360
370
361 class UserLog(Base, BaseModel):
371 class UserLog(Base, BaseModel):
362 __tablename__ = 'user_logs'
372 __tablename__ = 'user_logs'
363 __table_args__ = {'extend_existing': True}
373 __table_args__ = {'extend_existing': True}
364 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
374 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
365 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
375 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
366 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
376 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
367 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
377 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
368 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
378 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
369 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
379 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
370 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
380 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
371
381
372 @property
382 @property
373 def action_as_day(self):
383 def action_as_day(self):
374 return datetime.date(*self.action_date.timetuple()[:3])
384 return datetime.date(*self.action_date.timetuple()[:3])
375
385
376 user = relationship('User')
386 user = relationship('User')
377 repository = relationship('Repository',cascade='')
387 repository = relationship('Repository',cascade='')
378
388
379
389
380 class UsersGroup(Base, BaseModel):
390 class UsersGroup(Base, BaseModel):
381 __tablename__ = 'users_groups'
391 __tablename__ = 'users_groups'
382 __table_args__ = {'extend_existing': True}
392 __table_args__ = {'extend_existing': True}
383
393
384 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
394 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
385 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
395 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
386 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
396 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
387
397
388 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
398 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
389
399
390 def __repr__(self):
400 def __repr__(self):
391 return '<userGroup(%s)>' % (self.users_group_name)
401 return '<userGroup(%s)>' % (self.users_group_name)
392
402
393 @classmethod
403 @classmethod
394 def get_by_group_name(cls, group_name, cache=False,
404 def get_by_group_name(cls, group_name, cache=False,
395 case_insensitive=False):
405 case_insensitive=False):
396 if case_insensitive:
406 if case_insensitive:
397 q = cls.query().filter(cls.users_group_name.ilike(group_name))
407 q = cls.query().filter(cls.users_group_name.ilike(group_name))
398 else:
408 else:
399 q = cls.query().filter(cls.users_group_name == group_name)
409 q = cls.query().filter(cls.users_group_name == group_name)
400 if cache:
410 if cache:
401 q = q.options(FromCache("sql_cache_short",
411 q = q.options(FromCache("sql_cache_short",
402 "get_user_%s" % group_name))
412 "get_user_%s" % group_name))
403 return q.scalar()
413 return q.scalar()
404
414
405 @classmethod
415 @classmethod
406 def get(cls, users_group_id, cache=False):
416 def get(cls, users_group_id, cache=False):
407 users_group = cls.query()
417 users_group = cls.query()
408 if cache:
418 if cache:
409 users_group = users_group.options(FromCache("sql_cache_short",
419 users_group = users_group.options(FromCache("sql_cache_short",
410 "get_users_group_%s" % users_group_id))
420 "get_users_group_%s" % users_group_id))
411 return users_group.get(users_group_id)
421 return users_group.get(users_group_id)
412
422
413
423
414 class UsersGroupMember(Base, BaseModel):
424 class UsersGroupMember(Base, BaseModel):
415 __tablename__ = 'users_groups_members'
425 __tablename__ = 'users_groups_members'
416 __table_args__ = {'extend_existing': True}
426 __table_args__ = {'extend_existing': True}
417
427
418 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
428 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
419 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
429 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
420 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
430 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
421
431
422 user = relationship('User', lazy='joined')
432 user = relationship('User', lazy='joined')
423 users_group = relationship('UsersGroup')
433 users_group = relationship('UsersGroup')
424
434
425 def __init__(self, gr_id='', u_id=''):
435 def __init__(self, gr_id='', u_id=''):
426 self.users_group_id = gr_id
436 self.users_group_id = gr_id
427 self.user_id = u_id
437 self.user_id = u_id
428
438
429 @staticmethod
439 @staticmethod
430 def add_user_to_group(group, user):
440 def add_user_to_group(group, user):
431 ugm = UsersGroupMember()
441 ugm = UsersGroupMember()
432 ugm.users_group = group
442 ugm.users_group = group
433 ugm.user = user
443 ugm.user = user
434 Session.add(ugm)
444 Session.add(ugm)
435 Session.commit()
445 Session.commit()
436 return ugm
446 return ugm
437
447
438
448
439 class Repository(Base, BaseModel):
449 class Repository(Base, BaseModel):
440 __tablename__ = 'repositories'
450 __tablename__ = 'repositories'
441 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing': True},)
451 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing': True},)
442
452
443 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
453 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
444 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
454 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
445 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
455 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
446 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
456 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
447 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
448 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
458 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
449 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
459 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
450 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
460 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
451 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
461 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
452 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
462 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
453
463
454 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
464 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
455 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
465 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
456
466
457 user = relationship('User')
467 user = relationship('User')
458 fork = relationship('Repository', remote_side=repo_id)
468 fork = relationship('Repository', remote_side=repo_id)
459 group = relationship('RepoGroup')
469 group = relationship('RepoGroup')
460 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
470 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
461 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
471 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
462 stats = relationship('Statistics', cascade='all', uselist=False)
472 stats = relationship('Statistics', cascade='all', uselist=False)
463
473
464 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
474 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
465
475
466 logs = relationship('UserLog')
476 logs = relationship('UserLog')
467
477
468 def __repr__(self):
478 def __repr__(self):
469 return "<%s('%s:%s')>" % (self.__class__.__name__,
479 return "<%s('%s:%s')>" % (self.__class__.__name__,
470 self.repo_id, self.repo_name)
480 self.repo_id, self.repo_name)
471
481
472 @classmethod
482 @classmethod
473 def url_sep(cls):
483 def url_sep(cls):
474 return '/'
484 return '/'
475
485
476 @classmethod
486 @classmethod
477 def get_by_repo_name(cls, repo_name):
487 def get_by_repo_name(cls, repo_name):
478 q = Session.query(cls).filter(cls.repo_name == repo_name)
488 q = Session.query(cls).filter(cls.repo_name == repo_name)
479 q = q.options(joinedload(Repository.fork))\
489 q = q.options(joinedload(Repository.fork))\
480 .options(joinedload(Repository.user))\
490 .options(joinedload(Repository.user))\
481 .options(joinedload(Repository.group))
491 .options(joinedload(Repository.group))
482 return q.scalar()
492 return q.scalar()
483
493
484 @classmethod
494 @classmethod
485 def get_repo_forks(cls, repo_id):
495 def get_repo_forks(cls, repo_id):
486 return cls.query().filter(Repository.fork_id == repo_id)
496 return cls.query().filter(Repository.fork_id == repo_id)
487
497
488 @classmethod
498 @classmethod
489 def base_path(cls):
499 def base_path(cls):
490 """
500 """
491 Returns base path when all repos are stored
501 Returns base path when all repos are stored
492
502
493 :param cls:
503 :param cls:
494 """
504 """
495 q = Session.query(RhodeCodeUi)\
505 q = Session.query(RhodeCodeUi)\
496 .filter(RhodeCodeUi.ui_key == cls.url_sep())
506 .filter(RhodeCodeUi.ui_key == cls.url_sep())
497 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
507 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
498 return q.one().ui_value
508 return q.one().ui_value
499
509
500 @property
510 @property
501 def just_name(self):
511 def just_name(self):
502 return self.repo_name.split(Repository.url_sep())[-1]
512 return self.repo_name.split(Repository.url_sep())[-1]
503
513
504 @property
514 @property
505 def groups_with_parents(self):
515 def groups_with_parents(self):
506 groups = []
516 groups = []
507 if self.group is None:
517 if self.group is None:
508 return groups
518 return groups
509
519
510 cur_gr = self.group
520 cur_gr = self.group
511 groups.insert(0, cur_gr)
521 groups.insert(0, cur_gr)
512 while 1:
522 while 1:
513 gr = getattr(cur_gr, 'parent_group', None)
523 gr = getattr(cur_gr, 'parent_group', None)
514 cur_gr = cur_gr.parent_group
524 cur_gr = cur_gr.parent_group
515 if gr is None:
525 if gr is None:
516 break
526 break
517 groups.insert(0, gr)
527 groups.insert(0, gr)
518
528
519 return groups
529 return groups
520
530
521 @property
531 @property
522 def groups_and_repo(self):
532 def groups_and_repo(self):
523 return self.groups_with_parents, self.just_name
533 return self.groups_with_parents, self.just_name
524
534
525 @LazyProperty
535 @LazyProperty
526 def repo_path(self):
536 def repo_path(self):
527 """
537 """
528 Returns base full path for that repository means where it actually
538 Returns base full path for that repository means where it actually
529 exists on a filesystem
539 exists on a filesystem
530 """
540 """
531 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
541 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
532 Repository.url_sep())
542 Repository.url_sep())
533 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
543 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
534 return q.one().ui_value
544 return q.one().ui_value
535
545
536 @property
546 @property
537 def repo_full_path(self):
547 def repo_full_path(self):
538 p = [self.repo_path]
548 p = [self.repo_path]
539 # we need to split the name by / since this is how we store the
549 # we need to split the name by / since this is how we store the
540 # names in the database, but that eventually needs to be converted
550 # names in the database, but that eventually needs to be converted
541 # into a valid system path
551 # into a valid system path
542 p += self.repo_name.split(Repository.url_sep())
552 p += self.repo_name.split(Repository.url_sep())
543 return os.path.join(*p)
553 return os.path.join(*p)
544
554
545 def get_new_name(self, repo_name):
555 def get_new_name(self, repo_name):
546 """
556 """
547 returns new full repository name based on assigned group and new new
557 returns new full repository name based on assigned group and new new
548
558
549 :param group_name:
559 :param group_name:
550 """
560 """
551 path_prefix = self.group.full_path_splitted if self.group else []
561 path_prefix = self.group.full_path_splitted if self.group else []
552 return Repository.url_sep().join(path_prefix + [repo_name])
562 return Repository.url_sep().join(path_prefix + [repo_name])
553
563
554 @property
564 @property
555 def _ui(self):
565 def _ui(self):
556 """
566 """
557 Creates an db based ui object for this repository
567 Creates an db based ui object for this repository
558 """
568 """
559 from mercurial import ui
569 from mercurial import ui
560 from mercurial import config
570 from mercurial import config
561 baseui = ui.ui()
571 baseui = ui.ui()
562
572
563 #clean the baseui object
573 #clean the baseui object
564 baseui._ocfg = config.config()
574 baseui._ocfg = config.config()
565 baseui._ucfg = config.config()
575 baseui._ucfg = config.config()
566 baseui._tcfg = config.config()
576 baseui._tcfg = config.config()
567
577
568 ret = RhodeCodeUi.query()\
578 ret = RhodeCodeUi.query()\
569 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
579 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
570
580
571 hg_ui = ret
581 hg_ui = ret
572 for ui_ in hg_ui:
582 for ui_ in hg_ui:
573 if ui_.ui_active:
583 if ui_.ui_active:
574 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
584 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
575 ui_.ui_key, ui_.ui_value)
585 ui_.ui_key, ui_.ui_value)
576 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
586 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
577
587
578 return baseui
588 return baseui
579
589
580 @classmethod
590 @classmethod
581 def is_valid(cls, repo_name):
591 def is_valid(cls, repo_name):
582 """
592 """
583 returns True if given repo name is a valid filesystem repository
593 returns True if given repo name is a valid filesystem repository
584
594
585 :param cls:
595 :param cls:
586 :param repo_name:
596 :param repo_name:
587 """
597 """
588 from rhodecode.lib.utils import is_valid_repo
598 from rhodecode.lib.utils import is_valid_repo
589
599
590 return is_valid_repo(repo_name, cls.base_path())
600 return is_valid_repo(repo_name, cls.base_path())
591
601
592 #==========================================================================
602 #==========================================================================
593 # SCM PROPERTIES
603 # SCM PROPERTIES
594 #==========================================================================
604 #==========================================================================
595
605
596 def get_changeset(self, rev):
606 def get_changeset(self, rev):
597 return get_changeset_safe(self.scm_instance, rev)
607 return get_changeset_safe(self.scm_instance, rev)
598
608
599 @property
609 @property
600 def tip(self):
610 def tip(self):
601 return self.get_changeset('tip')
611 return self.get_changeset('tip')
602
612
603 @property
613 @property
604 def author(self):
614 def author(self):
605 return self.tip.author
615 return self.tip.author
606
616
607 @property
617 @property
608 def last_change(self):
618 def last_change(self):
609 return self.scm_instance.last_change
619 return self.scm_instance.last_change
610
620
611 def comments(self, revisions=None):
621 def comments(self, revisions=None):
612 """
622 """
613 Returns comments for this repository grouped by revisions
623 Returns comments for this repository grouped by revisions
614
624
615 :param revisions: filter query by revisions only
625 :param revisions: filter query by revisions only
616 """
626 """
617 cmts = ChangesetComment.query()\
627 cmts = ChangesetComment.query()\
618 .filter(ChangesetComment.repo == self)
628 .filter(ChangesetComment.repo == self)
619 if revisions:
629 if revisions:
620 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
630 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
621 grouped = defaultdict(list)
631 grouped = defaultdict(list)
622 for cmt in cmts.all():
632 for cmt in cmts.all():
623 grouped[cmt.revision].append(cmt)
633 grouped[cmt.revision].append(cmt)
624 return grouped
634 return grouped
625
635
626
636
627 #==========================================================================
637 #==========================================================================
628 # SCM CACHE INSTANCE
638 # SCM CACHE INSTANCE
629 #==========================================================================
639 #==========================================================================
630
640
631 @property
641 @property
632 def invalidate(self):
642 def invalidate(self):
633 return CacheInvalidation.invalidate(self.repo_name)
643 return CacheInvalidation.invalidate(self.repo_name)
634
644
635 def set_invalidate(self):
645 def set_invalidate(self):
636 """
646 """
637 set a cache for invalidation for this instance
647 set a cache for invalidation for this instance
638 """
648 """
639 CacheInvalidation.set_invalidate(self.repo_name)
649 CacheInvalidation.set_invalidate(self.repo_name)
640
650
641 @LazyProperty
651 @LazyProperty
642 def scm_instance(self):
652 def scm_instance(self):
643 return self.__get_instance()
653 return self.__get_instance()
644
654
645 @property
655 @property
646 def scm_instance_cached(self):
656 def scm_instance_cached(self):
647 @cache_region('long_term')
657 @cache_region('long_term')
648 def _c(repo_name):
658 def _c(repo_name):
649 return self.__get_instance()
659 return self.__get_instance()
650 rn = self.repo_name
660 rn = self.repo_name
651 log.debug('Getting cached instance of repo')
661 log.debug('Getting cached instance of repo')
652 inv = self.invalidate
662 inv = self.invalidate
653 if inv is not None:
663 if inv is not None:
654 region_invalidate(_c, None, rn)
664 region_invalidate(_c, None, rn)
655 # update our cache
665 # update our cache
656 CacheInvalidation.set_valid(inv.cache_key)
666 CacheInvalidation.set_valid(inv.cache_key)
657 return _c(rn)
667 return _c(rn)
658
668
659 def __get_instance(self):
669 def __get_instance(self):
660 repo_full_path = self.repo_full_path
670 repo_full_path = self.repo_full_path
661 try:
671 try:
662 alias = get_scm(repo_full_path)[0]
672 alias = get_scm(repo_full_path)[0]
663 log.debug('Creating instance of %s repository', alias)
673 log.debug('Creating instance of %s repository', alias)
664 backend = get_backend(alias)
674 backend = get_backend(alias)
665 except VCSError:
675 except VCSError:
666 log.error(traceback.format_exc())
676 log.error(traceback.format_exc())
667 log.error('Perhaps this repository is in db and not in '
677 log.error('Perhaps this repository is in db and not in '
668 'filesystem run rescan repositories with '
678 'filesystem run rescan repositories with '
669 '"destroy old data " option from admin panel')
679 '"destroy old data " option from admin panel')
670 return
680 return
671
681
672 if alias == 'hg':
682 if alias == 'hg':
673 repo = backend(safe_str(repo_full_path), create=False,
683 repo = backend(safe_str(repo_full_path), create=False,
674 baseui=self._ui)
684 baseui=self._ui)
675 # skip hidden web repository
685 # skip hidden web repository
676 if repo._get_hidden():
686 if repo._get_hidden():
677 return
687 return
678 else:
688 else:
679 repo = backend(repo_full_path, create=False)
689 repo = backend(repo_full_path, create=False)
680
690
681 return repo
691 return repo
682
692
683
693
684 class RepoGroup(Base, BaseModel):
694 class RepoGroup(Base, BaseModel):
685 __tablename__ = 'groups'
695 __tablename__ = 'groups'
686 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
696 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
687 CheckConstraint('group_id != group_parent_id'), {'extend_existing': True},)
697 CheckConstraint('group_id != group_parent_id'), {'extend_existing': True},)
688 __mapper_args__ = {'order_by':'group_name'}
698 __mapper_args__ = {'order_by':'group_name'}
689
699
690 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
700 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
701 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
692 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
702 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
693 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
703 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
694
704
695 parent_group = relationship('RepoGroup', remote_side=group_id)
705 parent_group = relationship('RepoGroup', remote_side=group_id)
696
706
697 def __init__(self, group_name='', parent_group=None):
707 def __init__(self, group_name='', parent_group=None):
698 self.group_name = group_name
708 self.group_name = group_name
699 self.parent_group = parent_group
709 self.parent_group = parent_group
700
710
701 def __repr__(self):
711 def __repr__(self):
702 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
712 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
703 self.group_name)
713 self.group_name)
704
714
705 @classmethod
715 @classmethod
706 def groups_choices(cls):
716 def groups_choices(cls):
707 from webhelpers.html import literal as _literal
717 from webhelpers.html import literal as _literal
708 repo_groups = [('', '')]
718 repo_groups = [('', '')]
709 sep = ' &raquo; '
719 sep = ' &raquo; '
710 _name = lambda k: _literal(sep.join(k))
720 _name = lambda k: _literal(sep.join(k))
711
721
712 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
722 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
713 for x in cls.query().all()])
723 for x in cls.query().all()])
714
724
715 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
725 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
716 return repo_groups
726 return repo_groups
717
727
718 @classmethod
728 @classmethod
719 def url_sep(cls):
729 def url_sep(cls):
720 return '/'
730 return '/'
721
731
722 @classmethod
732 @classmethod
723 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
733 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
724 if case_insensitive:
734 if case_insensitive:
725 gr = cls.query()\
735 gr = cls.query()\
726 .filter(cls.group_name.ilike(group_name))
736 .filter(cls.group_name.ilike(group_name))
727 else:
737 else:
728 gr = cls.query()\
738 gr = cls.query()\
729 .filter(cls.group_name == group_name)
739 .filter(cls.group_name == group_name)
730 if cache:
740 if cache:
731 gr = gr.options(FromCache("sql_cache_short",
741 gr = gr.options(FromCache("sql_cache_short",
732 "get_group_%s" % group_name))
742 "get_group_%s" % group_name))
733 return gr.scalar()
743 return gr.scalar()
734
744
735 @property
745 @property
736 def parents(self):
746 def parents(self):
737 parents_recursion_limit = 5
747 parents_recursion_limit = 5
738 groups = []
748 groups = []
739 if self.parent_group is None:
749 if self.parent_group is None:
740 return groups
750 return groups
741 cur_gr = self.parent_group
751 cur_gr = self.parent_group
742 groups.insert(0, cur_gr)
752 groups.insert(0, cur_gr)
743 cnt = 0
753 cnt = 0
744 while 1:
754 while 1:
745 cnt += 1
755 cnt += 1
746 gr = getattr(cur_gr, 'parent_group', None)
756 gr = getattr(cur_gr, 'parent_group', None)
747 cur_gr = cur_gr.parent_group
757 cur_gr = cur_gr.parent_group
748 if gr is None:
758 if gr is None:
749 break
759 break
750 if cnt == parents_recursion_limit:
760 if cnt == parents_recursion_limit:
751 # this will prevent accidental infinit loops
761 # this will prevent accidental infinit loops
752 log.error('group nested more than %s' %
762 log.error('group nested more than %s' %
753 parents_recursion_limit)
763 parents_recursion_limit)
754 break
764 break
755
765
756 groups.insert(0, gr)
766 groups.insert(0, gr)
757 return groups
767 return groups
758
768
759 @property
769 @property
760 def children(self):
770 def children(self):
761 return RepoGroup.query().filter(RepoGroup.parent_group == self)
771 return RepoGroup.query().filter(RepoGroup.parent_group == self)
762
772
763 @property
773 @property
764 def name(self):
774 def name(self):
765 return self.group_name.split(RepoGroup.url_sep())[-1]
775 return self.group_name.split(RepoGroup.url_sep())[-1]
766
776
767 @property
777 @property
768 def full_path(self):
778 def full_path(self):
769 return self.group_name
779 return self.group_name
770
780
771 @property
781 @property
772 def full_path_splitted(self):
782 def full_path_splitted(self):
773 return self.group_name.split(RepoGroup.url_sep())
783 return self.group_name.split(RepoGroup.url_sep())
774
784
775 @property
785 @property
776 def repositories(self):
786 def repositories(self):
777 return Repository.query().filter(Repository.group == self)
787 return Repository.query().filter(Repository.group == self)
778
788
779 @property
789 @property
780 def repositories_recursive_count(self):
790 def repositories_recursive_count(self):
781 cnt = self.repositories.count()
791 cnt = self.repositories.count()
782
792
783 def children_count(group):
793 def children_count(group):
784 cnt = 0
794 cnt = 0
785 for child in group.children:
795 for child in group.children:
786 cnt += child.repositories.count()
796 cnt += child.repositories.count()
787 cnt += children_count(child)
797 cnt += children_count(child)
788 return cnt
798 return cnt
789
799
790 return cnt + children_count(self)
800 return cnt + children_count(self)
791
801
792
802
793 def get_new_name(self, group_name):
803 def get_new_name(self, group_name):
794 """
804 """
795 returns new full group name based on parent and new name
805 returns new full group name based on parent and new name
796
806
797 :param group_name:
807 :param group_name:
798 """
808 """
799 path_prefix = (self.parent_group.full_path_splitted if
809 path_prefix = (self.parent_group.full_path_splitted if
800 self.parent_group else [])
810 self.parent_group else [])
801 return RepoGroup.url_sep().join(path_prefix + [group_name])
811 return RepoGroup.url_sep().join(path_prefix + [group_name])
802
812
803
813
804 class Permission(Base, BaseModel):
814 class Permission(Base, BaseModel):
805 __tablename__ = 'permissions'
815 __tablename__ = 'permissions'
806 __table_args__ = {'extend_existing': True}
816 __table_args__ = {'extend_existing': True}
807 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
817 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
808 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
818 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
809 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
819 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
810
820
811 def __repr__(self):
821 def __repr__(self):
812 return "<%s('%s:%s')>" % (self.__class__.__name__,
822 return "<%s('%s:%s')>" % (self.__class__.__name__,
813 self.permission_id, self.permission_name)
823 self.permission_id, self.permission_name)
814
824
815 @classmethod
825 @classmethod
816 def get_by_key(cls, key):
826 def get_by_key(cls, key):
817 return cls.query().filter(cls.permission_name == key).scalar()
827 return cls.query().filter(cls.permission_name == key).scalar()
818
828
819 @classmethod
829 @classmethod
820 def get_default_perms(cls, default_user_id):
830 def get_default_perms(cls, default_user_id):
821 q = Session.query(UserRepoToPerm, Repository, cls)\
831 q = Session.query(UserRepoToPerm, Repository, cls)\
822 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
832 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
823 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
833 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
824 .filter(UserRepoToPerm.user_id == default_user_id)
834 .filter(UserRepoToPerm.user_id == default_user_id)
825
835
826 return q.all()
836 return q.all()
827
837
828
838
829 class UserRepoToPerm(Base, BaseModel):
839 class UserRepoToPerm(Base, BaseModel):
830 __tablename__ = 'repo_to_perm'
840 __tablename__ = 'repo_to_perm'
831 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing': True})
841 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing': True})
832 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
842 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
833 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
843 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
834 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
844 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
835 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
845 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
836
846
837 user = relationship('User')
847 user = relationship('User')
838 permission = relationship('Permission')
848 permission = relationship('Permission')
839 repository = relationship('Repository')
849 repository = relationship('Repository')
840
850
841 @classmethod
851 @classmethod
842 def create(cls, user, repository, permission):
852 def create(cls, user, repository, permission):
843 n = cls()
853 n = cls()
844 n.user = user
854 n.user = user
845 n.repository = repository
855 n.repository = repository
846 n.permission = permission
856 n.permission = permission
847 Session.add(n)
857 Session.add(n)
848 return n
858 return n
849
859
850 def __repr__(self):
860 def __repr__(self):
851 return '<user:%s => %s >' % (self.user, self.repository)
861 return '<user:%s => %s >' % (self.user, self.repository)
852
862
853
863
854 class UserToPerm(Base, BaseModel):
864 class UserToPerm(Base, BaseModel):
855 __tablename__ = 'user_to_perm'
865 __tablename__ = 'user_to_perm'
856 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True})
866 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True})
857 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
867 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
869 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
860
870
861 user = relationship('User')
871 user = relationship('User')
862 permission = relationship('Permission', lazy='joined')
872 permission = relationship('Permission', lazy='joined')
863
873
864
874
865 class UsersGroupRepoToPerm(Base, BaseModel):
875 class UsersGroupRepoToPerm(Base, BaseModel):
866 __tablename__ = 'users_group_repo_to_perm'
876 __tablename__ = 'users_group_repo_to_perm'
867 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing': True})
877 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing': True})
868 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
878 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
869 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
879 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
870 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
880 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
871 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
881 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
872
882
873 users_group = relationship('UsersGroup')
883 users_group = relationship('UsersGroup')
874 permission = relationship('Permission')
884 permission = relationship('Permission')
875 repository = relationship('Repository')
885 repository = relationship('Repository')
876
886
877 @classmethod
887 @classmethod
878 def create(cls, users_group, repository, permission):
888 def create(cls, users_group, repository, permission):
879 n = cls()
889 n = cls()
880 n.users_group = users_group
890 n.users_group = users_group
881 n.repository = repository
891 n.repository = repository
882 n.permission = permission
892 n.permission = permission
883 Session.add(n)
893 Session.add(n)
884 return n
894 return n
885
895
886 def __repr__(self):
896 def __repr__(self):
887 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
897 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
888
898
889
899
890 class UsersGroupToPerm(Base, BaseModel):
900 class UsersGroupToPerm(Base, BaseModel):
891 __tablename__ = 'users_group_to_perm'
901 __tablename__ = 'users_group_to_perm'
892 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
902 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
893 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
903 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
904 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
895
905
896 users_group = relationship('UsersGroup')
906 users_group = relationship('UsersGroup')
897 permission = relationship('Permission')
907 permission = relationship('Permission')
898
908
899
909
900 class UserRepoGroupToPerm(Base, BaseModel):
910 class UserRepoGroupToPerm(Base, BaseModel):
901 __tablename__ = 'group_to_perm'
911 __tablename__ = 'group_to_perm'
902 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing': True})
912 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing': True})
903
913
904 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
914 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
905 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
915 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
906 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
916 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
907 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
917 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
908
918
909 user = relationship('User')
919 user = relationship('User')
910 permission = relationship('Permission')
920 permission = relationship('Permission')
911 group = relationship('RepoGroup')
921 group = relationship('RepoGroup')
912
922
913
923
914 class UsersGroupRepoGroupToPerm(Base, BaseModel):
924 class UsersGroupRepoGroupToPerm(Base, BaseModel):
915 __tablename__ = 'users_group_repo_group_to_perm'
925 __tablename__ = 'users_group_repo_group_to_perm'
916 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing': True})
926 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing': True})
917
927
918 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
928 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
919 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
929 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
920 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
930 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
921 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
931 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
922
932
923 users_group = relationship('UsersGroup')
933 users_group = relationship('UsersGroup')
924 permission = relationship('Permission')
934 permission = relationship('Permission')
925 group = relationship('RepoGroup')
935 group = relationship('RepoGroup')
926
936
927
937
928 class Statistics(Base, BaseModel):
938 class Statistics(Base, BaseModel):
929 __tablename__ = 'statistics'
939 __tablename__ = 'statistics'
930 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
940 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
931 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
932 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
942 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
933 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
943 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
934 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
944 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
935 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
945 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
936 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
946 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
937
947
938 repository = relationship('Repository', single_parent=True)
948 repository = relationship('Repository', single_parent=True)
939
949
940
950
941 class UserFollowing(Base, BaseModel):
951 class UserFollowing(Base, BaseModel):
942 __tablename__ = 'user_followings'
952 __tablename__ = 'user_followings'
943 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
953 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
944 UniqueConstraint('user_id', 'follows_user_id')
954 UniqueConstraint('user_id', 'follows_user_id')
945 , {'extend_existing': True})
955 , {'extend_existing': True})
946
956
947 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
957 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
949 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
959 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
950 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
960 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
951 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
961 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
952
962
953 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
963 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
954
964
955 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
965 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
956 follows_repository = relationship('Repository', order_by='Repository.repo_name')
966 follows_repository = relationship('Repository', order_by='Repository.repo_name')
957
967
958
968
959 @classmethod
969 @classmethod
960 def get_repo_followers(cls, repo_id):
970 def get_repo_followers(cls, repo_id):
961 return cls.query().filter(cls.follows_repo_id == repo_id)
971 return cls.query().filter(cls.follows_repo_id == repo_id)
962
972
963
973
964 class CacheInvalidation(Base, BaseModel):
974 class CacheInvalidation(Base, BaseModel):
965 __tablename__ = 'cache_invalidation'
975 __tablename__ = 'cache_invalidation'
966 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
976 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
967 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
977 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
968 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
978 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
969 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
979 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
970 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
980 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
971
981
972
982
973 def __init__(self, cache_key, cache_args=''):
983 def __init__(self, cache_key, cache_args=''):
974 self.cache_key = cache_key
984 self.cache_key = cache_key
975 self.cache_args = cache_args
985 self.cache_args = cache_args
976 self.cache_active = False
986 self.cache_active = False
977
987
978 def __repr__(self):
988 def __repr__(self):
979 return "<%s('%s:%s')>" % (self.__class__.__name__,
989 return "<%s('%s:%s')>" % (self.__class__.__name__,
980 self.cache_id, self.cache_key)
990 self.cache_id, self.cache_key)
981
991
982 @classmethod
992 @classmethod
983 def invalidate(cls, key):
993 def invalidate(cls, key):
984 """
994 """
985 Returns Invalidation object if this given key should be invalidated
995 Returns Invalidation object if this given key should be invalidated
986 None otherwise. `cache_active = False` means that this cache
996 None otherwise. `cache_active = False` means that this cache
987 state is not valid and needs to be invalidated
997 state is not valid and needs to be invalidated
988
998
989 :param key:
999 :param key:
990 """
1000 """
991 return cls.query()\
1001 return cls.query()\
992 .filter(CacheInvalidation.cache_key == key)\
1002 .filter(CacheInvalidation.cache_key == key)\
993 .filter(CacheInvalidation.cache_active == False)\
1003 .filter(CacheInvalidation.cache_active == False)\
994 .scalar()
1004 .scalar()
995
1005
996 @classmethod
1006 @classmethod
997 def set_invalidate(cls, key):
1007 def set_invalidate(cls, key):
998 """
1008 """
999 Mark this Cache key for invalidation
1009 Mark this Cache key for invalidation
1000
1010
1001 :param key:
1011 :param key:
1002 """
1012 """
1003
1013
1004 log.debug('marking %s for invalidation' % key)
1014 log.debug('marking %s for invalidation' % key)
1005 inv_obj = Session.query(cls)\
1015 inv_obj = Session.query(cls)\
1006 .filter(cls.cache_key == key).scalar()
1016 .filter(cls.cache_key == key).scalar()
1007 if inv_obj:
1017 if inv_obj:
1008 inv_obj.cache_active = False
1018 inv_obj.cache_active = False
1009 else:
1019 else:
1010 log.debug('cache key not found in invalidation db -> creating one')
1020 log.debug('cache key not found in invalidation db -> creating one')
1011 inv_obj = CacheInvalidation(key)
1021 inv_obj = CacheInvalidation(key)
1012
1022
1013 try:
1023 try:
1014 Session.add(inv_obj)
1024 Session.add(inv_obj)
1015 Session.commit()
1025 Session.commit()
1016 except Exception:
1026 except Exception:
1017 log.error(traceback.format_exc())
1027 log.error(traceback.format_exc())
1018 Session.rollback()
1028 Session.rollback()
1019
1029
1020 @classmethod
1030 @classmethod
1021 def set_valid(cls, key):
1031 def set_valid(cls, key):
1022 """
1032 """
1023 Mark this cache key as active and currently cached
1033 Mark this cache key as active and currently cached
1024
1034
1025 :param key:
1035 :param key:
1026 """
1036 """
1027 inv_obj = CacheInvalidation.query()\
1037 inv_obj = CacheInvalidation.query()\
1028 .filter(CacheInvalidation.cache_key == key).scalar()
1038 .filter(CacheInvalidation.cache_key == key).scalar()
1029 inv_obj.cache_active = True
1039 inv_obj.cache_active = True
1030 Session.add(inv_obj)
1040 Session.add(inv_obj)
1031 Session.commit()
1041 Session.commit()
1032
1042
1033
1043
1034 class ChangesetComment(Base, BaseModel):
1044 class ChangesetComment(Base, BaseModel):
1035 __tablename__ = 'changeset_comments'
1045 __tablename__ = 'changeset_comments'
1036 __table_args__ = ({'extend_existing': True},)
1046 __table_args__ = ({'extend_existing': True},)
1037 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1047 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1038 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1048 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1039 revision = Column('revision', String(40), nullable=False)
1049 revision = Column('revision', String(40), nullable=False)
1040 line_no = Column('line_no', Unicode(10), nullable=True)
1050 line_no = Column('line_no', Unicode(10), nullable=True)
1041 f_path = Column('f_path', Unicode(1000), nullable=True)
1051 f_path = Column('f_path', Unicode(1000), nullable=True)
1042 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1052 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1043 text = Column('text', Unicode(25000), nullable=False)
1053 text = Column('text', Unicode(25000), nullable=False)
1044 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1054 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1045
1055
1046 author = relationship('User', lazy='joined')
1056 author = relationship('User', lazy='joined')
1047 repo = relationship('Repository')
1057 repo = relationship('Repository')
1048
1058
1049 @classmethod
1059 @classmethod
1050 def get_users(cls, revision):
1060 def get_users(cls, revision):
1051 """
1061 """
1052 Returns user associated with this changesetComment. ie those
1062 Returns user associated with this changesetComment. ie those
1053 who actually commented
1063 who actually commented
1054
1064
1055 :param cls:
1065 :param cls:
1056 :param revision:
1066 :param revision:
1057 """
1067 """
1058 return Session.query(User)\
1068 return Session.query(User)\
1059 .filter(cls.revision == revision)\
1069 .filter(cls.revision == revision)\
1060 .join(ChangesetComment.author).all()
1070 .join(ChangesetComment.author).all()
1061
1071
1062
1072
1063 class Notification(Base, BaseModel):
1073 class Notification(Base, BaseModel):
1064 __tablename__ = 'notifications'
1074 __tablename__ = 'notifications'
1065 __table_args__ = ({'extend_existing': True},)
1075 __table_args__ = ({'extend_existing': True},)
1066
1076
1067 TYPE_CHANGESET_COMMENT = u'cs_comment'
1077 TYPE_CHANGESET_COMMENT = u'cs_comment'
1068 TYPE_MESSAGE = u'message'
1078 TYPE_MESSAGE = u'message'
1069 TYPE_MENTION = u'mention'
1079 TYPE_MENTION = u'mention'
1070 TYPE_REGISTRATION = u'registration'
1080 TYPE_REGISTRATION = u'registration'
1071
1081
1072 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1082 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1073 subject = Column('subject', Unicode(512), nullable=True)
1083 subject = Column('subject', Unicode(512), nullable=True)
1074 body = Column('body', Unicode(50000), nullable=True)
1084 body = Column('body', Unicode(50000), nullable=True)
1075 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1085 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1076 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1077 type_ = Column('type', Unicode(256))
1087 type_ = Column('type', Unicode(256))
1078
1088
1079 created_by_user = relationship('User')
1089 created_by_user = relationship('User')
1080 notifications_to_users = relationship('UserNotification', lazy='joined',
1090 notifications_to_users = relationship('UserNotification', lazy='joined',
1081 cascade="all, delete, delete-orphan")
1091 cascade="all, delete, delete-orphan")
1082
1092
1083 @property
1093 @property
1084 def recipients(self):
1094 def recipients(self):
1085 return [x.user for x in UserNotification.query()\
1095 return [x.user for x in UserNotification.query()\
1086 .filter(UserNotification.notification == self).all()]
1096 .filter(UserNotification.notification == self).all()]
1087
1097
1088 @classmethod
1098 @classmethod
1089 def create(cls, created_by, subject, body, recipients, type_=None):
1099 def create(cls, created_by, subject, body, recipients, type_=None):
1090 if type_ is None:
1100 if type_ is None:
1091 type_ = Notification.TYPE_MESSAGE
1101 type_ = Notification.TYPE_MESSAGE
1092
1102
1093 notification = cls()
1103 notification = cls()
1094 notification.created_by_user = created_by
1104 notification.created_by_user = created_by
1095 notification.subject = subject
1105 notification.subject = subject
1096 notification.body = body
1106 notification.body = body
1097 notification.type_ = type_
1107 notification.type_ = type_
1098 notification.created_on = datetime.datetime.now()
1108 notification.created_on = datetime.datetime.now()
1099
1109
1100 for u in recipients:
1110 for u in recipients:
1101 assoc = UserNotification()
1111 assoc = UserNotification()
1102 assoc.notification = notification
1112 assoc.notification = notification
1103 u.notifications.append(assoc)
1113 u.notifications.append(assoc)
1104 Session.add(notification)
1114 Session.add(notification)
1105 return notification
1115 return notification
1106
1116
1107 @property
1117 @property
1108 def description(self):
1118 def description(self):
1109 from rhodecode.model.notification import NotificationModel
1119 from rhodecode.model.notification import NotificationModel
1110 return NotificationModel().make_description(self)
1120 return NotificationModel().make_description(self)
1111
1121
1112
1122
1113 class UserNotification(Base, BaseModel):
1123 class UserNotification(Base, BaseModel):
1114 __tablename__ = 'user_to_notification'
1124 __tablename__ = 'user_to_notification'
1115 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1125 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1116 {'extend_existing': True})
1126 {'extend_existing': True})
1117 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1127 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1118 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1128 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1119 read = Column('read', Boolean, default=False)
1129 read = Column('read', Boolean, default=False)
1120 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1130 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1121
1131
1122 user = relationship('User', lazy="joined")
1132 user = relationship('User', lazy="joined")
1123 notification = relationship('Notification', lazy="joined",
1133 notification = relationship('Notification', lazy="joined",
1124 order_by=lambda:Notification.created_on.desc(),)
1134 order_by=lambda:Notification.created_on.desc(),)
1125
1135
1126 def mark_as_read(self):
1136 def mark_as_read(self):
1127 self.read = True
1137 self.read = True
1128 Session.add(self)
1138 Session.add(self)
1129
1139
1130
1140
1131 class DbMigrateVersion(Base, BaseModel):
1141 class DbMigrateVersion(Base, BaseModel):
1132 __tablename__ = 'db_migrate_version'
1142 __tablename__ = 'db_migrate_version'
1133 __table_args__ = {'extend_existing': True}
1143 __table_args__ = {'extend_existing': True}
1134 repository_id = Column('repository_id', String(250), primary_key=True)
1144 repository_id = Column('repository_id', String(250), primary_key=True)
1135 repository_path = Column('repository_path', Text)
1145 repository_path = Column('repository_path', Text)
1136 version = Column('version', Integer)
1146 version = Column('version', Integer)
@@ -1,723 +1,749 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.config.routing import ADMIN_PREFIX
35 from rhodecode.config.routing import ADMIN_PREFIX
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 from rhodecode import BACKENDS
40 from rhodecode import BACKENDS
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 #this is needed to translate the messages using _() in validators
45 #this is needed to translate the messages using _() in validators
46 class State_obj(object):
46 class State_obj(object):
47 _ = staticmethod(_)
47 _ = staticmethod(_)
48
48
49
49
50 #==============================================================================
50 #==============================================================================
51 # VALIDATORS
51 # VALIDATORS
52 #==============================================================================
52 #==============================================================================
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token':_('Token mismatch')}
54 messages = {'invalid_token': _('Token mismatch')}
55
55
56 def validate_python(self, value, state):
56 def validate_python(self, value, state):
57
57
58 if value != authentication_token():
58 if value != authentication_token():
59 raise formencode.Invalid(self.message('invalid_token', state,
59 raise formencode.Invalid(
60 search_number=value), value, state)
60 self.message('invalid_token',
61 state, search_number=value),
62 value,
63 state
64 )
61
65
62
66
63 def ValidUsername(edit, old_data):
67 def ValidUsername(edit, old_data):
64 class _ValidUsername(formencode.validators.FancyValidator):
68 class _ValidUsername(formencode.validators.FancyValidator):
65
69
66 def validate_python(self, value, state):
70 def validate_python(self, value, state):
67 if value in ['default', 'new_user']:
71 if value in ['default', 'new_user']:
68 raise formencode.Invalid(_('Invalid username'), value, state)
72 raise formencode.Invalid(_('Invalid username'), value, state)
69 #check if user is unique
73 #check if user is unique
70 old_un = None
74 old_un = None
71 if edit:
75 if edit:
72 old_un = User.get(old_data.get('user_id')).username
76 old_un = User.get(old_data.get('user_id')).username
73
77
74 if old_un != value or not edit:
78 if old_un != value or not edit:
75 if User.get_by_username(value, case_insensitive=True):
79 if User.get_by_username(value, case_insensitive=True):
76 raise formencode.Invalid(_('This username already '
80 raise formencode.Invalid(_('This username already '
77 'exists') , value, state)
81 'exists') , value, state)
78
82
79 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
80 raise formencode.Invalid(_('Username may only contain '
84 raise formencode.Invalid(
81 'alphanumeric characters '
85 _('Username may only contain alphanumeric characters '
82 'underscores, periods or dashes '
86 'underscores, periods or dashes and must begin with '
83 'and must begin with alphanumeric '
87 'alphanumeric character'),
84 'character'), value, state)
88 value,
89 state
90 )
85
91
86 return _ValidUsername
92 return _ValidUsername
87
93
88
94
89 def ValidUsersGroup(edit, old_data):
95 def ValidUsersGroup(edit, old_data):
90
96
91 class _ValidUsersGroup(formencode.validators.FancyValidator):
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
92
98
93 def validate_python(self, value, state):
99 def validate_python(self, value, state):
94 if value in ['default']:
100 if value in ['default']:
95 raise formencode.Invalid(_('Invalid group name'), value, state)
101 raise formencode.Invalid(_('Invalid group name'), value, state)
96 #check if group is unique
102 #check if group is unique
97 old_ugname = None
103 old_ugname = None
98 if edit:
104 if edit:
99 old_ugname = UsersGroup.get(
105 old_ugname = UsersGroup.get(
100 old_data.get('users_group_id')).users_group_name
106 old_data.get('users_group_id')).users_group_name
101
107
102 if old_ugname != value or not edit:
108 if old_ugname != value or not edit:
103 if UsersGroup.get_by_group_name(value, cache=False,
109 if UsersGroup.get_by_group_name(value, cache=False,
104 case_insensitive=True):
110 case_insensitive=True):
105 raise formencode.Invalid(_('This users group '
111 raise formencode.Invalid(_('This users group '
106 'already exists') , value,
112 'already exists'), value,
107 state)
113 state)
108
114
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('RepoGroup name may only contain '
116 raise formencode.Invalid(
111 'alphanumeric characters '
117 _('RepoGroup name may only contain alphanumeric characters '
112 'underscores, periods or dashes '
118 'underscores, periods or dashes and must begin with '
113 'and must begin with alphanumeric '
119 'alphanumeric character'),
114 'character'), value, state)
120 value,
121 state
122 )
115
123
116 return _ValidUsersGroup
124 return _ValidUsersGroup
117
125
118
126
119 def ValidReposGroup(edit, old_data):
127 def ValidReposGroup(edit, old_data):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
121
129
122 def validate_python(self, value, state):
130 def validate_python(self, value, state):
123 # TODO WRITE VALIDATIONS
131 # TODO WRITE VALIDATIONS
124 group_name = value.get('group_name')
132 group_name = value.get('group_name')
125 group_parent_id = value.get('group_parent_id')
133 group_parent_id = value.get('group_parent_id')
126
134
127 # slugify repo group just in case :)
135 # slugify repo group just in case :)
128 slug = repo_name_slug(group_name)
136 slug = repo_name_slug(group_name)
129
137
130 # check for parent of self
138 # check for parent of self
131 parent_of_self = lambda: (
139 parent_of_self = lambda: (
132 old_data['group_id'] == int(group_parent_id)
140 old_data['group_id'] == int(group_parent_id)
133 if group_parent_id else False
141 if group_parent_id else False
134 )
142 )
135 if edit and parent_of_self():
143 if edit and parent_of_self():
136 e_dict = {
144 e_dict = {
137 'group_parent_id': _('Cannot assign this group as parent')
145 'group_parent_id': _('Cannot assign this group as parent')
138 }
146 }
139 raise formencode.Invalid('', value, state,
147 raise formencode.Invalid('', value, state,
140 error_dict=e_dict)
148 error_dict=e_dict)
141
149
142 old_gname = None
150 old_gname = None
143 if edit:
151 if edit:
144 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
145
153
146 if old_gname != group_name or not edit:
154 if old_gname != group_name or not edit:
147
155
148 # check group
156 # check group
149 gr = RepoGroup.query()\
157 gr = RepoGroup.query()\
150 .filter(RepoGroup.group_name == slug)\
158 .filter(RepoGroup.group_name == slug)\
151 .filter(RepoGroup.group_parent_id == group_parent_id)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
152 .scalar()
160 .scalar()
153
161
154 if gr:
162 if gr:
155 e_dict = {
163 e_dict = {
156 'group_name': _('This group already exists')
164 'group_name': _('This group already exists')
157 }
165 }
158 raise formencode.Invalid('', value, state,
166 raise formencode.Invalid('', value, state,
159 error_dict=e_dict)
167 error_dict=e_dict)
160
168
161 # check for same repo
169 # check for same repo
162 repo = Repository.query()\
170 repo = Repository.query()\
163 .filter(Repository.repo_name == slug)\
171 .filter(Repository.repo_name == slug)\
164 .scalar()
172 .scalar()
165
173
166 if repo:
174 if repo:
167 e_dict = {
175 e_dict = {
168 'group_name': _('Repository with this name already exists')
176 'group_name': _('Repository with this name already exists')
169 }
177 }
170 raise formencode.Invalid('', value, state,
178 raise formencode.Invalid('', value, state,
171 error_dict=e_dict)
179 error_dict=e_dict)
172
180
173 return _ValidReposGroup
181 return _ValidReposGroup
174
182
175
183
176 class ValidPassword(formencode.validators.FancyValidator):
184 class ValidPassword(formencode.validators.FancyValidator):
177
185
178 def to_python(self, value, state):
186 def to_python(self, value, state):
179
187
180 if value:
188 if not value:
189 return
181
190
182 if value.get('password'):
191 if value.get('password'):
183 try:
192 try:
184 value['password'] = get_crypt_password(value['password'])
193 value['password'] = get_crypt_password(value['password'])
185 except UnicodeEncodeError:
194 except UnicodeEncodeError:
186 e_dict = {'password':_('Invalid characters in password')}
195 e_dict = {'password': _('Invalid characters in password')}
187 raise formencode.Invalid('', value, state, error_dict=e_dict)
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
188
197
189 if value.get('password_confirmation'):
198 if value.get('password_confirmation'):
190 try:
199 try:
191 value['password_confirmation'] = \
200 value['password_confirmation'] = \
192 get_crypt_password(value['password_confirmation'])
201 get_crypt_password(value['password_confirmation'])
193 except UnicodeEncodeError:
202 except UnicodeEncodeError:
194 e_dict = {'password_confirmation':_('Invalid characters in password')}
203 e_dict = {
195 raise formencode.Invalid('', value, state, error_dict=e_dict)
204 'password_confirmation': _('Invalid characters in password')
205 }
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
196
207
197 if value.get('new_password'):
208 if value.get('new_password'):
198 try:
209 try:
199 value['new_password'] = \
210 value['new_password'] = \
200 get_crypt_password(value['new_password'])
211 get_crypt_password(value['new_password'])
201 except UnicodeEncodeError:
212 except UnicodeEncodeError:
202 e_dict = {'new_password':_('Invalid characters in password')}
213 e_dict = {'new_password': _('Invalid characters in password')}
203 raise formencode.Invalid('', value, state, error_dict=e_dict)
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
204
215
205 return value
216 return value
206
217
207
218
208 class ValidPasswordsMatch(formencode.validators.FancyValidator):
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
209
220
210 def validate_python(self, value, state):
221 def validate_python(self, value, state):
211
222
212 pass_val = value.get('password') or value.get('new_password')
223 pass_val = value.get('password') or value.get('new_password')
213 if pass_val != value['password_confirmation']:
224 if pass_val != value['password_confirmation']:
214 e_dict = {'password_confirmation':
225 e_dict = {'password_confirmation':
215 _('Passwords do not match')}
226 _('Passwords do not match')}
216 raise formencode.Invalid('', value, state, error_dict=e_dict)
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
217
228
218
229
219 class ValidAuth(formencode.validators.FancyValidator):
230 class ValidAuth(formencode.validators.FancyValidator):
220 messages = {
231 messages = {
221 'invalid_password':_('invalid password'),
232 'invalid_password':_('invalid password'),
222 'invalid_login':_('invalid user name'),
233 'invalid_login':_('invalid user name'),
223 'disabled_account':_('Your account is disabled')
234 'disabled_account':_('Your account is disabled')
224 }
235 }
225
236
226 # error mapping
237 # error mapping
227 e_dict = {'username':messages['invalid_login'],
238 e_dict = {'username': messages['invalid_login'],
228 'password':messages['invalid_password']}
239 'password': messages['invalid_password']}
229 e_dict_disable = {'username':messages['disabled_account']}
240 e_dict_disable = {'username': messages['disabled_account']}
230
241
231 def validate_python(self, value, state):
242 def validate_python(self, value, state):
232 password = value['password']
243 password = value['password']
233 username = value['username']
244 username = value['username']
234 user = User.get_by_username(username)
245 user = User.get_by_username(username)
235
246
236 if authenticate(username, password):
247 if authenticate(username, password):
237 return value
248 return value
238 else:
249 else:
239 if user and user.active is False:
250 if user and user.active is False:
240 log.warning('user %s is disabled', username)
251 log.warning('user %s is disabled', username)
241 raise formencode.Invalid(self.message('disabled_account',
252 raise formencode.Invalid(
242 state=State_obj),
253 self.message('disabled_account',
243 value, state,
254 state=State_obj),
244 error_dict=self.e_dict_disable)
255 value, state,
256 error_dict=self.e_dict_disable
257 )
245 else:
258 else:
246 log.warning('user %s not authenticated', username)
259 log.warning('user %s not authenticated', username)
247 raise formencode.Invalid(self.message('invalid_password',
260 raise formencode.Invalid(
248 state=State_obj), value, state,
261 self.message('invalid_password',
249 error_dict=self.e_dict)
262 state=State_obj), value, state,
263 error_dict=self.e_dict
264 )
250
265
251
266
252 class ValidRepoUser(formencode.validators.FancyValidator):
267 class ValidRepoUser(formencode.validators.FancyValidator):
253
268
254 def to_python(self, value, state):
269 def to_python(self, value, state):
255 try:
270 try:
256 User.query().filter(User.active == True)\
271 User.query().filter(User.active == True)\
257 .filter(User.username == value).one()
272 .filter(User.username == value).one()
258 except Exception:
273 except Exception:
259 raise formencode.Invalid(_('This username is not valid'),
274 raise formencode.Invalid(_('This username is not valid'),
260 value, state)
275 value, state)
261 return value
276 return value
262
277
263
278
264 def ValidRepoName(edit, old_data):
279 def ValidRepoName(edit, old_data):
265 class _ValidRepoName(formencode.validators.FancyValidator):
280 class _ValidRepoName(formencode.validators.FancyValidator):
266 def to_python(self, value, state):
281 def to_python(self, value, state):
267
282
268 repo_name = value.get('repo_name')
283 repo_name = value.get('repo_name')
269
284
270 slug = repo_name_slug(repo_name)
285 slug = repo_name_slug(repo_name)
271 if slug in [ADMIN_PREFIX, '']:
286 if slug in [ADMIN_PREFIX, '']:
272 e_dict = {'repo_name': _('This repository name is disallowed')}
287 e_dict = {'repo_name': _('This repository name is disallowed')}
273 raise formencode.Invalid('', value, state, error_dict=e_dict)
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
274
289
275
276 if value.get('repo_group'):
290 if value.get('repo_group'):
277 gr = RepoGroup.get(value.get('repo_group'))
291 gr = RepoGroup.get(value.get('repo_group'))
278 group_path = gr.full_path
292 group_path = gr.full_path
279 # value needs to be aware of group name in order to check
293 # value needs to be aware of group name in order to check
280 # db key This is an actual just the name to store in the
294 # db key This is an actual just the name to store in the
281 # database
295 # database
282 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
283
297
284 else:
298 else:
285 group_path = ''
299 group_path = ''
286 repo_name_full = repo_name
300 repo_name_full = repo_name
287
301
288
289 value['repo_name_full'] = repo_name_full
302 value['repo_name_full'] = repo_name_full
290 rename = old_data.get('repo_name') != repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
291 create = not edit
304 create = not edit
292 if rename or create:
305 if rename or create:
293
306
294 if group_path != '':
307 if group_path != '':
295 if Repository.get_by_repo_name(repo_name_full):
308 if Repository.get_by_repo_name(repo_name_full):
296 e_dict = {'repo_name':_('This repository already '
309 e_dict = {
297 'exists in a group "%s"') %
310 'repo_name': _('This repository already exists in '
298 gr.group_name}
311 'a group "%s"') % gr.group_name
312 }
299 raise formencode.Invalid('', value, state,
313 raise formencode.Invalid('', value, state,
300 error_dict=e_dict)
314 error_dict=e_dict)
301 elif RepoGroup.get_by_group_name(repo_name_full):
315 elif RepoGroup.get_by_group_name(repo_name_full):
302 e_dict = {'repo_name':_('There is a group with this'
316 e_dict = {
303 ' name already "%s"') %
317 'repo_name': _('There is a group with this name '
304 repo_name_full}
318 'already "%s"') % repo_name_full
319 }
305 raise formencode.Invalid('', value, state,
320 raise formencode.Invalid('', value, state,
306 error_dict=e_dict)
321 error_dict=e_dict)
307
322
308 elif Repository.get_by_repo_name(repo_name_full):
323 elif Repository.get_by_repo_name(repo_name_full):
309 e_dict = {'repo_name':_('This repository '
324 e_dict = {'repo_name': _('This repository '
310 'already exists')}
325 'already exists')}
311 raise formencode.Invalid('', value, state,
326 raise formencode.Invalid('', value, state,
312 error_dict=e_dict)
327 error_dict=e_dict)
313
328
314 return value
329 return value
315
330
316 return _ValidRepoName
331 return _ValidRepoName
317
332
318
333
319 def ValidForkName(*args, **kwargs):
334 def ValidForkName(*args, **kwargs):
320 return ValidRepoName(*args, **kwargs)
335 return ValidRepoName(*args, **kwargs)
321
336
322
337
323 def SlugifyName():
338 def SlugifyName():
324 class _SlugifyName(formencode.validators.FancyValidator):
339 class _SlugifyName(formencode.validators.FancyValidator):
325
340
326 def to_python(self, value, state):
341 def to_python(self, value, state):
327 return repo_name_slug(value)
342 return repo_name_slug(value)
328
343
329 return _SlugifyName
344 return _SlugifyName
330
345
331
346
332 def ValidCloneUri():
347 def ValidCloneUri():
333 from mercurial.httprepo import httprepository, httpsrepository
348 from mercurial.httprepo import httprepository, httpsrepository
334 from rhodecode.lib.utils import make_ui
349 from rhodecode.lib.utils import make_ui
335
350
336 class _ValidCloneUri(formencode.validators.FancyValidator):
351 class _ValidCloneUri(formencode.validators.FancyValidator):
337
352
338 def to_python(self, value, state):
353 def to_python(self, value, state):
339 if not value:
354 if not value:
340 pass
355 pass
341 elif value.startswith('https'):
356 elif value.startswith('https'):
342 try:
357 try:
343 httpsrepository(make_ui('db'), value).capabilities
358 httpsrepository(make_ui('db'), value).capabilities
344 except Exception, e:
359 except Exception:
345 log.error(traceback.format_exc())
360 log.error(traceback.format_exc())
346 raise formencode.Invalid(_('invalid clone url'), value,
361 raise formencode.Invalid(_('invalid clone url'), value,
347 state)
362 state)
348 elif value.startswith('http'):
363 elif value.startswith('http'):
349 try:
364 try:
350 httprepository(make_ui('db'), value).capabilities
365 httprepository(make_ui('db'), value).capabilities
351 except Exception, e:
366 except Exception:
352 log.error(traceback.format_exc())
367 log.error(traceback.format_exc())
353 raise formencode.Invalid(_('invalid clone url'), value,
368 raise formencode.Invalid(_('invalid clone url'), value,
354 state)
369 state)
355 else:
370 else:
356 raise formencode.Invalid(_('Invalid clone url, provide a '
371 raise formencode.Invalid(_('Invalid clone url, provide a '
357 'valid clone http\s url'), value,
372 'valid clone http\s url'), value,
358 state)
373 state)
359 return value
374 return value
360
375
361 return _ValidCloneUri
376 return _ValidCloneUri
362
377
363
378
364 def ValidForkType(old_data):
379 def ValidForkType(old_data):
365 class _ValidForkType(formencode.validators.FancyValidator):
380 class _ValidForkType(formencode.validators.FancyValidator):
366
381
367 def to_python(self, value, state):
382 def to_python(self, value, state):
368 if old_data['repo_type'] != value:
383 if old_data['repo_type'] != value:
369 raise formencode.Invalid(_('Fork have to be the same '
384 raise formencode.Invalid(_('Fork have to be the same '
370 'type as original'), value, state)
385 'type as original'), value, state)
371
386
372 return value
387 return value
373 return _ValidForkType
388 return _ValidForkType
374
389
375
390
376 class ValidPerms(formencode.validators.FancyValidator):
391 class ValidPerms(formencode.validators.FancyValidator):
377 messages = {'perm_new_member_name':_('This username or users group name'
392 messages = {'perm_new_member_name': _('This username or users group name'
378 ' is not valid')}
393 ' is not valid')}
379
394
380 def to_python(self, value, state):
395 def to_python(self, value, state):
381 perms_update = []
396 perms_update = []
382 perms_new = []
397 perms_new = []
383 #build a list of permission to update and new permission to create
398 #build a list of permission to update and new permission to create
384 for k, v in value.items():
399 for k, v in value.items():
385 #means new added member to permissions
400 #means new added member to permissions
386 if k.startswith('perm_new_member'):
401 if k.startswith('perm_new_member'):
387 new_perm = value.get('perm_new_member', False)
402 new_perm = value.get('perm_new_member', False)
388 new_member = value.get('perm_new_member_name', False)
403 new_member = value.get('perm_new_member_name', False)
389 new_type = value.get('perm_new_member_type')
404 new_type = value.get('perm_new_member_type')
390
405
391 if new_member and new_perm:
406 if new_member and new_perm:
392 if (new_member, new_perm, new_type) not in perms_new:
407 if (new_member, new_perm, new_type) not in perms_new:
393 perms_new.append((new_member, new_perm, new_type))
408 perms_new.append((new_member, new_perm, new_type))
394 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
409 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
395 member = k[7:]
410 member = k[7:]
396 t = {'u':'user',
411 t = {'u': 'user',
397 'g':'users_group'}[k[0]]
412 'g': 'users_group'
413 }[k[0]]
398 if member == 'default':
414 if member == 'default':
399 if value['private']:
415 if value['private']:
400 #set none for default when updating to private repo
416 #set none for default when updating to private repo
401 v = 'repository.none'
417 v = 'repository.none'
402 perms_update.append((member, v, t))
418 perms_update.append((member, v, t))
403
419
404 value['perms_updates'] = perms_update
420 value['perms_updates'] = perms_update
405 value['perms_new'] = perms_new
421 value['perms_new'] = perms_new
406
422
407 #update permissions
423 #update permissions
408 for k, v, t in perms_new:
424 for k, v, t in perms_new:
409 try:
425 try:
410 if t is 'user':
426 if t is 'user':
411 self.user_db = User.query()\
427 self.user_db = User.query()\
412 .filter(User.active == True)\
428 .filter(User.active == True)\
413 .filter(User.username == k).one()
429 .filter(User.username == k).one()
414 if t is 'users_group':
430 if t is 'users_group':
415 self.user_db = UsersGroup.query()\
431 self.user_db = UsersGroup.query()\
416 .filter(UsersGroup.users_group_active == True)\
432 .filter(UsersGroup.users_group_active == True)\
417 .filter(UsersGroup.users_group_name == k).one()
433 .filter(UsersGroup.users_group_name == k).one()
418
434
419 except Exception:
435 except Exception:
420 msg = self.message('perm_new_member_name',
436 msg = self.message('perm_new_member_name',
421 state=State_obj)
437 state=State_obj)
422 raise formencode.Invalid(msg, value, state,
438 raise formencode.Invalid(
423 error_dict={'perm_new_member_name':msg})
439 msg, value, state, error_dict={'perm_new_member_name': msg}
440 )
424 return value
441 return value
425
442
426
443
427 class ValidSettings(formencode.validators.FancyValidator):
444 class ValidSettings(formencode.validators.FancyValidator):
428
445
429 def to_python(self, value, state):
446 def to_python(self, value, state):
430 # settings form can't edit user
447 # settings form can't edit user
431 if value.has_key('user'):
448 if 'user' in value:
432 del['value']['user']
449 del['value']['user']
433
434 return value
450 return value
435
451
436
452
437 class ValidPath(formencode.validators.FancyValidator):
453 class ValidPath(formencode.validators.FancyValidator):
438 def to_python(self, value, state):
454 def to_python(self, value, state):
439
455
440 if not os.path.isdir(value):
456 if not os.path.isdir(value):
441 msg = _('This is not a valid path')
457 msg = _('This is not a valid path')
442 raise formencode.Invalid(msg, value, state,
458 raise formencode.Invalid(msg, value, state,
443 error_dict={'paths_root_path':msg})
459 error_dict={'paths_root_path': msg})
444 return value
460 return value
445
461
446
462
447 def UniqSystemEmail(old_data):
463 def UniqSystemEmail(old_data):
448 class _UniqSystemEmail(formencode.validators.FancyValidator):
464 class _UniqSystemEmail(formencode.validators.FancyValidator):
449 def to_python(self, value, state):
465 def to_python(self, value, state):
450 value = value.lower()
466 value = value.lower()
451 if old_data.get('email','').lower() != value:
467 if old_data.get('email', '').lower() != value:
452 user = User.get_by_email(value, case_insensitive=True)
468 user = User.get_by_email(value, case_insensitive=True)
453 if user:
469 if user:
454 raise formencode.Invalid(
470 raise formencode.Invalid(
455 _("This e-mail address is already taken"),
471 _("This e-mail address is already taken"), value, state
456 value, state)
472 )
457 return value
473 return value
458
474
459 return _UniqSystemEmail
475 return _UniqSystemEmail
460
476
461
477
462 class ValidSystemEmail(formencode.validators.FancyValidator):
478 class ValidSystemEmail(formencode.validators.FancyValidator):
463 def to_python(self, value, state):
479 def to_python(self, value, state):
464 value = value.lower()
480 value = value.lower()
465 user = User.get_by_email(value, case_insensitive=True)
481 user = User.get_by_email(value, case_insensitive=True)
466 if user is None:
482 if user is None:
467 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
483 raise formencode.Invalid(
468 value, state)
484 _("This e-mail address doesn't exist."), value, state
485 )
469
486
470 return value
487 return value
471
488
472
489
473 class LdapLibValidator(formencode.validators.FancyValidator):
490 class LdapLibValidator(formencode.validators.FancyValidator):
474
491
475 def to_python(self, value, state):
492 def to_python(self, value, state):
476
493
477 try:
494 try:
478 import ldap
495 import ldap
479 except ImportError:
496 except ImportError:
480 raise LdapImportError
497 raise LdapImportError
481 return value
498 return value
482
499
483
500
484 class AttrLoginValidator(formencode.validators.FancyValidator):
501 class AttrLoginValidator(formencode.validators.FancyValidator):
485
502
486 def to_python(self, value, state):
503 def to_python(self, value, state):
487
504
488 if not value or not isinstance(value, (str, unicode)):
505 if not value or not isinstance(value, (str, unicode)):
489 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
506 raise formencode.Invalid(
490 "must be specified - this is the name "
507 _("The LDAP Login attribute of the CN must be specified - "
491 "of the attribute that is equivalent "
508 "this is the name of the attribute that is equivalent "
492 "to 'username'"),
509 "to 'username'"), value, state
493 value, state)
510 )
494
511
495 return value
512 return value
496
513
514
497 #==============================================================================
515 #==============================================================================
498 # FORMS
516 # FORMS
499 #==============================================================================
517 #==============================================================================
500 class LoginForm(formencode.Schema):
518 class LoginForm(formencode.Schema):
501 allow_extra_fields = True
519 allow_extra_fields = True
502 filter_extra_fields = True
520 filter_extra_fields = True
503 username = UnicodeString(
521 username = UnicodeString(
504 strip=True,
522 strip=True,
505 min=1,
523 min=1,
506 not_empty=True,
524 not_empty=True,
507 messages={
525 messages={
508 'empty':_('Please enter a login'),
526 'empty': _('Please enter a login'),
509 'tooShort':_('Enter a value %(min)i characters long or more')}
527 'tooShort': _('Enter a value %(min)i characters long or more')}
510 )
528 )
511
529
512 password = UnicodeString(
530 password = UnicodeString(
513 strip=True,
531 strip=True,
514 min=3,
532 min=3,
515 not_empty=True,
533 not_empty=True,
516 messages={
534 messages={
517 'empty':_('Please enter a password'),
535 'empty': _('Please enter a password'),
518 'tooShort':_('Enter %(min)i characters or more')}
536 'tooShort': _('Enter %(min)i characters or more')}
519 )
537 )
520
538
521 remember = StringBoolean(if_missing=False)
539 remember = StringBoolean(if_missing=False)
522
540
523 chained_validators = [ValidAuth]
541 chained_validators = [ValidAuth]
524
542
525
543
526 def UserForm(edit=False, old_data={}):
544 def UserForm(edit=False, old_data={}):
527 class _UserForm(formencode.Schema):
545 class _UserForm(formencode.Schema):
528 allow_extra_fields = True
546 allow_extra_fields = True
529 filter_extra_fields = True
547 filter_extra_fields = True
530 username = All(UnicodeString(strip=True, min=1, not_empty=True),
548 username = All(UnicodeString(strip=True, min=1, not_empty=True),
531 ValidUsername(edit, old_data))
549 ValidUsername(edit, old_data))
532 if edit:
550 if edit:
533 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
551 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
534 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
552 password_confirmation = All(UnicodeString(strip=True, min=6,
553 not_empty=False))
535 admin = StringBoolean(if_missing=False)
554 admin = StringBoolean(if_missing=False)
536 else:
555 else:
537 password = All(UnicodeString(strip=True, min=6, not_empty=True))
556 password = All(UnicodeString(strip=True, min=6, not_empty=True))
538 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
557 password_confirmation = All(UnicodeString(strip=True, min=6,
558 not_empty=False))
539
559
540 active = StringBoolean(if_missing=False)
560 active = StringBoolean(if_missing=False)
541 name = UnicodeString(strip=True, min=1, not_empty=True)
561 name = UnicodeString(strip=True, min=1, not_empty=False)
542 lastname = UnicodeString(strip=True, min=1, not_empty=True)
562 lastname = UnicodeString(strip=True, min=1, not_empty=False)
543 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
563 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
544
564
545 chained_validators = [ValidPasswordsMatch, ValidPassword]
565 chained_validators = [ValidPasswordsMatch, ValidPassword]
546
566
547 return _UserForm
567 return _UserForm
548
568
549
569
550 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
570 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
551 class _UsersGroupForm(formencode.Schema):
571 class _UsersGroupForm(formencode.Schema):
552 allow_extra_fields = True
572 allow_extra_fields = True
553 filter_extra_fields = True
573 filter_extra_fields = True
554
574
555 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
575 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
556 ValidUsersGroup(edit, old_data))
576 ValidUsersGroup(edit, old_data))
557
577
558 users_group_active = StringBoolean(if_missing=False)
578 users_group_active = StringBoolean(if_missing=False)
559
579
560 if edit:
580 if edit:
561 users_group_members = OneOf(available_members, hideList=False,
581 users_group_members = OneOf(available_members, hideList=False,
562 testValueList=True,
582 testValueList=True,
563 if_missing=None, not_empty=False)
583 if_missing=None, not_empty=False)
564
584
565 return _UsersGroupForm
585 return _UsersGroupForm
566
586
567
587
568 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
588 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
569 class _ReposGroupForm(formencode.Schema):
589 class _ReposGroupForm(formencode.Schema):
570 allow_extra_fields = True
590 allow_extra_fields = True
571 filter_extra_fields = True
591 filter_extra_fields = True
572
592
573 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
593 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
574 SlugifyName())
594 SlugifyName())
575 group_description = UnicodeString(strip=True, min=1,
595 group_description = UnicodeString(strip=True, min=1,
576 not_empty=True)
596 not_empty=True)
577 group_parent_id = OneOf(available_groups, hideList=False,
597 group_parent_id = OneOf(available_groups, hideList=False,
578 testValueList=True,
598 testValueList=True,
579 if_missing=None, not_empty=False)
599 if_missing=None, not_empty=False)
580
600
581 chained_validators = [ValidReposGroup(edit, old_data)]
601 chained_validators = [ValidReposGroup(edit, old_data)]
582
602
583 return _ReposGroupForm
603 return _ReposGroupForm
584
604
585
605
586 def RegisterForm(edit=False, old_data={}):
606 def RegisterForm(edit=False, old_data={}):
587 class _RegisterForm(formencode.Schema):
607 class _RegisterForm(formencode.Schema):
588 allow_extra_fields = True
608 allow_extra_fields = True
589 filter_extra_fields = True
609 filter_extra_fields = True
590 username = All(ValidUsername(edit, old_data),
610 username = All(ValidUsername(edit, old_data),
591 UnicodeString(strip=True, min=1, not_empty=True))
611 UnicodeString(strip=True, min=1, not_empty=True))
592 password = All(UnicodeString(strip=True, min=6, not_empty=True))
612 password = All(UnicodeString(strip=True, min=6, not_empty=True))
593 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
613 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
594 active = StringBoolean(if_missing=False)
614 active = StringBoolean(if_missing=False)
595 name = UnicodeString(strip=True, min=1, not_empty=True)
615 name = UnicodeString(strip=True, min=1, not_empty=False)
596 lastname = UnicodeString(strip=True, min=1, not_empty=True)
616 lastname = UnicodeString(strip=True, min=1, not_empty=False)
597 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
617 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
598
618
599 chained_validators = [ValidPasswordsMatch, ValidPassword]
619 chained_validators = [ValidPasswordsMatch, ValidPassword]
600
620
601 return _RegisterForm
621 return _RegisterForm
602
622
623
603 def PasswordResetForm():
624 def PasswordResetForm():
604 class _PasswordResetForm(formencode.Schema):
625 class _PasswordResetForm(formencode.Schema):
605 allow_extra_fields = True
626 allow_extra_fields = True
606 filter_extra_fields = True
627 filter_extra_fields = True
607 email = All(ValidSystemEmail(), Email(not_empty=True))
628 email = All(ValidSystemEmail(), Email(not_empty=True))
608 return _PasswordResetForm
629 return _PasswordResetForm
609
630
631
610 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
632 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
611 repo_groups=[]):
633 repo_groups=[]):
612 class _RepoForm(formencode.Schema):
634 class _RepoForm(formencode.Schema):
613 allow_extra_fields = True
635 allow_extra_fields = True
614 filter_extra_fields = False
636 filter_extra_fields = False
615 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
637 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
616 SlugifyName())
638 SlugifyName())
617 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
639 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
618 ValidCloneUri()())
640 ValidCloneUri()())
619 repo_group = OneOf(repo_groups, hideList=True)
641 repo_group = OneOf(repo_groups, hideList=True)
620 repo_type = OneOf(supported_backends)
642 repo_type = OneOf(supported_backends)
621 description = UnicodeString(strip=True, min=1, not_empty=True)
643 description = UnicodeString(strip=True, min=1, not_empty=True)
622 private = StringBoolean(if_missing=False)
644 private = StringBoolean(if_missing=False)
623 enable_statistics = StringBoolean(if_missing=False)
645 enable_statistics = StringBoolean(if_missing=False)
624 enable_downloads = StringBoolean(if_missing=False)
646 enable_downloads = StringBoolean(if_missing=False)
625
647
626 if edit:
648 if edit:
627 #this is repo owner
649 #this is repo owner
628 user = All(UnicodeString(not_empty=True), ValidRepoUser)
650 user = All(UnicodeString(not_empty=True), ValidRepoUser)
629
651
630 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
652 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
631 return _RepoForm
653 return _RepoForm
632
654
655
633 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
656 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
634 repo_groups=[]):
657 repo_groups=[]):
635 class _RepoForkForm(formencode.Schema):
658 class _RepoForkForm(formencode.Schema):
636 allow_extra_fields = True
659 allow_extra_fields = True
637 filter_extra_fields = False
660 filter_extra_fields = False
638 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
661 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
639 SlugifyName())
662 SlugifyName())
640 repo_group = OneOf(repo_groups, hideList=True)
663 repo_group = OneOf(repo_groups, hideList=True)
641 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
664 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
642 description = UnicodeString(strip=True, min=1, not_empty=True)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
643 private = StringBoolean(if_missing=False)
666 private = StringBoolean(if_missing=False)
644 copy_permissions = StringBoolean(if_missing=False)
667 copy_permissions = StringBoolean(if_missing=False)
645 update_after_clone = StringBoolean(if_missing=False)
668 update_after_clone = StringBoolean(if_missing=False)
646 fork_parent_id = UnicodeString()
669 fork_parent_id = UnicodeString()
647 chained_validators = [ValidForkName(edit, old_data)]
670 chained_validators = [ValidForkName(edit, old_data)]
648
671
649 return _RepoForkForm
672 return _RepoForkForm
650
673
674
651 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
675 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
652 repo_groups=[]):
676 repo_groups=[]):
653 class _RepoForm(formencode.Schema):
677 class _RepoForm(formencode.Schema):
654 allow_extra_fields = True
678 allow_extra_fields = True
655 filter_extra_fields = False
679 filter_extra_fields = False
656 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
680 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
657 SlugifyName())
681 SlugifyName())
658 description = UnicodeString(strip=True, min=1, not_empty=True)
682 description = UnicodeString(strip=True, min=1, not_empty=True)
659 repo_group = OneOf(repo_groups, hideList=True)
683 repo_group = OneOf(repo_groups, hideList=True)
660 private = StringBoolean(if_missing=False)
684 private = StringBoolean(if_missing=False)
661
685
662 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
686 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
663 ValidSettings]
687 ValidSettings]
664 return _RepoForm
688 return _RepoForm
665
689
666
690
667 def ApplicationSettingsForm():
691 def ApplicationSettingsForm():
668 class _ApplicationSettingsForm(formencode.Schema):
692 class _ApplicationSettingsForm(formencode.Schema):
669 allow_extra_fields = True
693 allow_extra_fields = True
670 filter_extra_fields = False
694 filter_extra_fields = False
671 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
695 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
672 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
696 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
673 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
697 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
674
698
675 return _ApplicationSettingsForm
699 return _ApplicationSettingsForm
676
700
701
677 def ApplicationUiSettingsForm():
702 def ApplicationUiSettingsForm():
678 class _ApplicationUiSettingsForm(formencode.Schema):
703 class _ApplicationUiSettingsForm(formencode.Schema):
679 allow_extra_fields = True
704 allow_extra_fields = True
680 filter_extra_fields = False
705 filter_extra_fields = False
681 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
706 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
682 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
707 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
683 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
708 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
684 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
709 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
685 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
710 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
686 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
711 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
687
712
688 return _ApplicationUiSettingsForm
713 return _ApplicationUiSettingsForm
689
714
715
690 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
716 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
691 class _DefaultPermissionsForm(formencode.Schema):
717 class _DefaultPermissionsForm(formencode.Schema):
692 allow_extra_fields = True
718 allow_extra_fields = True
693 filter_extra_fields = True
719 filter_extra_fields = True
694 overwrite_default = StringBoolean(if_missing=False)
720 overwrite_default = StringBoolean(if_missing=False)
695 anonymous = OneOf(['True', 'False'], if_missing=False)
721 anonymous = OneOf(['True', 'False'], if_missing=False)
696 default_perm = OneOf(perms_choices)
722 default_perm = OneOf(perms_choices)
697 default_register = OneOf(register_choices)
723 default_register = OneOf(register_choices)
698 default_create = OneOf(create_choices)
724 default_create = OneOf(create_choices)
699
725
700 return _DefaultPermissionsForm
726 return _DefaultPermissionsForm
701
727
702
728
703 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
729 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
704 class _LdapSettingsForm(formencode.Schema):
730 class _LdapSettingsForm(formencode.Schema):
705 allow_extra_fields = True
731 allow_extra_fields = True
706 filter_extra_fields = True
732 filter_extra_fields = True
707 pre_validators = [LdapLibValidator]
733 pre_validators = [LdapLibValidator]
708 ldap_active = StringBoolean(if_missing=False)
734 ldap_active = StringBoolean(if_missing=False)
709 ldap_host = UnicodeString(strip=True,)
735 ldap_host = UnicodeString(strip=True,)
710 ldap_port = Number(strip=True,)
736 ldap_port = Number(strip=True,)
711 ldap_tls_kind = OneOf(tls_kind_choices)
737 ldap_tls_kind = OneOf(tls_kind_choices)
712 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
738 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
713 ldap_dn_user = UnicodeString(strip=True,)
739 ldap_dn_user = UnicodeString(strip=True,)
714 ldap_dn_pass = UnicodeString(strip=True,)
740 ldap_dn_pass = UnicodeString(strip=True,)
715 ldap_base_dn = UnicodeString(strip=True,)
741 ldap_base_dn = UnicodeString(strip=True,)
716 ldap_filter = UnicodeString(strip=True,)
742 ldap_filter = UnicodeString(strip=True,)
717 ldap_search_scope = OneOf(search_scope_choices)
743 ldap_search_scope = OneOf(search_scope_choices)
718 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
744 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
719 ldap_attr_firstname = UnicodeString(strip=True,)
745 ldap_attr_firstname = UnicodeString(strip=True,)
720 ldap_attr_lastname = UnicodeString(strip=True,)
746 ldap_attr_lastname = UnicodeString(strip=True,)
721 ldap_attr_email = UnicodeString(strip=True,)
747 ldap_attr_email = UnicodeString(strip=True,)
722
748
723 return _LdapSettingsForm
749 return _LdapSettingsForm
@@ -1,507 +1,504 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import url
29 from pylons import url
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.lib import safe_unicode
32 from rhodecode.lib import safe_unicode
33 from rhodecode.lib.caching_query import FromCache
33 from rhodecode.lib.caching_query import FromCache
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification
38 Notification
39 from rhodecode.lib.exceptions import DefaultUserException, \
39 from rhodecode.lib.exceptions import DefaultUserException, \
40 UserOwnsReposException
40 UserOwnsReposException
41
41
42 from sqlalchemy.exc import DatabaseError
42 from sqlalchemy.exc import DatabaseError
43 from rhodecode.lib import generate_api_key
43 from rhodecode.lib import generate_api_key
44 from sqlalchemy.orm import joinedload
44 from sqlalchemy.orm import joinedload
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 PERM_WEIGHTS = {'repository.none': 0,
49 PERM_WEIGHTS = {'repository.none': 0,
50 'repository.read': 1,
50 'repository.read': 1,
51 'repository.write': 3,
51 'repository.write': 3,
52 'repository.admin': 3}
52 'repository.admin': 3}
53
53
54
54
55 class UserModel(BaseModel):
55 class UserModel(BaseModel):
56
56
57 def __get_user(self, user):
57 def __get_user(self, user):
58 return self._get_instance(User, user)
58 return self._get_instance(User, user)
59
59
60 def get(self, user_id, cache=False):
60 def get(self, user_id, cache=False):
61 user = self.sa.query(User)
61 user = self.sa.query(User)
62 if cache:
62 if cache:
63 user = user.options(FromCache("sql_cache_short",
63 user = user.options(FromCache("sql_cache_short",
64 "get_user_%s" % user_id))
64 "get_user_%s" % user_id))
65 return user.get(user_id)
65 return user.get(user_id)
66
66
67 def get_by_username(self, username, cache=False, case_insensitive=False):
67 def get_by_username(self, username, cache=False, case_insensitive=False):
68
68
69 if case_insensitive:
69 if case_insensitive:
70 user = self.sa.query(User).filter(User.username.ilike(username))
70 user = self.sa.query(User).filter(User.username.ilike(username))
71 else:
71 else:
72 user = self.sa.query(User)\
72 user = self.sa.query(User)\
73 .filter(User.username == username)
73 .filter(User.username == username)
74 if cache:
74 if cache:
75 user = user.options(FromCache("sql_cache_short",
75 user = user.options(FromCache("sql_cache_short",
76 "get_user_%s" % username))
76 "get_user_%s" % username))
77 return user.scalar()
77 return user.scalar()
78
78
79 def get_by_api_key(self, api_key, cache=False):
79 def get_by_api_key(self, api_key, cache=False):
80 return User.get_by_api_key(api_key, cache)
80 return User.get_by_api_key(api_key, cache)
81
81
82 def create(self, form_data):
82 def create(self, form_data):
83 try:
83 try:
84 new_user = User()
84 new_user = User()
85 for k, v in form_data.items():
85 for k, v in form_data.items():
86 setattr(new_user, k, v)
86 setattr(new_user, k, v)
87
87
88 new_user.api_key = generate_api_key(form_data['username'])
88 new_user.api_key = generate_api_key(form_data['username'])
89 self.sa.add(new_user)
89 self.sa.add(new_user)
90 return new_user
90 return new_user
91 except:
91 except:
92 log.error(traceback.format_exc())
92 log.error(traceback.format_exc())
93 raise
93 raise
94
94
95
96 def create_or_update(self, username, password, email, name, lastname,
95 def create_or_update(self, username, password, email, name, lastname,
97 active=True, admin=False, ldap_dn=None):
96 active=True, admin=False, ldap_dn=None):
98 """
97 """
99 Creates a new instance if not found, or updates current one
98 Creates a new instance if not found, or updates current one
100
99
101 :param username:
100 :param username:
102 :param password:
101 :param password:
103 :param email:
102 :param email:
104 :param active:
103 :param active:
105 :param name:
104 :param name:
106 :param lastname:
105 :param lastname:
107 :param active:
106 :param active:
108 :param admin:
107 :param admin:
109 :param ldap_dn:
108 :param ldap_dn:
110 """
109 """
111
110
112 from rhodecode.lib.auth import get_crypt_password
111 from rhodecode.lib.auth import get_crypt_password
113
112
114 log.debug('Checking for %s account in RhodeCode database', username)
113 log.debug('Checking for %s account in RhodeCode database', username)
115 user = User.get_by_username(username, case_insensitive=True)
114 user = User.get_by_username(username, case_insensitive=True)
116 if user is None:
115 if user is None:
117 log.debug('creating new user %s', username)
116 log.debug('creating new user %s', username)
118 new_user = User()
117 new_user = User()
119 else:
118 else:
120 log.debug('updating user %s', username)
119 log.debug('updating user %s', username)
121 new_user = user
120 new_user = user
122
121
123 try:
122 try:
124 new_user.username = username
123 new_user.username = username
125 new_user.admin = admin
124 new_user.admin = admin
126 new_user.password = get_crypt_password(password)
125 new_user.password = get_crypt_password(password)
127 new_user.api_key = generate_api_key(username)
126 new_user.api_key = generate_api_key(username)
128 new_user.email = email
127 new_user.email = email
129 new_user.active = active
128 new_user.active = active
130 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
129 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
131 new_user.name = name
130 new_user.name = name
132 new_user.lastname = lastname
131 new_user.lastname = lastname
133 self.sa.add(new_user)
132 self.sa.add(new_user)
134 return new_user
133 return new_user
135 except (DatabaseError,):
134 except (DatabaseError,):
136 log.error(traceback.format_exc())
135 log.error(traceback.format_exc())
137 raise
136 raise
138
137
139
140 def create_for_container_auth(self, username, attrs):
138 def create_for_container_auth(self, username, attrs):
141 """
139 """
142 Creates the given user if it's not already in the database
140 Creates the given user if it's not already in the database
143
141
144 :param username:
142 :param username:
145 :param attrs:
143 :param attrs:
146 """
144 """
147 if self.get_by_username(username, case_insensitive=True) is None:
145 if self.get_by_username(username, case_insensitive=True) is None:
148
146
149 # autogenerate email for container account without one
147 # autogenerate email for container account without one
150 generate_email = lambda usr: '%s@container_auth.account' % usr
148 generate_email = lambda usr: '%s@container_auth.account' % usr
151
149
152 try:
150 try:
153 new_user = User()
151 new_user = User()
154 new_user.username = username
152 new_user.username = username
155 new_user.password = None
153 new_user.password = None
156 new_user.api_key = generate_api_key(username)
154 new_user.api_key = generate_api_key(username)
157 new_user.email = attrs['email']
155 new_user.email = attrs['email']
158 new_user.active = attrs.get('active', True)
156 new_user.active = attrs.get('active', True)
159 new_user.name = attrs['name'] or generate_email(username)
157 new_user.name = attrs['name'] or generate_email(username)
160 new_user.lastname = attrs['lastname']
158 new_user.lastname = attrs['lastname']
161
159
162 self.sa.add(new_user)
160 self.sa.add(new_user)
163 return new_user
161 return new_user
164 except (DatabaseError,):
162 except (DatabaseError,):
165 log.error(traceback.format_exc())
163 log.error(traceback.format_exc())
166 self.sa.rollback()
164 self.sa.rollback()
167 raise
165 raise
168 log.debug('User %s already exists. Skipping creation of account'
166 log.debug('User %s already exists. Skipping creation of account'
169 ' for container auth.', username)
167 ' for container auth.', username)
170 return None
168 return None
171
169
172 def create_ldap(self, username, password, user_dn, attrs):
170 def create_ldap(self, username, password, user_dn, attrs):
173 """
171 """
174 Checks if user is in database, if not creates this user marked
172 Checks if user is in database, if not creates this user marked
175 as ldap user
173 as ldap user
176
174
177 :param username:
175 :param username:
178 :param password:
176 :param password:
179 :param user_dn:
177 :param user_dn:
180 :param attrs:
178 :param attrs:
181 """
179 """
182 from rhodecode.lib.auth import get_crypt_password
180 from rhodecode.lib.auth import get_crypt_password
183 log.debug('Checking for such ldap account in RhodeCode database')
181 log.debug('Checking for such ldap account in RhodeCode database')
184 if self.get_by_username(username, case_insensitive=True) is None:
182 if self.get_by_username(username, case_insensitive=True) is None:
185
183
186 # autogenerate email for ldap account without one
184 # autogenerate email for ldap account without one
187 generate_email = lambda usr: '%s@ldap.account' % usr
185 generate_email = lambda usr: '%s@ldap.account' % usr
188
186
189 try:
187 try:
190 new_user = User()
188 new_user = User()
191 username = username.lower()
189 username = username.lower()
192 # add ldap account always lowercase
190 # add ldap account always lowercase
193 new_user.username = username
191 new_user.username = username
194 new_user.password = get_crypt_password(password)
192 new_user.password = get_crypt_password(password)
195 new_user.api_key = generate_api_key(username)
193 new_user.api_key = generate_api_key(username)
196 new_user.email = attrs['email'] or generate_email(username)
194 new_user.email = attrs['email'] or generate_email(username)
197 new_user.active = attrs.get('active', True)
195 new_user.active = attrs.get('active', True)
198 new_user.ldap_dn = safe_unicode(user_dn)
196 new_user.ldap_dn = safe_unicode(user_dn)
199 new_user.name = attrs['name']
197 new_user.name = attrs['name']
200 new_user.lastname = attrs['lastname']
198 new_user.lastname = attrs['lastname']
201
199
202 self.sa.add(new_user)
200 self.sa.add(new_user)
203 return new_user
201 return new_user
204 except (DatabaseError,):
202 except (DatabaseError,):
205 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
206 self.sa.rollback()
204 self.sa.rollback()
207 raise
205 raise
208 log.debug('this %s user exists skipping creation of ldap account',
206 log.debug('this %s user exists skipping creation of ldap account',
209 username)
207 username)
210 return None
208 return None
211
209
212 def create_registration(self, form_data):
210 def create_registration(self, form_data):
213 from rhodecode.model.notification import NotificationModel
211 from rhodecode.model.notification import NotificationModel
214
212
215 try:
213 try:
216 new_user = User()
214 new_user = User()
217 for k, v in form_data.items():
215 for k, v in form_data.items():
218 if k != 'admin':
216 if k != 'admin':
219 setattr(new_user, k, v)
217 setattr(new_user, k, v)
220
218
221 self.sa.add(new_user)
219 self.sa.add(new_user)
222 self.sa.flush()
220 self.sa.flush()
223
221
224 # notification to admins
222 # notification to admins
225 subject = _('new user registration')
223 subject = _('new user registration')
226 body = ('New user registration\n'
224 body = ('New user registration\n'
227 '---------------------\n'
225 '---------------------\n'
228 '- Username: %s\n'
226 '- Username: %s\n'
229 '- Full Name: %s\n'
227 '- Full Name: %s\n'
230 '- Email: %s\n')
228 '- Email: %s\n')
231 body = body % (new_user.username, new_user.full_name,
229 body = body % (new_user.username, new_user.full_name,
232 new_user.email)
230 new_user.email)
233 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
231 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
234 kw = {'registered_user_url':edit_url}
232 kw = {'registered_user_url': edit_url}
235 NotificationModel().create(created_by=new_user, subject=subject,
233 NotificationModel().create(created_by=new_user, subject=subject,
236 body=body, recipients=None,
234 body=body, recipients=None,
237 type_=Notification.TYPE_REGISTRATION,
235 type_=Notification.TYPE_REGISTRATION,
238 email_kwargs=kw)
236 email_kwargs=kw)
239
237
240 except:
238 except:
241 log.error(traceback.format_exc())
239 log.error(traceback.format_exc())
242 raise
240 raise
243
241
244 def update(self, user_id, form_data):
242 def update(self, user_id, form_data):
245 try:
243 try:
246 user = self.get(user_id, cache=False)
244 user = self.get(user_id, cache=False)
247 if user.username == 'default':
245 if user.username == 'default':
248 raise DefaultUserException(
246 raise DefaultUserException(
249 _("You can't Edit this user since it's"
247 _("You can't Edit this user since it's"
250 " crucial for entire application"))
248 " crucial for entire application"))
251
249
252 for k, v in form_data.items():
250 for k, v in form_data.items():
253 if k == 'new_password' and v != '':
251 if k == 'new_password' and v != '':
254 user.password = v
252 user.password = v
255 user.api_key = generate_api_key(user.username)
253 user.api_key = generate_api_key(user.username)
256 else:
254 else:
257 setattr(user, k, v)
255 setattr(user, k, v)
258
256
259 self.sa.add(user)
257 self.sa.add(user)
260 except:
258 except:
261 log.error(traceback.format_exc())
259 log.error(traceback.format_exc())
262 raise
260 raise
263
261
264 def update_my_account(self, user_id, form_data):
262 def update_my_account(self, user_id, form_data):
265 try:
263 try:
266 user = self.get(user_id, cache=False)
264 user = self.get(user_id, cache=False)
267 if user.username == 'default':
265 if user.username == 'default':
268 raise DefaultUserException(
266 raise DefaultUserException(
269 _("You can't Edit this user since it's"
267 _("You can't Edit this user since it's"
270 " crucial for entire application"))
268 " crucial for entire application"))
271 for k, v in form_data.items():
269 for k, v in form_data.items():
272 if k == 'new_password' and v != '':
270 if k == 'new_password' and v != '':
273 user.password = v
271 user.password = v
274 user.api_key = generate_api_key(user.username)
272 user.api_key = generate_api_key(user.username)
275 else:
273 else:
276 if k not in ['admin', 'active']:
274 if k not in ['admin', 'active']:
277 setattr(user, k, v)
275 setattr(user, k, v)
278
276
279 self.sa.add(user)
277 self.sa.add(user)
280 except:
278 except:
281 log.error(traceback.format_exc())
279 log.error(traceback.format_exc())
282 raise
280 raise
283
281
284 def delete(self, user):
282 def delete(self, user):
285 user = self.__get_user(user)
283 user = self.__get_user(user)
286
284
287 try:
285 try:
288 if user.username == 'default':
286 if user.username == 'default':
289 raise DefaultUserException(
287 raise DefaultUserException(
290 _("You can't remove this user since it's"
288 _("You can't remove this user since it's"
291 " crucial for entire application"))
289 " crucial for entire application"))
292 if user.repositories:
290 if user.repositories:
293 raise UserOwnsReposException(_('This user still owns %s '
291 raise UserOwnsReposException(_('This user still owns %s '
294 'repositories and cannot be '
292 'repositories and cannot be '
295 'removed. Switch owners or '
293 'removed. Switch owners or '
296 'remove those repositories') \
294 'remove those repositories') \
297 % user.repositories)
295 % user.repositories)
298 self.sa.delete(user)
296 self.sa.delete(user)
299 except:
297 except:
300 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
301 raise
299 raise
302
300
303 def reset_password_link(self, data):
301 def reset_password_link(self, data):
304 from rhodecode.lib.celerylib import tasks, run_task
302 from rhodecode.lib.celerylib import tasks, run_task
305 run_task(tasks.send_password_link, data['email'])
303 run_task(tasks.send_password_link, data['email'])
306
304
307 def reset_password(self, data):
305 def reset_password(self, data):
308 from rhodecode.lib.celerylib import tasks, run_task
306 from rhodecode.lib.celerylib import tasks, run_task
309 run_task(tasks.reset_user_password, data['email'])
307 run_task(tasks.reset_user_password, data['email'])
310
308
311 def fill_data(self, auth_user, user_id=None, api_key=None):
309 def fill_data(self, auth_user, user_id=None, api_key=None):
312 """
310 """
313 Fetches auth_user by user_id,or api_key if present.
311 Fetches auth_user by user_id,or api_key if present.
314 Fills auth_user attributes with those taken from database.
312 Fills auth_user attributes with those taken from database.
315 Additionally set's is_authenitated if lookup fails
313 Additionally set's is_authenitated if lookup fails
316 present in database
314 present in database
317
315
318 :param auth_user: instance of user to set attributes
316 :param auth_user: instance of user to set attributes
319 :param user_id: user id to fetch by
317 :param user_id: user id to fetch by
320 :param api_key: api key to fetch by
318 :param api_key: api key to fetch by
321 """
319 """
322 if user_id is None and api_key is None:
320 if user_id is None and api_key is None:
323 raise Exception('You need to pass user_id or api_key')
321 raise Exception('You need to pass user_id or api_key')
324
322
325 try:
323 try:
326 if api_key:
324 if api_key:
327 dbuser = self.get_by_api_key(api_key)
325 dbuser = self.get_by_api_key(api_key)
328 else:
326 else:
329 dbuser = self.get(user_id)
327 dbuser = self.get(user_id)
330
328
331 if dbuser is not None and dbuser.active:
329 if dbuser is not None and dbuser.active:
332 log.debug('filling %s data', dbuser)
330 log.debug('filling %s data', dbuser)
333 for k, v in dbuser.get_dict().items():
331 for k, v in dbuser.get_dict().items():
334 setattr(auth_user, k, v)
332 setattr(auth_user, k, v)
335 else:
333 else:
336 return False
334 return False
337
335
338 except:
336 except:
339 log.error(traceback.format_exc())
337 log.error(traceback.format_exc())
340 auth_user.is_authenticated = False
338 auth_user.is_authenticated = False
341 return False
339 return False
342
340
343 return True
341 return True
344
342
345 def fill_perms(self, user):
343 def fill_perms(self, user):
346 """
344 """
347 Fills user permission attribute with permissions taken from database
345 Fills user permission attribute with permissions taken from database
348 works for permissions given for repositories, and for permissions that
346 works for permissions given for repositories, and for permissions that
349 are granted to groups
347 are granted to groups
350
348
351 :param user: user instance to fill his perms
349 :param user: user instance to fill his perms
352 """
350 """
353
351
354 user.permissions['repositories'] = {}
352 user.permissions['repositories'] = {}
355 user.permissions['global'] = set()
353 user.permissions['global'] = set()
356
354
357 #======================================================================
355 #======================================================================
358 # fetch default permissions
356 # fetch default permissions
359 #======================================================================
357 #======================================================================
360 default_user = User.get_by_username('default', cache=True)
358 default_user = User.get_by_username('default', cache=True)
361 default_user_id = default_user.user_id
359 default_user_id = default_user.user_id
362
360
363 default_perms = Permission.get_default_perms(default_user_id)
361 default_perms = Permission.get_default_perms(default_user_id)
364
362
365 if user.is_admin:
363 if user.is_admin:
366 #==================================================================
364 #==================================================================
367 # #admin have all default rights set to admin
365 # #admin have all default rights set to admin
368 #==================================================================
366 #==================================================================
369 user.permissions['global'].add('hg.admin')
367 user.permissions['global'].add('hg.admin')
370
368
371 for perm in default_perms:
369 for perm in default_perms:
372 p = 'repository.admin'
370 p = 'repository.admin'
373 user.permissions['repositories'][perm.UserRepoToPerm.
371 user.permissions['repositories'][perm.UserRepoToPerm.
374 repository.repo_name] = p
372 repository.repo_name] = p
375
373
376 else:
374 else:
377 #==================================================================
375 #==================================================================
378 # set default permissions
376 # set default permissions
379 #==================================================================
377 #==================================================================
380 uid = user.user_id
378 uid = user.user_id
381
379
382 # default global
380 # default global
383 default_global_perms = self.sa.query(UserToPerm)\
381 default_global_perms = self.sa.query(UserToPerm)\
384 .filter(UserToPerm.user_id == default_user_id)
382 .filter(UserToPerm.user_id == default_user_id)
385
383
386 for perm in default_global_perms:
384 for perm in default_global_perms:
387 user.permissions['global'].add(perm.permission.permission_name)
385 user.permissions['global'].add(perm.permission.permission_name)
388
386
389 # default for repositories
387 # default for repositories
390 for perm in default_perms:
388 for perm in default_perms:
391 if perm.Repository.private and not (perm.Repository.user_id ==
389 if perm.Repository.private and not (perm.Repository.user_id ==
392 uid):
390 uid):
393 # disable defaults for private repos,
391 # disable defaults for private repos,
394 p = 'repository.none'
392 p = 'repository.none'
395 elif perm.Repository.user_id == uid:
393 elif perm.Repository.user_id == uid:
396 # set admin if owner
394 # set admin if owner
397 p = 'repository.admin'
395 p = 'repository.admin'
398 else:
396 else:
399 p = perm.Permission.permission_name
397 p = perm.Permission.permission_name
400
398
401 user.permissions['repositories'][perm.UserRepoToPerm.
399 user.permissions['repositories'][perm.UserRepoToPerm.
402 repository.repo_name] = p
400 repository.repo_name] = p
403
401
404 #==================================================================
402 #==================================================================
405 # overwrite default with user permissions if any
403 # overwrite default with user permissions if any
406 #==================================================================
404 #==================================================================
407
405
408 # user global
406 # user global
409 user_perms = self.sa.query(UserToPerm)\
407 user_perms = self.sa.query(UserToPerm)\
410 .options(joinedload(UserToPerm.permission))\
408 .options(joinedload(UserToPerm.permission))\
411 .filter(UserToPerm.user_id == uid).all()
409 .filter(UserToPerm.user_id == uid).all()
412
410
413 for perm in user_perms:
411 for perm in user_perms:
414 user.permissions['global'].add(perm.permission.permission_name)
412 user.permissions['global'].add(perm.permission.permission_name)
415
413
416 # user repositories
414 # user repositories
417 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
415 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
418 Repository)\
416 Repository)\
419 .join((Repository, UserRepoToPerm.repository_id ==
417 .join((Repository, UserRepoToPerm.repository_id ==
420 Repository.repo_id))\
418 Repository.repo_id))\
421 .join((Permission, UserRepoToPerm.permission_id ==
419 .join((Permission, UserRepoToPerm.permission_id ==
422 Permission.permission_id))\
420 Permission.permission_id))\
423 .filter(UserRepoToPerm.user_id == uid).all()
421 .filter(UserRepoToPerm.user_id == uid).all()
424
422
425 for perm in user_repo_perms:
423 for perm in user_repo_perms:
426 # set admin if owner
424 # set admin if owner
427 if perm.Repository.user_id == uid:
425 if perm.Repository.user_id == uid:
428 p = 'repository.admin'
426 p = 'repository.admin'
429 else:
427 else:
430 p = perm.Permission.permission_name
428 p = perm.Permission.permission_name
431 user.permissions['repositories'][perm.UserRepoToPerm.
429 user.permissions['repositories'][perm.UserRepoToPerm.
432 repository.repo_name] = p
430 repository.repo_name] = p
433
431
434 #==================================================================
432 #==================================================================
435 # check if user is part of groups for this repository and fill in
433 # check if user is part of groups for this repository and fill in
436 # (or replace with higher) permissions
434 # (or replace with higher) permissions
437 #==================================================================
435 #==================================================================
438
436
439 # users group global
437 # users group global
440 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
438 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
441 .options(joinedload(UsersGroupToPerm.permission))\
439 .options(joinedload(UsersGroupToPerm.permission))\
442 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
440 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
443 UsersGroupMember.users_group_id))\
441 UsersGroupMember.users_group_id))\
444 .filter(UsersGroupMember.user_id == uid).all()
442 .filter(UsersGroupMember.user_id == uid).all()
445
443
446 for perm in user_perms_from_users_groups:
444 for perm in user_perms_from_users_groups:
447 user.permissions['global'].add(perm.permission.permission_name)
445 user.permissions['global'].add(perm.permission.permission_name)
448
446
449 # users group repositories
447 # users group repositories
450 user_repo_perms_from_users_groups = self.sa.query(
448 user_repo_perms_from_users_groups = self.sa.query(
451 UsersGroupRepoToPerm,
449 UsersGroupRepoToPerm,
452 Permission, Repository,)\
450 Permission, Repository,)\
453 .join((Repository, UsersGroupRepoToPerm.repository_id ==
451 .join((Repository, UsersGroupRepoToPerm.repository_id ==
454 Repository.repo_id))\
452 Repository.repo_id))\
455 .join((Permission, UsersGroupRepoToPerm.permission_id ==
453 .join((Permission, UsersGroupRepoToPerm.permission_id ==
456 Permission.permission_id))\
454 Permission.permission_id))\
457 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
455 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
458 UsersGroupMember.users_group_id))\
456 UsersGroupMember.users_group_id))\
459 .filter(UsersGroupMember.user_id == uid).all()
457 .filter(UsersGroupMember.user_id == uid).all()
460
458
461 for perm in user_repo_perms_from_users_groups:
459 for perm in user_repo_perms_from_users_groups:
462 p = perm.Permission.permission_name
460 p = perm.Permission.permission_name
463 cur_perm = user.permissions['repositories'][perm.
461 cur_perm = user.permissions['repositories'][perm.
464 UsersGroupRepoToPerm.
462 UsersGroupRepoToPerm.
465 repository.repo_name]
463 repository.repo_name]
466 # overwrite permission only if it's greater than permission
464 # overwrite permission only if it's greater than permission
467 # given from other sources
465 # given from other sources
468 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
466 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
469 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
467 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
470 repository.repo_name] = p
468 repository.repo_name] = p
471
469
472 return user
470 return user
473
471
474 def has_perm(self, user, perm):
472 def has_perm(self, user, perm):
475 if not isinstance(perm, Permission):
473 if not isinstance(perm, Permission):
476 raise Exception('perm needs to be an instance of Permission class '
474 raise Exception('perm needs to be an instance of Permission class '
477 'got %s instead' % type(perm))
475 'got %s instead' % type(perm))
478
476
479 user = self.__get_user(user)
477 user = self.__get_user(user)
480
478
481 return UserToPerm.query().filter(UserToPerm.user == user)\
479 return UserToPerm.query().filter(UserToPerm.user == user)\
482 .filter(UserToPerm.permission == perm).scalar() is not None
480 .filter(UserToPerm.permission == perm).scalar() is not None
483
481
484 def grant_perm(self, user, perm):
482 def grant_perm(self, user, perm):
485 if not isinstance(perm, Permission):
483 if not isinstance(perm, Permission):
486 raise Exception('perm needs to be an instance of Permission class '
484 raise Exception('perm needs to be an instance of Permission class '
487 'got %s instead' % type(perm))
485 'got %s instead' % type(perm))
488
486
489 user = self.__get_user(user)
487 user = self.__get_user(user)
490
488
491 new = UserToPerm()
489 new = UserToPerm()
492 new.user = user
490 new.user = user
493 new.permission = perm
491 new.permission = perm
494 self.sa.add(new)
492 self.sa.add(new)
495
493
496
497 def revoke_perm(self, user, perm):
494 def revoke_perm(self, user, perm):
498 if not isinstance(perm, Permission):
495 if not isinstance(perm, Permission):
499 raise Exception('perm needs to be an instance of Permission class '
496 raise Exception('perm needs to be an instance of Permission class '
500 'got %s instead' % type(perm))
497 'got %s instead' % type(perm))
501
498
502 user = self.__get_user(user)
499 user = self.__get_user(user)
503
500
504 obj = UserToPerm.query().filter(UserToPerm.user == user)\
501 obj = UserToPerm.query().filter(UserToPerm.user == user)\
505 .filter(UserToPerm.permission == perm).scalar()
502 .filter(UserToPerm.permission == perm).scalar()
506 if obj:
503 if obj:
507 self.sa.delete(obj)
504 self.sa.delete(obj)
@@ -1,100 +1,100 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add user')} - ${c.rhodecode_name}
5 ${_('Add user')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Users'),h.url('users'))}
10 ${h.link_to(_('Users'),h.url('users'))}
11 &raquo;
11 &raquo;
12 ${_('add new user')}
12 ${_('add new user')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('users'))}
26 ${h.form(url('users'))}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="username">${_('Username')}:</label>
32 <label for="username">${_('Username')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('username',class_='small')}
35 ${h.text('username',class_='small')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="password">${_('Password')}:</label>
41 <label for="password">${_('Password')}:</label>
42 </div>
42 </div>
43 <div class="input">
43 <div class="input">
44 ${h.password('password',class_='small')}
44 ${h.password('password',class_='small')}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="password_confirmation">${_('Password confirmation')}:</label>
50 <label for="password_confirmation">${_('Password confirmation')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.password('password_confirmation',class_="small",autocomplete="off")}
53 ${h.password('password_confirmation',class_="small",autocomplete="off")}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="field">
57 <div class="field">
58 <div class="label">
58 <div class="label">
59 <label for="name">${_('First Name')}:</label>
59 <label for="name">${_('First Name')}:</label>
60 </div>
60 </div>
61 <div class="input">
61 <div class="input">
62 ${h.text('name',class_='small')}
62 ${h.text('name',class_='small')}
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="lastname">${_('Last Name')}:</label>
68 <label for="lastname">${_('Last Name')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.text('lastname',class_='small')}
71 ${h.text('lastname',class_='small')}
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="email">${_('Email')}:</label>
77 <label for="email">${_('Email')}:</label>
78 </div>
78 </div>
79 <div class="input">
79 <div class="input">
80 ${h.text('email',class_='small')}
80 ${h.text('email',class_='small')}
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label label-checkbox">
85 <div class="label label-checkbox">
86 <label for="active">${_('Active')}:</label>
86 <label for="active">${_('Active')}:</label>
87 </div>
87 </div>
88 <div class="checkboxes">
88 <div class="checkboxes">
89 ${h.checkbox('active',value=True)}
89 ${h.checkbox('active',value=True,checked='checked')}
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="buttons">
93 <div class="buttons">
94 ${h.submit('save',_('save'),class_="ui-button")}
94 ${h.submit('save',_('save'),class_="ui-button")}
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98 ${h.end_form()}
98 ${h.end_form()}
99 </div>
99 </div>
100 </%def>
100 </%def>
@@ -1,336 +1,336 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- HEADER -->
4 <!-- HEADER -->
5 <div id="header">
5 <div id="header">
6 <div id="header-inner" class="title">
6 <div id="header-inner" class="title">
7 <div id="logo">
7 <div id="logo">
8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 </div>
9 </div>
10 <!-- MENU -->
10 <!-- MENU -->
11 ${self.page_nav()}
11 ${self.page_nav()}
12 <!-- END MENU -->
12 <!-- END MENU -->
13 ${self.body()}
13 ${self.body()}
14 </div>
14 </div>
15 </div>
15 </div>
16 <!-- END HEADER -->
16 <!-- END HEADER -->
17
17
18 <!-- CONTENT -->
18 <!-- CONTENT -->
19 <div id="content">
19 <div id="content">
20 <div class="flash_msg">
20 <div class="flash_msg">
21 <% messages = h.flash.pop_messages() %>
21 <% messages = h.flash.pop_messages() %>
22 % if messages:
22 % if messages:
23 <ul id="flash-messages">
23 <ul id="flash-messages">
24 % for message in messages:
24 % for message in messages:
25 <li class="${message.category}_msg">${message}</li>
25 <li class="${message.category}_msg">${message}</li>
26 % endfor
26 % endfor
27 </ul>
27 </ul>
28 % endif
28 % endif
29 </div>
29 </div>
30 <div id="main">
30 <div id="main">
31 ${next.main()}
31 ${next.main()}
32 </div>
32 </div>
33 </div>
33 </div>
34 <!-- END CONTENT -->
34 <!-- END CONTENT -->
35
35
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title">
38 <div id="footer-inner" class="title">
39 <div>
39 <div>
40 <p class="footer-link">
40 <p class="footer-link">
41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 </p>
42 </p>
43 <p class="footer-link-right">
43 <p class="footer-link-right">
44 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
44 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 </p>
46 </p>
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 <!-- END FOOTER -->
50 <!-- END FOOTER -->
51
51
52 ### MAKO DEFS ###
52 ### MAKO DEFS ###
53 <%def name="page_nav()">
53 <%def name="page_nav()">
54 ${self.menu()}
54 ${self.menu()}
55 </%def>
55 </%def>
56
56
57 <%def name="breadcrumbs()">
57 <%def name="breadcrumbs()">
58 <div class="breadcrumbs">
58 <div class="breadcrumbs">
59 ${self.breadcrumbs_links()}
59 ${self.breadcrumbs_links()}
60 </div>
60 </div>
61 </%def>
61 </%def>
62
62
63 <%def name="usermenu()">
63 <%def name="usermenu()">
64 <div class="user-menu">
64 <div class="user-menu">
65 <div class="container">
65 <div class="container">
66 <div class="gravatar" id="quick_login_link">
66 <div class="gravatar" id="quick_login_link">
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
68 </div>
68 </div>
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
70 <div class="notifications">
70 <div class="notifications">
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
72 </div>
72 </div>
73 %endif
73 %endif
74 </div>
74 </div>
75 <div id="quick_login" style="display:none">
75 <div id="quick_login" style="display:none">
76 %if c.rhodecode_user.username == 'default':
76 %if c.rhodecode_user.username == 'default':
77 <h4>${_('Login to your account')}</h4>
77 <h4>${_('Login to your account')}</h4>
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
79 <div class="form">
79 <div class="form">
80 <div class="fields">
80 <div class="fields">
81 <div class="field">
81 <div class="field">
82 <div class="label">
82 <div class="label">
83 <label for="username">${_('Username')}:</label>
83 <label for="username">${_('Username')}:</label>
84 </div>
84 </div>
85 <div class="input">
85 <div class="input">
86 ${h.text('username',class_='focus',size=40)}
86 ${h.text('username',class_='focus',size=40)}
87 </div>
87 </div>
88
88
89 </div>
89 </div>
90 <div class="field">
90 <div class="field">
91 <div class="label">
91 <div class="label">
92 <label for="password">${_('Password')}:</label>
92 <label for="password">${_('Password')}:</label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 ${h.password('password',class_='focus',size=40)}
95 ${h.password('password',class_='focus',size=40)}
96 </div>
96 </div>
97
97
98 </div>
98 </div>
99 <div class="buttons">
99 <div class="buttons">
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
101 <div class="register">
101 <div class="register">
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
104 %endif
104 %endif
105 </div>
105 </div>
106 <div class="submit">
106 <div class="submit">
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 </div>
111 </div>
112 ${h.end_form()}
112 ${h.end_form()}
113 %else:
113 %else:
114 <div class="links_left">
114 <div class="links_left">
115 <div class="full_name">${c.rhodecode_user.full_name}</div>
115 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
118 </div>
118 </div>
119 <div class="links_right">
119 <div class="links_right">
120 <ol class="links">
120 <ol class="links">
121 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
121 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
122 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
122 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
123 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
123 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
124 </ol>
124 </ol>
125 </div>
125 </div>
126 %endif
126 %endif
127 </div>
127 </div>
128 </div>
128 </div>
129 </%def>
129 </%def>
130
130
131 <%def name="menu(current=None)">
131 <%def name="menu(current=None)">
132 <%
132 <%
133 def is_current(selected):
133 def is_current(selected):
134 if selected == current:
134 if selected == current:
135 return h.literal('class="current"')
135 return h.literal('class="current"')
136 %>
136 %>
137 %if current not in ['home','admin']:
137 %if current not in ['home','admin']:
138 ##REGULAR MENU
138 ##REGULAR MENU
139 <ul id="quick">
139 <ul id="quick">
140 <!-- repo switcher -->
140 <!-- repo switcher -->
141 <li>
141 <li>
142 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
142 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
143 <span class="icon">
143 <span class="icon">
144 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
144 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
145 </span>
145 </span>
146 <span>&darr;</span>
146 <span>&darr;</span>
147 </a>
147 </a>
148 <ul id="repo_switcher_list" class="repo_switcher">
148 <ul id="repo_switcher_list" class="repo_switcher">
149 <li>
149 <li>
150 <a href="#">${_('loading...')}</a>
150 <a href="#">${_('loading...')}</a>
151 </li>
151 </li>
152 </ul>
152 </ul>
153 </li>
153 </li>
154
154
155 <li ${is_current('summary')}>
155 <li ${is_current('summary')}>
156 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
156 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
157 <span class="icon">
157 <span class="icon">
158 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
158 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
159 </span>
159 </span>
160 <span>${_('Summary')}</span>
160 <span>${_('Summary')}</span>
161 </a>
161 </a>
162 </li>
162 </li>
163 <li ${is_current('changelog')}>
163 <li ${is_current('changelog')}>
164 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
164 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
165 <span class="icon">
165 <span class="icon">
166 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
166 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
167 </span>
167 </span>
168 <span>${_('Changelog')}</span>
168 <span>${_('Changelog')}</span>
169 </a>
169 </a>
170 </li>
170 </li>
171
171
172 <li ${is_current('switch_to')}>
172 <li ${is_current('switch_to')}>
173 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
173 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
174 <span class="icon">
174 <span class="icon">
175 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
175 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
176 </span>
176 </span>
177 <span>${_('Switch to')}</span>
177 <span>${_('Switch to')}</span>
178 </a>
178 </a>
179 <ul id="switch_to_list" class="switch_to">
179 <ul id="switch_to_list" class="switch_to">
180 <li><a href="#">${_('loading...')}</a></li>
180 <li><a href="#">${_('loading...')}</a></li>
181 </ul>
181 </ul>
182 </li>
182 </li>
183 <li ${is_current('files')}>
183 <li ${is_current('files')}>
184 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
184 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
185 <span class="icon">
185 <span class="icon">
186 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
186 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
187 </span>
187 </span>
188 <span>${_('Files')}</span>
188 <span>${_('Files')}</span>
189 </a>
189 </a>
190 </li>
190 </li>
191
191
192 <li ${is_current('options')}>
192 <li ${is_current('options')}>
193 <a class="menu_link" title="${_('Options')}" href="#">
193 <a class="menu_link" title="${_('Options')}" href="#">
194 <span class="icon">
194 <span class="icon">
195 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
195 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
196 </span>
196 </span>
197 <span>${_('Options')}</span>
197 <span>${_('Options')}</span>
198 </a>
198 </a>
199 <ul>
199 <ul>
200 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
200 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
201 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
201 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
202 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
202 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
203 %else:
203 %else:
204 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
204 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
205 %endif
205 %endif
206 %endif
206 %endif
207 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
207 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
208 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
208 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
209
209
210 % if h.HasPermissionAll('hg.admin')('access admin main page'):
210 % if h.HasPermissionAll('hg.admin')('access admin main page'):
211 <li>
211 <li>
212 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
212 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
213 <%def name="admin_menu()">
213 <%def name="admin_menu()">
214 <ul>
214 <ul>
215 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
215 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
216 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
216 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
217 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
217 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
218 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
218 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
219 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
219 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
220 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
220 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
221 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
221 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
222 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
222 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
223 </ul>
223 </ul>
224 </%def>
224 </%def>
225
225
226 ${admin_menu()}
226 ${admin_menu()}
227 </li>
227 </li>
228 % endif
228 % endif
229 </ul>
229 </ul>
230 </li>
230 </li>
231
231
232 <li>
232 <li>
233 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
233 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
234 <span class="icon_short">
234 <span class="icon_short">
235 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
235 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
236 </span>
236 </span>
237 <span id="current_followers_count" class="short">${c.repository_followers}</span>
237 <span id="current_followers_count" class="short">${c.repository_followers}</span>
238 </a>
238 </a>
239 </li>
239 </li>
240 <li>
240 <li>
241 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
241 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
242 <span class="icon_short">
242 <span class="icon_short">
243 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
243 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
244 </span>
244 </span>
245 <span class="short">${c.repository_forks}</span>
245 <span class="short">${c.repository_forks}</span>
246 </a>
246 </a>
247 </li>
247 </li>
248 ${usermenu()}
248 ${usermenu()}
249 </ul>
249 </ul>
250 <script type="text/javascript">
250 <script type="text/javascript">
251 YUE.on('repo_switcher','mouseover',function(){
251 YUE.on('repo_switcher','mouseover',function(){
252 function qfilter(){
252 function qfilter(){
253 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
253 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
254 var target = 'q_filter_rs';
254 var target = 'q_filter_rs';
255 var func = function(node){
255 var func = function(node){
256 return node.parentNode;
256 return node.parentNode;
257 }
257 }
258 q_filter(target,nodes,func);
258 q_filter(target,nodes,func);
259 }
259 }
260 var loaded = YUD.hasClass('repo_switcher','loaded');
260 var loaded = YUD.hasClass('repo_switcher','loaded');
261 if(!loaded){
261 if(!loaded){
262 YUD.addClass('repo_switcher','loaded');
262 YUD.addClass('repo_switcher','loaded');
263 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
263 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
264 function(o){qfilter();},
264 function(o){qfilter();},
265 function(o){YUD.removeClass('repo_switcher','loaded');}
265 function(o){YUD.removeClass('repo_switcher','loaded');}
266 ,null);
266 ,null);
267 }
267 }
268 return false;
268 return false;
269 });
269 });
270
270
271 YUE.on('branch_tag_switcher','mouseover',function(){
271 YUE.on('branch_tag_switcher','mouseover',function(){
272 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
272 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
273 if(!loaded){
273 if(!loaded){
274 YUD.addClass('branch_tag_switcher','loaded');
274 YUD.addClass('branch_tag_switcher','loaded');
275 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
275 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
276 function(o){},
276 function(o){},
277 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
277 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
278 ,null);
278 ,null);
279 }
279 }
280 return false;
280 return false;
281 });
281 });
282 </script>
282 </script>
283 %else:
283 %else:
284 ##ROOT MENU
284 ##ROOT MENU
285 <ul id="quick">
285 <ul id="quick">
286 <li>
286 <li>
287 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
287 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
288 <span class="icon">
288 <span class="icon">
289 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
289 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
290 </span>
290 </span>
291 <span>${_('Home')}</span>
291 <span>${_('Home')}</span>
292 </a>
292 </a>
293 </li>
293 </li>
294 %if c.rhodecode_user.username != 'default':
294 %if c.rhodecode_user.username != 'default':
295 <li>
295 <li>
296 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
296 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
297 <span class="icon">
297 <span class="icon">
298 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
298 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
299 </span>
299 </span>
300 <span>${_('Journal')}</span>
300 <span>${_('Journal')}</span>
301 </a>
301 </a>
302 </li>
302 </li>
303 %else:
303 %else:
304 <li>
304 <li>
305 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
305 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
306 <span class="icon">
306 <span class="icon">
307 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
307 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
308 </span>
308 </span>
309 <span>${_('Public journal')}</span>
309 <span>${_('Public journal')}</span>
310 </a>
310 </a>
311 </li>
311 </li>
312 %endif
312 %endif
313 <li>
313 <li>
314 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
314 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
315 <span class="icon">
315 <span class="icon">
316 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
316 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
317 </span>
317 </span>
318 <span>${_('Search')}</span>
318 <span>${_('Search')}</span>
319 </a>
319 </a>
320 </li>
320 </li>
321
321
322 %if h.HasPermissionAll('hg.admin')('access admin main page'):
322 %if h.HasPermissionAll('hg.admin')('access admin main page'):
323 <li ${is_current('admin')}>
323 <li ${is_current('admin')}>
324 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
324 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
325 <span class="icon">
325 <span class="icon">
326 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
326 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
327 </span>
327 </span>
328 <span>${_('Admin')}</span>
328 <span>${_('Admin')}</span>
329 </a>
329 </a>
330 ${admin_menu()}
330 ${admin_menu()}
331 </li>
331 </li>
332 %endif
332 %endif
333 ${usermenu()}
333 ${usermenu()}
334 </ul>
334 </ul>
335 %endif
335 %endif
336 </%def>
336 </%def>
General Comments 0
You need to be logged in to leave comments. Login now