##// END OF EJS Templates
api: add option to get pullrequests for get_repo
domruf -
r6594:bf9900e6 default
parent child Browse files
Show More
@@ -1,1123 +1,1173 b''
1 .. _api:
1 .. _api:
2
2
3 ===
3 ===
4 API
4 API
5 ===
5 ===
6
6
7 Kallithea has a simple JSON RPC API with a single schema for calling all API
7 Kallithea has a simple JSON RPC API with a single schema for calling all API
8 methods. Everything is available by sending JSON encoded http(s) requests to
8 methods. Everything is available by sending JSON encoded http(s) requests to
9 ``<your_server>/_admin/api``.
9 ``<your_server>/_admin/api``.
10
10
11
11
12 API keys
12 API keys
13 --------
13 --------
14
14
15 Every Kallithea user automatically receives an API key, which they can
15 Every Kallithea user automatically receives an API key, which they can
16 view under "My Account". On this page, API keys can also be revoked, and
16 view under "My Account". On this page, API keys can also be revoked, and
17 additional API keys can be generated.
17 additional API keys can be generated.
18
18
19
19
20 API access
20 API access
21 ----------
21 ----------
22
22
23 Clients must send JSON encoded JSON-RPC requests::
23 Clients must send JSON encoded JSON-RPC requests::
24
24
25 {
25 {
26 "id: "<id>",
26 "id: "<id>",
27 "api_key": "<api_key>",
27 "api_key": "<api_key>",
28 "method": "<method_name>",
28 "method": "<method_name>",
29 "args": {"<arg_key>": "<arg_val>"}
29 "args": {"<arg_key>": "<arg_val>"}
30 }
30 }
31
31
32 For example, to pull to a local "CPython" mirror using curl::
32 For example, to pull to a local "CPython" mirror using curl::
33
33
34 curl https://kallithea.example.com/_admin/api -X POST -H 'content-type:text/plain' \
34 curl https://kallithea.example.com/_admin/api -X POST -H 'content-type:text/plain' \
35 --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
35 --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
36
36
37 In general, provide
37 In general, provide
38 - *id*, a value of any type, can be used to match the response with the request that it is replying to.
38 - *id*, a value of any type, can be used to match the response with the request that it is replying to.
39 - *api_key*, for authentication and permission validation.
39 - *api_key*, for authentication and permission validation.
40 - *method*, the name of the method to call -- a list of available methods can be found below.
40 - *method*, the name of the method to call -- a list of available methods can be found below.
41 - *args*, the arguments to pass to the method.
41 - *args*, the arguments to pass to the method.
42
42
43 .. note::
43 .. note::
44
44
45 api_key can be found or set on the user account page.
45 api_key can be found or set on the user account page.
46
46
47 The response to the JSON-RPC API call will always be a JSON structure::
47 The response to the JSON-RPC API call will always be a JSON structure::
48
48
49 {
49 {
50 "id": <id>, # the id that was used in the request
50 "id": <id>, # the id that was used in the request
51 "result": <result>|null, # JSON formatted result (null on error)
51 "result": <result>|null, # JSON formatted result (null on error)
52 "error": null|<error_message> # JSON formatted error (null on success)
52 "error": null|<error_message> # JSON formatted error (null on success)
53 }
53 }
54
54
55 All responses from the API will be ``HTTP/1.0 200 OK``. If an error occurs,
55 All responses from the API will be ``HTTP/1.0 200 OK``. If an error occurs,
56 the reponse will have a failure description in *error* and
56 the reponse will have a failure description in *error* and
57 *result* will be null.
57 *result* will be null.
58
58
59
59
60 API client
60 API client
61 ----------
61 ----------
62
62
63 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
63 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
64 way to call the JSON-RPC API.
64 way to call the JSON-RPC API.
65
65
66 For example, to call ``get_repo``::
66 For example, to call ``get_repo``::
67
67
68 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo
68 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo
69
69
70 Calling method get_repo => <Kallithea URL>
70 Calling method get_repo => <Kallithea URL>
71 Server response
71 Server response
72 ERROR:"Missing non optional `repoid` arg in JSON DATA"
72 ERROR:"Missing non optional `repoid` arg in JSON DATA"
73
73
74 Oops, looks like we forgot to add an argument. Let's try again, now
74 Oops, looks like we forgot to add an argument. Let's try again, now
75 providing the ``repoid`` as a parameter::
75 providing the ``repoid`` as a parameter::
76
76
77 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo repoid:myrepo
77 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo repoid:myrepo
78
78
79 Calling method get_repo => <Kallithea URL>
79 Calling method get_repo => <Kallithea URL>
80 Server response
80 Server response
81 {
81 {
82 "clone_uri": null,
82 "clone_uri": null,
83 "created_on": "2015-08-31T14:55:19.042",
83 "created_on": "2015-08-31T14:55:19.042",
84 ...
84 ...
85
85
86 To avoid specifying ``apihost`` and ``apikey`` every time, run::
86 To avoid specifying ``apihost`` and ``apikey`` every time, run::
87
87
88 kallithea-api --save-config --apihost=<Kallithea URL> --apikey=<API key>
88 kallithea-api --save-config --apihost=<Kallithea URL> --apikey=<API key>
89
89
90 This will create a ``~/.config/kallithea`` with the specified URL and API key
90 This will create a ``~/.config/kallithea`` with the specified URL and API key
91 so you don't have to specify them every time.
91 so you don't have to specify them every time.
92
92
93
93
94 API methods
94 API methods
95 -----------
95 -----------
96
96
97
97
98 pull
98 pull
99 ^^^^
99 ^^^^
100
100
101 Pull the given repo from remote location. Can be used to automatically keep
101 Pull the given repo from remote location. Can be used to automatically keep
102 remote repos up to date.
102 remote repos up to date.
103 This command can only be executed using the api_key of a user with admin rights.
103 This command can only be executed using the api_key of a user with admin rights.
104
104
105 INPUT::
105 INPUT::
106
106
107 id : <id_for_response>
107 id : <id_for_response>
108 api_key : "<api_key>"
108 api_key : "<api_key>"
109 method : "pull"
109 method : "pull"
110 args : {
110 args : {
111 "repoid" : "<reponame or repo_id>"
111 "repoid" : "<reponame or repo_id>"
112 }
112 }
113
113
114 OUTPUT::
114 OUTPUT::
115
115
116 id : <id_given_in_input>
116 id : <id_given_in_input>
117 result : "Pulled from `<reponame>`"
117 result : "Pulled from `<reponame>`"
118 error : null
118 error : null
119
119
120 rescan_repos
120 rescan_repos
121 ^^^^^^^^^^^^
121 ^^^^^^^^^^^^
122
122
123 Rescan repositories. If ``remove_obsolete`` is set,
123 Rescan repositories. If ``remove_obsolete`` is set,
124 Kallithea will delete repos that are in the database but not in the filesystem.
124 Kallithea will delete repos that are in the database but not in the filesystem.
125 This command can only be executed using the api_key of a user with admin rights.
125 This command can only be executed using the api_key of a user with admin rights.
126
126
127 INPUT::
127 INPUT::
128
128
129 id : <id_for_response>
129 id : <id_for_response>
130 api_key : "<api_key>"
130 api_key : "<api_key>"
131 method : "rescan_repos"
131 method : "rescan_repos"
132 args : {
132 args : {
133 "remove_obsolete" : "<boolean = Optional(False)>"
133 "remove_obsolete" : "<boolean = Optional(False)>"
134 }
134 }
135
135
136 OUTPUT::
136 OUTPUT::
137
137
138 id : <id_given_in_input>
138 id : <id_given_in_input>
139 result : "{'added': [<list of names of added repos>],
139 result : "{'added': [<list of names of added repos>],
140 'removed': [<list of names of removed repos>]}"
140 'removed': [<list of names of removed repos>]}"
141 error : null
141 error : null
142
142
143 invalidate_cache
143 invalidate_cache
144 ^^^^^^^^^^^^^^^^
144 ^^^^^^^^^^^^^^^^
145
145
146 Invalidate the cache for a repository.
146 Invalidate the cache for a repository.
147 This command can only be executed using the api_key of a user with admin rights,
147 This command can only be executed using the api_key of a user with admin rights,
148 or that of a regular user with admin or write access to the repository.
148 or that of a regular user with admin or write access to the repository.
149
149
150 INPUT::
150 INPUT::
151
151
152 id : <id_for_response>
152 id : <id_for_response>
153 api_key : "<api_key>"
153 api_key : "<api_key>"
154 method : "invalidate_cache"
154 method : "invalidate_cache"
155 args : {
155 args : {
156 "repoid" : "<reponame or repo_id>"
156 "repoid" : "<reponame or repo_id>"
157 }
157 }
158
158
159 OUTPUT::
159 OUTPUT::
160
160
161 id : <id_given_in_input>
161 id : <id_given_in_input>
162 result : "Caches of repository `<reponame>`"
162 result : "Caches of repository `<reponame>`"
163 error : null
163 error : null
164
164
165 lock
165 lock
166 ^^^^
166 ^^^^
167
167
168 Set the locking state on the given repository by the given user.
168 Set the locking state on the given repository by the given user.
169 If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method.
169 If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method.
170 If param ``locked`` is skipped, the current lock state of the repository is returned.
170 If param ``locked`` is skipped, the current lock state of the repository is returned.
171 This command can only be executed using the api_key of a user with admin rights, or that of a regular user with admin or write access to the repository.
171 This command can only be executed using the api_key of a user with admin rights, or that of a regular user with admin or write access to the repository.
172
172
173 INPUT::
173 INPUT::
174
174
175 id : <id_for_response>
175 id : <id_for_response>
176 api_key : "<api_key>"
176 api_key : "<api_key>"
177 method : "lock"
177 method : "lock"
178 args : {
178 args : {
179 "repoid" : "<reponame or repo_id>"
179 "repoid" : "<reponame or repo_id>"
180 "userid" : "<user_id or username = Optional(=apiuser)>",
180 "userid" : "<user_id or username = Optional(=apiuser)>",
181 "locked" : "<bool true|false = Optional(=None)>"
181 "locked" : "<bool true|false = Optional(=None)>"
182 }
182 }
183
183
184 OUTPUT::
184 OUTPUT::
185
185
186 id : <id_given_in_input>
186 id : <id_given_in_input>
187 result : {
187 result : {
188 "repo": "<reponame>",
188 "repo": "<reponame>",
189 "locked": "<bool true|false>",
189 "locked": "<bool true|false>",
190 "locked_since": "<float lock_time>",
190 "locked_since": "<float lock_time>",
191 "locked_by": "<username>",
191 "locked_by": "<username>",
192 "msg": "User `<username>` set lock state for repo `<reponame>` to `<false|true>`"
192 "msg": "User `<username>` set lock state for repo `<reponame>` to `<false|true>`"
193 }
193 }
194 error : null
194 error : null
195
195
196 get_ip
196 get_ip
197 ^^^^^^
197 ^^^^^^
198
198
199 Return IP address as seen from Kallithea server, together with all
199 Return IP address as seen from Kallithea server, together with all
200 defined IP addresses for given user.
200 defined IP addresses for given user.
201 This command can only be executed using the api_key of a user with admin rights.
201 This command can only be executed using the api_key of a user with admin rights.
202
202
203 INPUT::
203 INPUT::
204
204
205 id : <id_for_response>
205 id : <id_for_response>
206 api_key : "<api_key>"
206 api_key : "<api_key>"
207 method : "get_ip"
207 method : "get_ip"
208 args : {
208 args : {
209 "userid" : "<user_id or username>",
209 "userid" : "<user_id or username>",
210 }
210 }
211
211
212 OUTPUT::
212 OUTPUT::
213
213
214 id : <id_given_in_input>
214 id : <id_given_in_input>
215 result : {
215 result : {
216 "ip_addr_server": <ip_from_clien>",
216 "ip_addr_server": <ip_from_clien>",
217 "user_ips": [
217 "user_ips": [
218 {
218 {
219 "ip_addr": "<ip_with_mask>",
219 "ip_addr": "<ip_with_mask>",
220 "ip_range": ["<start_ip>", "<end_ip>"],
220 "ip_range": ["<start_ip>", "<end_ip>"],
221 },
221 },
222 ...
222 ...
223 ]
223 ]
224 }
224 }
225
225
226 error : null
226 error : null
227
227
228 get_user
228 get_user
229 ^^^^^^^^
229 ^^^^^^^^
230
230
231 Get a user by username or userid. The result is empty if user can't be found.
231 Get a user by username or userid. The result is empty if user can't be found.
232 If userid param is skipped, it is set to id of user who is calling this method.
232 If userid param is skipped, it is set to id of user who is calling this method.
233 Any userid can be specified when the command is executed using the api_key of a user with admin rights.
233 Any userid can be specified when the command is executed using the api_key of a user with admin rights.
234 Regular users can only specify their own userid.
234 Regular users can only specify their own userid.
235
235
236 INPUT::
236 INPUT::
237
237
238 id : <id_for_response>
238 id : <id_for_response>
239 api_key : "<api_key>"
239 api_key : "<api_key>"
240 method : "get_user"
240 method : "get_user"
241 args : {
241 args : {
242 "userid" : "<username or user_id Optional(=apiuser)>"
242 "userid" : "<username or user_id Optional(=apiuser)>"
243 }
243 }
244
244
245 OUTPUT::
245 OUTPUT::
246
246
247 id : <id_given_in_input>
247 id : <id_given_in_input>
248 result: None if user does not exist or
248 result: None if user does not exist or
249 {
249 {
250 "user_id" : "<user_id>",
250 "user_id" : "<user_id>",
251 "api_key" : "<api_key>",
251 "api_key" : "<api_key>",
252 "username" : "<username>",
252 "username" : "<username>",
253 "firstname": "<firstname>",
253 "firstname": "<firstname>",
254 "lastname" : "<lastname>",
254 "lastname" : "<lastname>",
255 "email" : "<email>",
255 "email" : "<email>",
256 "emails": "<list_of_all_additional_emails>",
256 "emails": "<list_of_all_additional_emails>",
257 "ip_addresses": "<list_of_ip_addresses_for_user>",
257 "ip_addresses": "<list_of_ip_addresses_for_user>",
258 "active" : "<bool>",
258 "active" : "<bool>",
259 "admin" :Β  "<bool>",
259 "admin" :Β  "<bool>",
260 "ldap_dn" : "<ldap_dn>",
260 "ldap_dn" : "<ldap_dn>",
261 "last_login": "<last_login>",
261 "last_login": "<last_login>",
262 "permissions": {
262 "permissions": {
263 "global": ["hg.create.repository",
263 "global": ["hg.create.repository",
264 "repository.read",
264 "repository.read",
265 "hg.register.manual_activate"],
265 "hg.register.manual_activate"],
266 "repositories": {"repo1": "repository.none"},
266 "repositories": {"repo1": "repository.none"},
267 "repositories_groups": {"Group1": "group.read"}
267 "repositories_groups": {"Group1": "group.read"}
268 },
268 },
269 }
269 }
270 error: null
270 error: null
271
271
272 get_users
272 get_users
273 ^^^^^^^^^
273 ^^^^^^^^^
274
274
275 List all existing users.
275 List all existing users.
276 This command can only be executed using the api_key of a user with admin rights.
276 This command can only be executed using the api_key of a user with admin rights.
277
277
278 INPUT::
278 INPUT::
279
279
280 id : <id_for_response>
280 id : <id_for_response>
281 api_key : "<api_key>"
281 api_key : "<api_key>"
282 method : "get_users"
282 method : "get_users"
283 args : { }
283 args : { }
284
284
285 OUTPUT::
285 OUTPUT::
286
286
287 id : <id_given_in_input>
287 id : <id_given_in_input>
288 result: [
288 result: [
289 {
289 {
290 "user_id" : "<user_id>",
290 "user_id" : "<user_id>",
291 "api_key" : "<api_key>",
291 "api_key" : "<api_key>",
292 "username" : "<username>",
292 "username" : "<username>",
293 "firstname": "<firstname>",
293 "firstname": "<firstname>",
294 "lastname" : "<lastname>",
294 "lastname" : "<lastname>",
295 "email" : "<email>",
295 "email" : "<email>",
296 "emails": "<list_of_all_additional_emails>",
296 "emails": "<list_of_all_additional_emails>",
297 "ip_addresses": "<list_of_ip_addresses_for_user>",
297 "ip_addresses": "<list_of_ip_addresses_for_user>",
298 "active" : "<bool>",
298 "active" : "<bool>",
299 "admin" :Β  "<bool>",
299 "admin" :Β  "<bool>",
300 "ldap_dn" : "<ldap_dn>",
300 "ldap_dn" : "<ldap_dn>",
301 "last_login": "<last_login>",
301 "last_login": "<last_login>",
302 },
302 },
303 …
303 …
304 ]
304 ]
305 error: null
305 error: null
306
306
307 .. _create-user:
307 .. _create-user:
308
308
309 create_user
309 create_user
310 ^^^^^^^^^^^
310 ^^^^^^^^^^^
311
311
312 Create new user.
312 Create new user.
313 This command can only be executed using the api_key of a user with admin rights.
313 This command can only be executed using the api_key of a user with admin rights.
314
314
315 INPUT::
315 INPUT::
316
316
317 id : <id_for_response>
317 id : <id_for_response>
318 api_key : "<api_key>"
318 api_key : "<api_key>"
319 method : "create_user"
319 method : "create_user"
320 args : {
320 args : {
321 "username" : "<username>",
321 "username" : "<username>",
322 "email" : "<useremail>",
322 "email" : "<useremail>",
323 "password" : "<password = Optional(None)>",
323 "password" : "<password = Optional(None)>",
324 "firstname" : "<firstname> = Optional(None)",
324 "firstname" : "<firstname> = Optional(None)",
325 "lastname" : "<lastname> = Optional(None)",
325 "lastname" : "<lastname> = Optional(None)",
326 "active" : "<bool> = Optional(True)",
326 "active" : "<bool> = Optional(True)",
327 "admin" : "<bool> = Optional(False)",
327 "admin" : "<bool> = Optional(False)",
328 "ldap_dn" : "<ldap_dn> = Optional(None)"
328 "ldap_dn" : "<ldap_dn> = Optional(None)"
329 }
329 }
330
330
331 OUTPUT::
331 OUTPUT::
332
332
333 id : <id_given_in_input>
333 id : <id_given_in_input>
334 result: {
334 result: {
335 "msg" : "created new user `<username>`",
335 "msg" : "created new user `<username>`",
336 "user": {
336 "user": {
337 "user_id" : "<user_id>",
337 "user_id" : "<user_id>",
338 "username" : "<username>",
338 "username" : "<username>",
339 "firstname": "<firstname>",
339 "firstname": "<firstname>",
340 "lastname" : "<lastname>",
340 "lastname" : "<lastname>",
341 "email" : "<email>",
341 "email" : "<email>",
342 "emails": "<list_of_all_additional_emails>",
342 "emails": "<list_of_all_additional_emails>",
343 "active" : "<bool>",
343 "active" : "<bool>",
344 "admin" :Β  "<bool>",
344 "admin" :Β  "<bool>",
345 "ldap_dn" : "<ldap_dn>",
345 "ldap_dn" : "<ldap_dn>",
346 "last_login": "<last_login>",
346 "last_login": "<last_login>",
347 },
347 },
348 }
348 }
349 error: null
349 error: null
350
350
351 Example::
351 Example::
352
352
353 kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com
353 kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com
354
354
355 update_user
355 update_user
356 ^^^^^^^^^^^
356 ^^^^^^^^^^^
357
357
358 Update the given user if such user exists.
358 Update the given user if such user exists.
359 This command can only be executed using the api_key of a user with admin rights.
359 This command can only be executed using the api_key of a user with admin rights.
360
360
361 INPUT::
361 INPUT::
362
362
363 id : <id_for_response>
363 id : <id_for_response>
364 api_key : "<api_key>"
364 api_key : "<api_key>"
365 method : "update_user"
365 method : "update_user"
366 args : {
366 args : {
367 "userid" : "<user_id or username>",
367 "userid" : "<user_id or username>",
368 "username" : "<username> = Optional(None)",
368 "username" : "<username> = Optional(None)",
369 "email" : "<useremail> = Optional(None)",
369 "email" : "<useremail> = Optional(None)",
370 "password" : "<password> = Optional(None)",
370 "password" : "<password> = Optional(None)",
371 "firstname" : "<firstname> = Optional(None)",
371 "firstname" : "<firstname> = Optional(None)",
372 "lastname" : "<lastname> = Optional(None)",
372 "lastname" : "<lastname> = Optional(None)",
373 "active" : "<bool> = Optional(None)",
373 "active" : "<bool> = Optional(None)",
374 "admin" : "<bool> = Optional(None)",
374 "admin" : "<bool> = Optional(None)",
375 "ldap_dn" : "<ldap_dn> = Optional(None)"
375 "ldap_dn" : "<ldap_dn> = Optional(None)"
376 }
376 }
377
377
378 OUTPUT::
378 OUTPUT::
379
379
380 id : <id_given_in_input>
380 id : <id_given_in_input>
381 result: {
381 result: {
382 "msg" : "updated user ID:<userid> <username>",
382 "msg" : "updated user ID:<userid> <username>",
383 "user": {
383 "user": {
384 "user_id" : "<user_id>",
384 "user_id" : "<user_id>",
385 "api_key" : "<api_key>",
385 "api_key" : "<api_key>",
386 "username" : "<username>",
386 "username" : "<username>",
387 "firstname": "<firstname>",
387 "firstname": "<firstname>",
388 "lastname" : "<lastname>",
388 "lastname" : "<lastname>",
389 "email" : "<email>",
389 "email" : "<email>",
390 "emails": "<list_of_all_additional_emails>",
390 "emails": "<list_of_all_additional_emails>",
391 "active" : "<bool>",
391 "active" : "<bool>",
392 "admin" :Β  "<bool>",
392 "admin" :Β  "<bool>",
393 "ldap_dn" : "<ldap_dn>",
393 "ldap_dn" : "<ldap_dn>",
394 "last_login": "<last_login>",
394 "last_login": "<last_login>",
395 },
395 },
396 }
396 }
397 error: null
397 error: null
398
398
399 delete_user
399 delete_user
400 ^^^^^^^^^^^
400 ^^^^^^^^^^^
401
401
402 Delete the given user if such a user exists.
402 Delete the given user if such a user exists.
403 This command can only be executed using the api_key of a user with admin rights.
403 This command can only be executed using the api_key of a user with admin rights.
404
404
405 INPUT::
405 INPUT::
406
406
407 id : <id_for_response>
407 id : <id_for_response>
408 api_key : "<api_key>"
408 api_key : "<api_key>"
409 method : "delete_user"
409 method : "delete_user"
410 args : {
410 args : {
411 "userid" : "<user_id or username>",
411 "userid" : "<user_id or username>",
412 }
412 }
413
413
414 OUTPUT::
414 OUTPUT::
415
415
416 id : <id_given_in_input>
416 id : <id_given_in_input>
417 result: {
417 result: {
418 "msg" : "deleted user ID:<userid> <username>",
418 "msg" : "deleted user ID:<userid> <username>",
419 "user": null
419 "user": null
420 }
420 }
421 error: null
421 error: null
422
422
423 get_user_group
423 get_user_group
424 ^^^^^^^^^^^^^^
424 ^^^^^^^^^^^^^^
425
425
426 Get an existing user group.
426 Get an existing user group.
427 This command can only be executed using the api_key of a user with admin rights.
427 This command can only be executed using the api_key of a user with admin rights.
428
428
429 INPUT::
429 INPUT::
430
430
431 id : <id_for_response>
431 id : <id_for_response>
432 api_key : "<api_key>"
432 api_key : "<api_key>"
433 method : "get_user_group"
433 method : "get_user_group"
434 args : {
434 args : {
435 "usergroupid" : "<user group id or name>"
435 "usergroupid" : "<user group id or name>"
436 }
436 }
437
437
438 OUTPUT::
438 OUTPUT::
439
439
440 id : <id_given_in_input>
440 id : <id_given_in_input>
441 result : None if group not exist
441 result : None if group not exist
442 {
442 {
443 "users_group_id" : "<id>",
443 "users_group_id" : "<id>",
444 "group_name" : "<groupname>",
444 "group_name" : "<groupname>",
445 "active": "<bool>",
445 "active": "<bool>",
446 "members" : [
446 "members" : [
447 {
447 {
448 "user_id" : "<user_id>",
448 "user_id" : "<user_id>",
449 "api_key" : "<api_key>",
449 "api_key" : "<api_key>",
450 "username" : "<username>",
450 "username" : "<username>",
451 "firstname": "<firstname>",
451 "firstname": "<firstname>",
452 "lastname" : "<lastname>",
452 "lastname" : "<lastname>",
453 "email" : "<email>",
453 "email" : "<email>",
454 "emails": "<list_of_all_additional_emails>",
454 "emails": "<list_of_all_additional_emails>",
455 "active" : "<bool>",
455 "active" : "<bool>",
456 "admin" :Β  "<bool>",
456 "admin" :Β  "<bool>",
457 "ldap_dn" : "<ldap_dn>",
457 "ldap_dn" : "<ldap_dn>",
458 "last_login": "<last_login>",
458 "last_login": "<last_login>",
459 },
459 },
460 …
460 …
461 ]
461 ]
462 }
462 }
463 error : null
463 error : null
464
464
465 get_user_groups
465 get_user_groups
466 ^^^^^^^^^^^^^^^
466 ^^^^^^^^^^^^^^^
467
467
468 List all existing user groups.
468 List all existing user groups.
469 This command can only be executed using the api_key of a user with admin rights.
469 This command can only be executed using the api_key of a user with admin rights.
470
470
471 INPUT::
471 INPUT::
472
472
473 id : <id_for_response>
473 id : <id_for_response>
474 api_key : "<api_key>"
474 api_key : "<api_key>"
475 method : "get_user_groups"
475 method : "get_user_groups"
476 args : { }
476 args : { }
477
477
478 OUTPUT::
478 OUTPUT::
479
479
480 id : <id_given_in_input>
480 id : <id_given_in_input>
481 result : [
481 result : [
482 {
482 {
483 "users_group_id" : "<id>",
483 "users_group_id" : "<id>",
484 "group_name" : "<groupname>",
484 "group_name" : "<groupname>",
485 "active": "<bool>",
485 "active": "<bool>",
486 },
486 },
487 …
487 …
488 ]
488 ]
489 error : null
489 error : null
490
490
491 create_user_group
491 create_user_group
492 ^^^^^^^^^^^^^^^^^
492 ^^^^^^^^^^^^^^^^^
493
493
494 Create a new user group.
494 Create a new user group.
495 This command can only be executed using the api_key of a user with admin rights.
495 This command can only be executed using the api_key of a user with admin rights.
496
496
497 INPUT::
497 INPUT::
498
498
499 id : <id_for_response>
499 id : <id_for_response>
500 api_key : "<api_key>"
500 api_key : "<api_key>"
501 method : "create_user_group"
501 method : "create_user_group"
502 args: {
502 args: {
503 "group_name": "<groupname>",
503 "group_name": "<groupname>",
504 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
504 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
505 "active": "<bool> = Optional(True)"
505 "active": "<bool> = Optional(True)"
506 }
506 }
507
507
508 OUTPUT::
508 OUTPUT::
509
509
510 id : <id_given_in_input>
510 id : <id_given_in_input>
511 result: {
511 result: {
512 "msg": "created new user group `<groupname>`",
512 "msg": "created new user group `<groupname>`",
513 "users_group": {
513 "users_group": {
514 "users_group_id" : "<id>",
514 "users_group_id" : "<id>",
515 "group_name" : "<groupname>",
515 "group_name" : "<groupname>",
516 "active": "<bool>",
516 "active": "<bool>",
517 },
517 },
518 }
518 }
519 error: null
519 error: null
520
520
521 add_user_to_user_group
521 add_user_to_user_group
522 ^^^^^^^^^^^^^^^^^^^^^^
522 ^^^^^^^^^^^^^^^^^^^^^^
523
523
524 Adds a user to a user group. If the user already is in that group, success will be
524 Adds a user to a user group. If the user already is in that group, success will be
525 ``false``.
525 ``false``.
526 This command can only be executed using the api_key of a user with admin rights.
526 This command can only be executed using the api_key of a user with admin rights.
527
527
528 INPUT::
528 INPUT::
529
529
530 id : <id_for_response>
530 id : <id_for_response>
531 api_key : "<api_key>"
531 api_key : "<api_key>"
532 method : "add_user_user_group"
532 method : "add_user_user_group"
533 args: {
533 args: {
534 "usersgroupid" : "<user group id or name>",
534 "usersgroupid" : "<user group id or name>",
535 "userid" : "<user_id or username>",
535 "userid" : "<user_id or username>",
536 }
536 }
537
537
538 OUTPUT::
538 OUTPUT::
539
539
540 id : <id_given_in_input>
540 id : <id_given_in_input>
541 result: {
541 result: {
542 "success": True|False # depends on if member is in group
542 "success": True|False # depends on if member is in group
543 "msg": "added member `<username>` to a user group `<groupname>` |
543 "msg": "added member `<username>` to a user group `<groupname>` |
544 User is already in that group"
544 User is already in that group"
545 }
545 }
546 error: null
546 error: null
547
547
548 remove_user_from_user_group
548 remove_user_from_user_group
549 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
549 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
550
550
551 Remove a user from a user group. If the user isn't in the given group, success will
551 Remove a user from a user group. If the user isn't in the given group, success will
552 be ``false``.
552 be ``false``.
553 This command can only be executed using the api_key of a user with admin rights.
553 This command can only be executed using the api_key of a user with admin rights.
554
554
555 INPUT::
555 INPUT::
556
556
557 id : <id_for_response>
557 id : <id_for_response>
558 api_key : "<api_key>"
558 api_key : "<api_key>"
559 method : "remove_user_from_user_group"
559 method : "remove_user_from_user_group"
560 args: {
560 args: {
561 "usersgroupid" : "<user group id or name>",
561 "usersgroupid" : "<user group id or name>",
562 "userid" : "<user_id or username>",
562 "userid" : "<user_id or username>",
563 }
563 }
564
564
565 OUTPUT::
565 OUTPUT::
566
566
567 id : <id_given_in_input>
567 id : <id_given_in_input>
568 result: {
568 result: {
569 "success": True|False, # depends on if member is in group
569 "success": True|False, # depends on if member is in group
570 "msg": "removed member <username> from user group <groupname> |
570 "msg": "removed member <username> from user group <groupname> |
571 User wasn't in group"
571 User wasn't in group"
572 }
572 }
573 error: null
573 error: null
574
574
575 get_repo
575 get_repo
576 ^^^^^^^^
576 ^^^^^^^^
577
577
578 Get an existing repository by its name or repository_id. Members will contain
578 Get an existing repository by its name or repository_id. Members will contain
579 either users_group or users associated to that repository.
579 either users_group or users associated to that repository.
580 This command can only be executed using the api_key of a user with admin rights,
580 This command can only be executed using the api_key of a user with admin rights,
581 or that of a regular user with at least read access to the repository.
581 or that of a regular user with at least read access to the repository.
582
582
583 INPUT::
583 INPUT::
584
584
585 id : <id_for_response>
585 id : <id_for_response>
586 api_key : "<api_key>"
586 api_key : "<api_key>"
587 method : "get_repo"
587 method : "get_repo"
588 args: {
588 args: {
589 "repoid" : "<reponame or repo_id>",
589 "repoid" : "<reponame or repo_id>",
590 "with_revision_names": "<bool> = Optional(False)",
590 "with_revision_names": "<bool> = Optional(False)",
591 "with_pullrequests": "<bool> = Optional(False)",
591 }
592 }
592
593
593 OUTPUT::
594 OUTPUT::
594
595
595 id : <id_given_in_input>
596 id : <id_given_in_input>
596 result: None if repository does not exist or
597 result: None if repository does not exist or
597 {
598 {
598 "repo_id" : "<repo_id>",
599 "repo_id" : "<repo_id>",
599 "repo_name" : "<reponame>"
600 "repo_name" : "<reponame>"
600 "repo_type" : "<repo_type>",
601 "repo_type" : "<repo_type>",
601 "clone_uri" : "<clone_uri>",
602 "clone_uri" : "<clone_uri>",
602 "enable_downloads": "<bool>",
603 "enable_downloads": "<bool>",
603 "enable_locking": "<bool>",
604 "enable_locking": "<bool>",
604 "enable_statistics": "<bool>",
605 "enable_statistics": "<bool>",
605 "private": "<bool>",
606 "private": "<bool>",
606 "created_on" : "<date_time_created>",
607 "created_on" : "<date_time_created>",
607 "description" : "<description>",
608 "description" : "<description>",
608 "landing_rev": "<landing_rev>",
609 "landing_rev": "<landing_rev>",
609 "last_changeset": {
610 "last_changeset": {
610 "author": "<full_author>",
611 "author": "<full_author>",
611 "date": "<date_time_of_commit>",
612 "date": "<date_time_of_commit>",
612 "message": "<commit_message>",
613 "message": "<commit_message>",
613 "raw_id": "<raw_id>",
614 "raw_id": "<raw_id>",
614 "revision": "<numeric_revision>",
615 "revision": "<numeric_revision>",
615 "short_id": "<short_id>"
616 "short_id": "<short_id>"
616 },
617 },
617 "owner": "<repo_owner>",
618 "owner": "<repo_owner>",
618 "fork_of": "<name_of_fork_parent>",
619 "fork_of": "<name_of_fork_parent>",
619 "members" : [
620 "members" : [
620 {
621 {
621 "type": "user",
622 "type": "user",
622 "user_id" : "<user_id>",
623 "user_id" : "<user_id>",
623 "api_key" : "<api_key>",
624 "api_key" : "<api_key>",
624 "username" : "<username>",
625 "username" : "<username>",
625 "firstname": "<firstname>",
626 "firstname": "<firstname>",
626 "lastname" : "<lastname>",
627 "lastname" : "<lastname>",
627 "email" : "<email>",
628 "email" : "<email>",
628 "emails": "<list_of_all_additional_emails>",
629 "emails": "<list_of_all_additional_emails>",
629 "active" : "<bool>",
630 "active" : "<bool>",
630 "admin" :Β  "<bool>",
631 "admin" :Β  "<bool>",
631 "ldap_dn" : "<ldap_dn>",
632 "ldap_dn" : "<ldap_dn>",
632 "last_login": "<last_login>",
633 "last_login": "<last_login>",
633 "permission" : "repository.(read|write|admin)"
634 "permission" : "repository.(read|write|admin)"
634 },
635 },
635 …
636 …
636 {
637 {
637 "type": "users_group",
638 "type": "users_group",
638 "id" : "<usersgroupid>",
639 "id" : "<usersgroupid>",
639 "name" : "<usersgroupname>",
640 "name" : "<usersgroupname>",
640 "active": "<bool>",
641 "active": "<bool>",
641 "permission" : "repository.(read|write|admin)"
642 "permission" : "repository.(read|write|admin)"
642 },
643 },
643 …
644 …
644 ],
645 ],
645 "followers": [
646 "followers": [
646 {
647 {
647 "user_id" : "<user_id>",
648 "user_id" : "<user_id>",
648 "username" : "<username>",
649 "username" : "<username>",
649 "api_key" : "<api_key>",
650 "api_key" : "<api_key>",
650 "firstname": "<firstname>",
651 "firstname": "<firstname>",
651 "lastname" : "<lastname>",
652 "lastname" : "<lastname>",
652 "email" : "<email>",
653 "email" : "<email>",
653 "emails": "<list_of_all_additional_emails>",
654 "emails": "<list_of_all_additional_emails>",
654 "ip_addresses": "<list_of_ip_addresses_for_user>",
655 "ip_addresses": "<list_of_ip_addresses_for_user>",
655 "active" : "<bool>",
656 "active" : "<bool>",
656 "admin" :Β  "<bool>",
657 "admin" :Β  "<bool>",
657 "ldap_dn" : "<ldap_dn>",
658 "ldap_dn" : "<ldap_dn>",
658 "last_login": "<last_login>",
659 "last_login": "<last_login>",
659 },
660 },
660 …
661 …
661 ],
662 ],
662 <if with_revision_names == True>
663 <if with_revision_names == True>
663 "tags": {
664 "tags": {
664 "<tagname>": "<raw_id>",
665 "<tagname>": "<raw_id>",
665 ...
666 ...
666 },
667 },
667 "branches": {
668 "branches": {
668 "<branchname>": "<raw_id>",
669 "<branchname>": "<raw_id>",
669 ...
670 ...
670 },
671 },
671 "bookmarks": {
672 "bookmarks": {
672 "<bookmarkname>": "<raw_id>",
673 "<bookmarkname>": "<raw_id>",
673 ...
674 ...
674 },
675 },
676 <if with_pullrequests == True>
677 "pull_requests": [
678 {
679 "status": "<pull_request_status>",
680 "pull_request_id": <pull_request_id>,
681 "description": "<pull_request_description>",
682 "title": "<pull_request_title>",
683 "url": "<pull_request_url>",
684 "reviewers": [
685 {
686 "username": "<user_id>",
687 },
688 ...
689 ],
690 "org_repo_url": "<repo_url>",
691 "org_ref_parts": [
692 "<ref_type>",
693 "<ref_name>",
694 "<raw_id>"
695 ],
696 "other_ref_parts": [
697 "<ref_type>",
698 "<ref_name>",
699 "<raw_id>"
700 ],
701 "comments": [
702 {
703 "username": "<user_id>",
704 "text": "<comment text>",
705 "comment_id": "<comment_id>",
706 },
707 ...
708 ],
709 "owner": "<username>",
710 "statuses": [
711 {
712 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
713 "reviewer": "<user_id>",
714 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
715 },
716 ...
717 ],
718 "revisions": [
719 "<raw_id>",
720 ...
721 ]
722 },
723 ...
724 ]
675 }
725 }
676 error: null
726 error: null
677
727
678 get_repos
728 get_repos
679 ^^^^^^^^^
729 ^^^^^^^^^
680
730
681 List all existing repositories.
731 List all existing repositories.
682 This command can only be executed using the api_key of a user with admin rights,
732 This command can only be executed using the api_key of a user with admin rights,
683 or that of a regular user with at least read access to the repository.
733 or that of a regular user with at least read access to the repository.
684
734
685 INPUT::
735 INPUT::
686
736
687 id : <id_for_response>
737 id : <id_for_response>
688 api_key : "<api_key>"
738 api_key : "<api_key>"
689 method : "get_repos"
739 method : "get_repos"
690 args: { }
740 args: { }
691
741
692 OUTPUT::
742 OUTPUT::
693
743
694 id : <id_given_in_input>
744 id : <id_given_in_input>
695 result: [
745 result: [
696 {
746 {
697 "repo_id" : "<repo_id>",
747 "repo_id" : "<repo_id>",
698 "repo_name" : "<reponame>"
748 "repo_name" : "<reponame>"
699 "repo_type" : "<repo_type>",
749 "repo_type" : "<repo_type>",
700 "clone_uri" : "<clone_uri>",
750 "clone_uri" : "<clone_uri>",
701 "private" : "<bool>",
751 "private" : "<bool>",
702 "created_on" : "<datetimecreated>",
752 "created_on" : "<datetimecreated>",
703 "description" : "<description>",
753 "description" : "<description>",
704 "landing_rev": "<landing_rev>",
754 "landing_rev": "<landing_rev>",
705 "owner": "<repo_owner>",
755 "owner": "<repo_owner>",
706 "fork_of": "<name_of_fork_parent>",
756 "fork_of": "<name_of_fork_parent>",
707 "enable_downloads": "<bool>",
757 "enable_downloads": "<bool>",
708 "enable_locking": "<bool>",
758 "enable_locking": "<bool>",
709 "enable_statistics": "<bool>",
759 "enable_statistics": "<bool>",
710 },
760 },
711 …
761 …
712 ]
762 ]
713 error: null
763 error: null
714
764
715 get_repo_nodes
765 get_repo_nodes
716 ^^^^^^^^^^^^^^
766 ^^^^^^^^^^^^^^
717
767
718 Return a list of files and directories for a given path at the given revision.
768 Return a list of files and directories for a given path at the given revision.
719 It is possible to specify ret_type to show only ``files`` or ``dirs``.
769 It is possible to specify ret_type to show only ``files`` or ``dirs``.
720 This command can only be executed using the api_key of a user with admin rights.
770 This command can only be executed using the api_key of a user with admin rights.
721
771
722 INPUT::
772 INPUT::
723
773
724 id : <id_for_response>
774 id : <id_for_response>
725 api_key : "<api_key>"
775 api_key : "<api_key>"
726 method : "get_repo_nodes"
776 method : "get_repo_nodes"
727 args: {
777 args: {
728 "repoid" : "<reponame or repo_id>"
778 "repoid" : "<reponame or repo_id>"
729 "revision" : "<revision>",
779 "revision" : "<revision>",
730 "root_path" : "<root_path>",
780 "root_path" : "<root_path>",
731 "ret_type" : "<ret_type> = Optional('all')"
781 "ret_type" : "<ret_type> = Optional('all')"
732 }
782 }
733
783
734 OUTPUT::
784 OUTPUT::
735
785
736 id : <id_given_in_input>
786 id : <id_given_in_input>
737 result: [
787 result: [
738 {
788 {
739 "name" : "<name>"
789 "name" : "<name>"
740 "type" : "<type>",
790 "type" : "<type>",
741 },
791 },
742 …
792 …
743 ]
793 ]
744 error: null
794 error: null
745
795
746 create_repo
796 create_repo
747 ^^^^^^^^^^^
797 ^^^^^^^^^^^
748
798
749 Create a repository. If the repository name contains "/", all needed repository
799 Create a repository. If the repository name contains "/", all needed repository
750 groups will be created. For example "foo/bar/baz" will create repository groups
800 groups will be created. For example "foo/bar/baz" will create repository groups
751 "foo", "bar" (with "foo" as parent), and create "baz" repository with
801 "foo", "bar" (with "foo" as parent), and create "baz" repository with
752 "bar" as group.
802 "bar" as group.
753 This command can only be executed using the api_key of a user with admin rights,
803 This command can only be executed using the api_key of a user with admin rights,
754 or that of a regular user with create repository permission.
804 or that of a regular user with create repository permission.
755 Regular users cannot specify owner parameter.
805 Regular users cannot specify owner parameter.
756
806
757 INPUT::
807 INPUT::
758
808
759 id : <id_for_response>
809 id : <id_for_response>
760 api_key : "<api_key>"
810 api_key : "<api_key>"
761 method : "create_repo"
811 method : "create_repo"
762 args: {
812 args: {
763 "repo_name" : "<reponame>",
813 "repo_name" : "<reponame>",
764 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
814 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
765 "repo_type" : "<repo_type> = Optional('hg')",
815 "repo_type" : "<repo_type> = Optional('hg')",
766 "description" : "<description> = Optional('')",
816 "description" : "<description> = Optional('')",
767 "private" : "<bool> = Optional(False)",
817 "private" : "<bool> = Optional(False)",
768 "clone_uri" : "<clone_uri> = Optional(None)",
818 "clone_uri" : "<clone_uri> = Optional(None)",
769 "landing_rev" : "<landing_rev> = Optional('tip')",
819 "landing_rev" : "<landing_rev> = Optional('tip')",
770 "enable_downloads": "<bool> = Optional(False)",
820 "enable_downloads": "<bool> = Optional(False)",
771 "enable_locking": "<bool> = Optional(False)",
821 "enable_locking": "<bool> = Optional(False)",
772 "enable_statistics": "<bool> = Optional(False)",
822 "enable_statistics": "<bool> = Optional(False)",
773 }
823 }
774
824
775 OUTPUT::
825 OUTPUT::
776
826
777 id : <id_given_in_input>
827 id : <id_given_in_input>
778 result: {
828 result: {
779 "msg": "Created new repository `<reponame>`",
829 "msg": "Created new repository `<reponame>`",
780 "repo": {
830 "repo": {
781 "repo_id" : "<repo_id>",
831 "repo_id" : "<repo_id>",
782 "repo_name" : "<reponame>"
832 "repo_name" : "<reponame>"
783 "repo_type" : "<repo_type>",
833 "repo_type" : "<repo_type>",
784 "clone_uri" : "<clone_uri>",
834 "clone_uri" : "<clone_uri>",
785 "private" : "<bool>",
835 "private" : "<bool>",
786 "created_on" : "<datetimecreated>",
836 "created_on" : "<datetimecreated>",
787 "description" : "<description>",
837 "description" : "<description>",
788 "landing_rev": "<landing_rev>",
838 "landing_rev": "<landing_rev>",
789 "owner": "<username or user_id>",
839 "owner": "<username or user_id>",
790 "fork_of": "<name_of_fork_parent>",
840 "fork_of": "<name_of_fork_parent>",
791 "enable_downloads": "<bool>",
841 "enable_downloads": "<bool>",
792 "enable_locking": "<bool>",
842 "enable_locking": "<bool>",
793 "enable_statistics": "<bool>",
843 "enable_statistics": "<bool>",
794 },
844 },
795 }
845 }
796 error: null
846 error: null
797
847
798 update_repo
848 update_repo
799 ^^^^^^^^^^^
849 ^^^^^^^^^^^
800
850
801 Update a repository.
851 Update a repository.
802 This command can only be executed using the api_key of a user with admin rights,
852 This command can only be executed using the api_key of a user with admin rights,
803 or that of a regular user with create repository permission.
853 or that of a regular user with create repository permission.
804 Regular users cannot specify owner parameter.
854 Regular users cannot specify owner parameter.
805
855
806 INPUT::
856 INPUT::
807
857
808 id : <id_for_response>
858 id : <id_for_response>
809 api_key : "<api_key>"
859 api_key : "<api_key>"
810 method : "update_repo"
860 method : "update_repo"
811 args: {
861 args: {
812 "repoid" : "<reponame or repo_id>"
862 "repoid" : "<reponame or repo_id>"
813 "name" : "<reponame> = Optional('')",
863 "name" : "<reponame> = Optional('')",
814 "group" : "<group_id> = Optional(None)",
864 "group" : "<group_id> = Optional(None)",
815 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
865 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
816 "description" : "<description> = Optional('')",
866 "description" : "<description> = Optional('')",
817 "private" : "<bool> = Optional(False)",
867 "private" : "<bool> = Optional(False)",
818 "clone_uri" : "<clone_uri> = Optional(None)",
868 "clone_uri" : "<clone_uri> = Optional(None)",
819 "landing_rev" : "<landing_rev> = Optional('tip')",
869 "landing_rev" : "<landing_rev> = Optional('tip')",
820 "enable_downloads": "<bool> = Optional(False)",
870 "enable_downloads": "<bool> = Optional(False)",
821 "enable_locking": "<bool> = Optional(False)",
871 "enable_locking": "<bool> = Optional(False)",
822 "enable_statistics": "<bool> = Optional(False)",
872 "enable_statistics": "<bool> = Optional(False)",
823 }
873 }
824
874
825 OUTPUT::
875 OUTPUT::
826
876
827 id : <id_given_in_input>
877 id : <id_given_in_input>
828 result: {
878 result: {
829 "msg": "updated repo ID:repo_id `<reponame>`",
879 "msg": "updated repo ID:repo_id `<reponame>`",
830 "repository": {
880 "repository": {
831 "repo_id" : "<repo_id>",
881 "repo_id" : "<repo_id>",
832 "repo_name" : "<reponame>"
882 "repo_name" : "<reponame>"
833 "repo_type" : "<repo_type>",
883 "repo_type" : "<repo_type>",
834 "clone_uri" : "<clone_uri>",
884 "clone_uri" : "<clone_uri>",
835 "private": "<bool>",
885 "private": "<bool>",
836 "created_on" : "<datetimecreated>",
886 "created_on" : "<datetimecreated>",
837 "description" : "<description>",
887 "description" : "<description>",
838 "landing_rev": "<landing_rev>",
888 "landing_rev": "<landing_rev>",
839 "owner": "<username or user_id>",
889 "owner": "<username or user_id>",
840 "fork_of": "<name_of_fork_parent>",
890 "fork_of": "<name_of_fork_parent>",
841 "enable_downloads": "<bool>",
891 "enable_downloads": "<bool>",
842 "enable_locking": "<bool>",
892 "enable_locking": "<bool>",
843 "enable_statistics": "<bool>",
893 "enable_statistics": "<bool>",
844 "last_changeset": {
894 "last_changeset": {
845 "author": "<full_author>",
895 "author": "<full_author>",
846 "date": "<date_time_of_commit>",
896 "date": "<date_time_of_commit>",
847 "message": "<commit_message>",
897 "message": "<commit_message>",
848 "raw_id": "<raw_id>",
898 "raw_id": "<raw_id>",
849 "revision": "<numeric_revision>",
899 "revision": "<numeric_revision>",
850 "short_id": "<short_id>"
900 "short_id": "<short_id>"
851 }
901 }
852 "locked_by": "<username>",
902 "locked_by": "<username>",
853 "locked_date": "<float lock_time>",
903 "locked_date": "<float lock_time>",
854 },
904 },
855 }
905 }
856 error: null
906 error: null
857
907
858 fork_repo
908 fork_repo
859 ^^^^^^^^^
909 ^^^^^^^^^
860
910
861 Create a fork of the given repo. If using Celery, this will
911 Create a fork of the given repo. If using Celery, this will
862 return success message immediately and a fork will be created
912 return success message immediately and a fork will be created
863 asynchronously.
913 asynchronously.
864 This command can only be executed using the api_key of a user with admin
914 This command can only be executed using the api_key of a user with admin
865 rights, or with the global fork permission, by a regular user with create
915 rights, or with the global fork permission, by a regular user with create
866 repository permission and at least read access to the repository.
916 repository permission and at least read access to the repository.
867 Regular users cannot specify owner parameter.
917 Regular users cannot specify owner parameter.
868
918
869 INPUT::
919 INPUT::
870
920
871 id : <id_for_response>
921 id : <id_for_response>
872 api_key : "<api_key>"
922 api_key : "<api_key>"
873 method : "fork_repo"
923 method : "fork_repo"
874 args: {
924 args: {
875 "repoid" : "<reponame or repo_id>",
925 "repoid" : "<reponame or repo_id>",
876 "fork_name": "<forkname>",
926 "fork_name": "<forkname>",
877 "owner": "<username or user_id = Optional(=apiuser)>",
927 "owner": "<username or user_id = Optional(=apiuser)>",
878 "description": "<description>",
928 "description": "<description>",
879 "copy_permissions": "<bool>",
929 "copy_permissions": "<bool>",
880 "private": "<bool>",
930 "private": "<bool>",
881 "landing_rev": "<landing_rev>"
931 "landing_rev": "<landing_rev>"
882
932
883 }
933 }
884
934
885 OUTPUT::
935 OUTPUT::
886
936
887 id : <id_given_in_input>
937 id : <id_given_in_input>
888 result: {
938 result: {
889 "msg": "Created fork of `<reponame>` as `<forkname>`",
939 "msg": "Created fork of `<reponame>` as `<forkname>`",
890 "success": true
940 "success": true
891 }
941 }
892 error: null
942 error: null
893
943
894 delete_repo
944 delete_repo
895 ^^^^^^^^^^^
945 ^^^^^^^^^^^
896
946
897 Delete a repository.
947 Delete a repository.
898 This command can only be executed using the api_key of a user with admin rights,
948 This command can only be executed using the api_key of a user with admin rights,
899 or that of a regular user with admin access to the repository.
949 or that of a regular user with admin access to the repository.
900 When ``forks`` param is set it is possible to detach or delete forks of the deleted repository.
950 When ``forks`` param is set it is possible to detach or delete forks of the deleted repository.
901
951
902 INPUT::
952 INPUT::
903
953
904 id : <id_for_response>
954 id : <id_for_response>
905 api_key : "<api_key>"
955 api_key : "<api_key>"
906 method : "delete_repo"
956 method : "delete_repo"
907 args: {
957 args: {
908 "repoid" : "<reponame or repo_id>",
958 "repoid" : "<reponame or repo_id>",
909 "forks" : "`delete` or `detach` = Optional(None)"
959 "forks" : "`delete` or `detach` = Optional(None)"
910 }
960 }
911
961
912 OUTPUT::
962 OUTPUT::
913
963
914 id : <id_given_in_input>
964 id : <id_given_in_input>
915 result: {
965 result: {
916 "msg": "Deleted repository `<reponame>`",
966 "msg": "Deleted repository `<reponame>`",
917 "success": true
967 "success": true
918 }
968 }
919 error: null
969 error: null
920
970
921 grant_user_permission
971 grant_user_permission
922 ^^^^^^^^^^^^^^^^^^^^^
972 ^^^^^^^^^^^^^^^^^^^^^
923
973
924 Grant permission for a user on the given repository, or update the existing one if found.
974 Grant permission for a user on the given repository, or update the existing one if found.
925 This command can only be executed using the api_key of a user with admin rights.
975 This command can only be executed using the api_key of a user with admin rights.
926
976
927 INPUT::
977 INPUT::
928
978
929 id : <id_for_response>
979 id : <id_for_response>
930 api_key : "<api_key>"
980 api_key : "<api_key>"
931 method : "grant_user_permission"
981 method : "grant_user_permission"
932 args: {
982 args: {
933 "repoid" : "<reponame or repo_id>"
983 "repoid" : "<reponame or repo_id>"
934 "userid" : "<username or user_id>"
984 "userid" : "<username or user_id>"
935 "perm" : "(repository.(none|read|write|admin))",
985 "perm" : "(repository.(none|read|write|admin))",
936 }
986 }
937
987
938 OUTPUT::
988 OUTPUT::
939
989
940 id : <id_given_in_input>
990 id : <id_given_in_input>
941 result: {
991 result: {
942 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
992 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
943 "success": true
993 "success": true
944 }
994 }
945 error: null
995 error: null
946
996
947 revoke_user_permission
997 revoke_user_permission
948 ^^^^^^^^^^^^^^^^^^^^^^
998 ^^^^^^^^^^^^^^^^^^^^^^
949
999
950 Revoke permission for a user on the given repository.
1000 Revoke permission for a user on the given repository.
951 This command can only be executed using the api_key of a user with admin rights.
1001 This command can only be executed using the api_key of a user with admin rights.
952
1002
953 INPUT::
1003 INPUT::
954
1004
955 id : <id_for_response>
1005 id : <id_for_response>
956 api_key : "<api_key>"
1006 api_key : "<api_key>"
957 method : "revoke_user_permission"
1007 method : "revoke_user_permission"
958 args: {
1008 args: {
959 "repoid" : "<reponame or repo_id>"
1009 "repoid" : "<reponame or repo_id>"
960 "userid" : "<username or user_id>"
1010 "userid" : "<username or user_id>"
961 }
1011 }
962
1012
963 OUTPUT::
1013 OUTPUT::
964
1014
965 id : <id_given_in_input>
1015 id : <id_given_in_input>
966 result: {
1016 result: {
967 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1017 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
968 "success": true
1018 "success": true
969 }
1019 }
970 error: null
1020 error: null
971
1021
972 grant_user_group_permission
1022 grant_user_group_permission
973 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1023 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
974
1024
975 Grant permission for a user group on the given repository, or update the
1025 Grant permission for a user group on the given repository, or update the
976 existing one if found.
1026 existing one if found.
977 This command can only be executed using the api_key of a user with admin rights.
1027 This command can only be executed using the api_key of a user with admin rights.
978
1028
979 INPUT::
1029 INPUT::
980
1030
981 id : <id_for_response>
1031 id : <id_for_response>
982 api_key : "<api_key>"
1032 api_key : "<api_key>"
983 method : "grant_user_group_permission"
1033 method : "grant_user_group_permission"
984 args: {
1034 args: {
985 "repoid" : "<reponame or repo_id>"
1035 "repoid" : "<reponame or repo_id>"
986 "usersgroupid" : "<user group id or name>"
1036 "usersgroupid" : "<user group id or name>"
987 "perm" : "(repository.(none|read|write|admin))",
1037 "perm" : "(repository.(none|read|write|admin))",
988 }
1038 }
989
1039
990 OUTPUT::
1040 OUTPUT::
991
1041
992 id : <id_given_in_input>
1042 id : <id_given_in_input>
993 result: {
1043 result: {
994 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1044 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
995 "success": true
1045 "success": true
996 }
1046 }
997 error: null
1047 error: null
998
1048
999 revoke_user_group_permission
1049 revoke_user_group_permission
1000 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1050 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1001
1051
1002 Revoke permission for a user group on the given repository.
1052 Revoke permission for a user group on the given repository.
1003 This command can only be executed using the api_key of a user with admin rights.
1053 This command can only be executed using the api_key of a user with admin rights.
1004
1054
1005 INPUT::
1055 INPUT::
1006
1056
1007 id : <id_for_response>
1057 id : <id_for_response>
1008 api_key : "<api_key>"
1058 api_key : "<api_key>"
1009 method : "revoke_user_group_permission"
1059 method : "revoke_user_group_permission"
1010 args: {
1060 args: {
1011 "repoid" : "<reponame or repo_id>"
1061 "repoid" : "<reponame or repo_id>"
1012 "usersgroupid" : "<user group id or name>"
1062 "usersgroupid" : "<user group id or name>"
1013 }
1063 }
1014
1064
1015 OUTPUT::
1065 OUTPUT::
1016
1066
1017 id : <id_given_in_input>
1067 id : <id_given_in_input>
1018 result: {
1068 result: {
1019 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1069 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1020 "success": true
1070 "success": true
1021 }
1071 }
1022 error: null
1072 error: null
1023
1073
1024 get_changeset
1074 get_changeset
1025 ^^^^^^^^^^^^^
1075 ^^^^^^^^^^^^^
1026
1076
1027 Get information and review status for a given changeset. This command can only
1077 Get information and review status for a given changeset. This command can only
1028 be executed using the api_key of a user with read permissions to the
1078 be executed using the api_key of a user with read permissions to the
1029 repository.
1079 repository.
1030
1080
1031 INPUT::
1081 INPUT::
1032
1082
1033 id : <id_for_response>
1083 id : <id_for_response>
1034 api_key : "<api_key>"
1084 api_key : "<api_key>"
1035 method : "get_changeset"
1085 method : "get_changeset"
1036 args: {
1086 args: {
1037 "repoid" : "<reponame or repo_id>",
1087 "repoid" : "<reponame or repo_id>",
1038 "raw_id" : "<raw_id>",
1088 "raw_id" : "<raw_id>",
1039 "with_reviews": "<bool> = Optional(False)"
1089 "with_reviews": "<bool> = Optional(False)"
1040 }
1090 }
1041
1091
1042 OUTPUT::
1092 OUTPUT::
1043
1093
1044 id : <id_given_in_input>
1094 id : <id_given_in_input>
1045 result: {
1095 result: {
1046 "author": "<full_author>",
1096 "author": "<full_author>",
1047 "date": "<date_time_of_commit>",
1097 "date": "<date_time_of_commit>",
1048 "message": "<commit_message>",
1098 "message": "<commit_message>",
1049 "raw_id": "<raw_id>",
1099 "raw_id": "<raw_id>",
1050 "revision": "<numeric_revision>",
1100 "revision": "<numeric_revision>",
1051 "short_id": "<short_id>",
1101 "short_id": "<short_id>",
1052 "reviews": [{
1102 "reviews": [{
1053 "reviewer": "<username>",
1103 "reviewer": "<username>",
1054 "modified_at": "<date_time_of_review>", # iso 8601 date, server's timezone
1104 "modified_at": "<date_time_of_review>", # iso 8601 date, server's timezone
1055 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1105 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1056 },
1106 },
1057 ...
1107 ...
1058 ]
1108 ]
1059 }
1109 }
1060 error: null
1110 error: null
1061
1111
1062 Example output::
1112 Example output::
1063
1113
1064 {
1114 {
1065 "id" : 1,
1115 "id" : 1,
1066 "error" : null,
1116 "error" : null,
1067 "result" : {
1117 "result" : {
1068 "author" : {
1118 "author" : {
1069 "email" : "user@example.com",
1119 "email" : "user@example.com",
1070 "name" : "Kallithea Admin"
1120 "name" : "Kallithea Admin"
1071 },
1121 },
1072 "changed" : [],
1122 "changed" : [],
1073 "short_id" : "e1022d3d28df",
1123 "short_id" : "e1022d3d28df",
1074 "date" : "2017-03-28T09:09:03",
1124 "date" : "2017-03-28T09:09:03",
1075 "added" : [
1125 "added" : [
1076 "README.rst"
1126 "README.rst"
1077 ],
1127 ],
1078 "removed" : [],
1128 "removed" : [],
1079 "revision" : 0,
1129 "revision" : 0,
1080 "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1130 "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1081 "message" : "Added file via Kallithea",
1131 "message" : "Added file via Kallithea",
1082 "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1132 "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1083 "reviews" : [
1133 "reviews" : [
1084 {
1134 {
1085 "status" : "under_review",
1135 "status" : "under_review",
1086 "modified_at" : "2017-03-28T09:17:08.618",
1136 "modified_at" : "2017-03-28T09:17:08.618",
1087 "reviewer" : "user"
1137 "reviewer" : "user"
1088 }
1138 }
1089 ]
1139 ]
1090 }
1140 }
1091 }
1141 }
1092
1142
1093
1143
1094 API access for web views
1144 API access for web views
1095 ------------------------
1145 ------------------------
1096
1146
1097 API access can also be turned on for each web view in Kallithea that is
1147 API access can also be turned on for each web view in Kallithea that is
1098 decorated with the ``@LoginRequired`` decorator. Some views use
1148 decorated with the ``@LoginRequired`` decorator. Some views use
1099 ``@LoginRequired(api_access=True)`` and are always available. By default only
1149 ``@LoginRequired(api_access=True)`` and are always available. By default only
1100 RSS/Atom feed views are enabled. Other views are
1150 RSS/Atom feed views are enabled. Other views are
1101 only available if they have been whitelisted. Edit the
1151 only available if they have been whitelisted. Edit the
1102 ``api_access_controllers_whitelist`` option in your .ini file and define views
1152 ``api_access_controllers_whitelist`` option in your .ini file and define views
1103 that should have API access enabled.
1153 that should have API access enabled.
1104
1154
1105 For example, to enable API access to patch/diff, raw file and archive::
1155 For example, to enable API access to patch/diff, raw file and archive::
1106
1156
1107 api_access_controllers_whitelist =
1157 api_access_controllers_whitelist =
1108 ChangesetController:changeset_patch,
1158 ChangesetController:changeset_patch,
1109 ChangesetController:changeset_raw,
1159 ChangesetController:changeset_raw,
1110 FilesController:raw,
1160 FilesController:raw,
1111 FilesController:archivefile
1161 FilesController:archivefile
1112
1162
1113 After this change, a Kallithea view can be accessed without login using
1163 After this change, a Kallithea view can be accessed without login using
1114 bearer authentication, by including this header with the request::
1164 bearer authentication, by including this header with the request::
1115
1165
1116 Authentication: Bearer <api_key>
1166 Authentication: Bearer <api_key>
1117
1167
1118 Alternatively, the API key can be passed in the URL query string using
1168 Alternatively, the API key can be passed in the URL query string using
1119 ``?api_key=<api_key>``, though this is not recommended due to the increased
1169 ``?api_key=<api_key>``, though this is not recommended due to the increased
1120 risk of API key leaks, and support will likely be removed in the future.
1170 risk of API key leaks, and support will likely be removed in the future.
1121
1171
1122 Exposing raw diffs is a good way to integrate with
1172 Exposing raw diffs is a good way to integrate with
1123 third-party services like code review, or build farms that can download archives.
1173 third-party services like code review, or build farms that can download archives.
@@ -1,2506 +1,2508 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.api.api
15 kallithea.controllers.api.api
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 API controller for Kallithea
18 API controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Aug 20, 2011
22 :created_on: Aug 20, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from sqlalchemy import or_
31 from sqlalchemy import or_
32
32
33 from tg import request
33 from tg import request
34
34
35 from kallithea.controllers.api import JSONRPCController, JSONRPCError
35 from kallithea.controllers.api import JSONRPCController, JSONRPCError
36 from kallithea.lib.auth import (
36 from kallithea.lib.auth import (
37 PasswordGenerator, AuthUser, HasPermissionAnyDecorator,
37 PasswordGenerator, AuthUser, HasPermissionAnyDecorator,
38 HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionLevel,
38 HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionLevel,
39 HasRepoGroupPermissionLevel, HasUserGroupPermissionLevel)
39 HasRepoGroupPermissionLevel, HasUserGroupPermissionLevel)
40 from kallithea.lib.utils import map_groups, repo2db_mapper
40 from kallithea.lib.utils import map_groups, repo2db_mapper
41 from kallithea.lib.utils2 import (
41 from kallithea.lib.utils2 import (
42 str2bool, time_to_datetime, safe_int, Optional, OAttr)
42 str2bool, time_to_datetime, safe_int, Optional, OAttr)
43 from kallithea.model.meta import Session
43 from kallithea.model.meta import Session
44 from kallithea.model.repo_group import RepoGroupModel
44 from kallithea.model.repo_group import RepoGroupModel
45 from kallithea.model.scm import ScmModel, UserGroupList
45 from kallithea.model.scm import ScmModel, UserGroupList
46 from kallithea.model.repo import RepoModel
46 from kallithea.model.repo import RepoModel
47 from kallithea.model.user import UserModel
47 from kallithea.model.user import UserModel
48 from kallithea.model.user_group import UserGroupModel
48 from kallithea.model.user_group import UserGroupModel
49 from kallithea.model.gist import GistModel
49 from kallithea.model.gist import GistModel
50 from kallithea.model.changeset_status import ChangesetStatusModel
50 from kallithea.model.changeset_status import ChangesetStatusModel
51 from kallithea.model.db import (
51 from kallithea.model.db import (
52 Repository, Setting, UserIpMap, Permission, User, Gist,
52 Repository, Setting, UserIpMap, Permission, User, Gist,
53 RepoGroup, UserGroup)
53 RepoGroup, UserGroup)
54 from kallithea.lib.compat import json
54 from kallithea.lib.compat import json
55 from kallithea.lib.exceptions import (
55 from kallithea.lib.exceptions import (
56 DefaultUserException, UserGroupsAssignedException)
56 DefaultUserException, UserGroupsAssignedException)
57 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
57 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
58 from kallithea.lib.vcs.backends.base import EmptyChangeset
58 from kallithea.lib.vcs.backends.base import EmptyChangeset
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 def store_update(updates, attr, name):
63 def store_update(updates, attr, name):
64 """
64 """
65 Stores param in updates dict if it's not instance of Optional
65 Stores param in updates dict if it's not instance of Optional
66 allows easy updates of passed in params
66 allows easy updates of passed in params
67 """
67 """
68 if not isinstance(attr, Optional):
68 if not isinstance(attr, Optional):
69 updates[name] = attr
69 updates[name] = attr
70
70
71
71
72 def get_user_or_error(userid):
72 def get_user_or_error(userid):
73 """
73 """
74 Get user by id or name or return JsonRPCError if not found
74 Get user by id or name or return JsonRPCError if not found
75
75
76 :param userid:
76 :param userid:
77 """
77 """
78 user = UserModel().get_user(userid)
78 user = UserModel().get_user(userid)
79 if user is None:
79 if user is None:
80 raise JSONRPCError("user `%s` does not exist" % (userid,))
80 raise JSONRPCError("user `%s` does not exist" % (userid,))
81 return user
81 return user
82
82
83
83
84 def get_repo_or_error(repoid):
84 def get_repo_or_error(repoid):
85 """
85 """
86 Get repo by id or name or return JsonRPCError if not found
86 Get repo by id or name or return JsonRPCError if not found
87
87
88 :param repoid:
88 :param repoid:
89 """
89 """
90 repo = RepoModel().get_repo(repoid)
90 repo = RepoModel().get_repo(repoid)
91 if repo is None:
91 if repo is None:
92 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
92 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
93 return repo
93 return repo
94
94
95
95
96 def get_repo_group_or_error(repogroupid):
96 def get_repo_group_or_error(repogroupid):
97 """
97 """
98 Get repo group by id or name or return JsonRPCError if not found
98 Get repo group by id or name or return JsonRPCError if not found
99
99
100 :param repogroupid:
100 :param repogroupid:
101 """
101 """
102 repo_group = RepoGroup.guess_instance(repogroupid)
102 repo_group = RepoGroup.guess_instance(repogroupid)
103 if repo_group is None:
103 if repo_group is None:
104 raise JSONRPCError(
104 raise JSONRPCError(
105 'repository group `%s` does not exist' % (repogroupid,))
105 'repository group `%s` does not exist' % (repogroupid,))
106 return repo_group
106 return repo_group
107
107
108
108
109 def get_user_group_or_error(usergroupid):
109 def get_user_group_or_error(usergroupid):
110 """
110 """
111 Get user group by id or name or return JsonRPCError if not found
111 Get user group by id or name or return JsonRPCError if not found
112
112
113 :param usergroupid:
113 :param usergroupid:
114 """
114 """
115 user_group = UserGroupModel().get_group(usergroupid)
115 user_group = UserGroupModel().get_group(usergroupid)
116 if user_group is None:
116 if user_group is None:
117 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
117 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
118 return user_group
118 return user_group
119
119
120
120
121 def get_perm_or_error(permid, prefix=None):
121 def get_perm_or_error(permid, prefix=None):
122 """
122 """
123 Get permission by id or name or return JsonRPCError if not found
123 Get permission by id or name or return JsonRPCError if not found
124
124
125 :param permid:
125 :param permid:
126 """
126 """
127 perm = Permission.get_by_key(permid)
127 perm = Permission.get_by_key(permid)
128 if perm is None:
128 if perm is None:
129 raise JSONRPCError('permission `%s` does not exist' % (permid,))
129 raise JSONRPCError('permission `%s` does not exist' % (permid,))
130 if prefix:
130 if prefix:
131 if not perm.permission_name.startswith(prefix):
131 if not perm.permission_name.startswith(prefix):
132 raise JSONRPCError('permission `%s` is invalid, '
132 raise JSONRPCError('permission `%s` is invalid, '
133 'should start with %s' % (permid, prefix))
133 'should start with %s' % (permid, prefix))
134 return perm
134 return perm
135
135
136
136
137 def get_gist_or_error(gistid):
137 def get_gist_or_error(gistid):
138 """
138 """
139 Get gist by id or gist_access_id or return JsonRPCError if not found
139 Get gist by id or gist_access_id or return JsonRPCError if not found
140
140
141 :param gistid:
141 :param gistid:
142 """
142 """
143 gist = GistModel().get_gist(gistid)
143 gist = GistModel().get_gist(gistid)
144 if gist is None:
144 if gist is None:
145 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
145 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
146 return gist
146 return gist
147
147
148
148
149 class ApiController(JSONRPCController):
149 class ApiController(JSONRPCController):
150 """
150 """
151 API Controller
151 API Controller
152
152
153 The authenticated user can be found as request.authuser.
153 The authenticated user can be found as request.authuser.
154
154
155 Example function::
155 Example function::
156
156
157 def func(arg1, arg2,...):
157 def func(arg1, arg2,...):
158 pass
158 pass
159
159
160 Each function should also **raise** JSONRPCError for any
160 Each function should also **raise** JSONRPCError for any
161 errors that happens.
161 errors that happens.
162 """
162 """
163
163
164 @HasPermissionAnyDecorator('hg.admin')
164 @HasPermissionAnyDecorator('hg.admin')
165 def test(self, args):
165 def test(self, args):
166 return args
166 return args
167
167
168 @HasPermissionAnyDecorator('hg.admin')
168 @HasPermissionAnyDecorator('hg.admin')
169 def pull(self, repoid):
169 def pull(self, repoid):
170 """
170 """
171 Triggers a pull from remote location on given repo. Can be used to
171 Triggers a pull from remote location on given repo. Can be used to
172 automatically keep remote repos up to date. This command can be executed
172 automatically keep remote repos up to date. This command can be executed
173 only using api_key belonging to user with admin rights
173 only using api_key belonging to user with admin rights
174
174
175 :param repoid: repository name or repository id
175 :param repoid: repository name or repository id
176 :type repoid: str or int
176 :type repoid: str or int
177
177
178 OUTPUT::
178 OUTPUT::
179
179
180 id : <id_given_in_input>
180 id : <id_given_in_input>
181 result : {
181 result : {
182 "msg": "Pulled from `<repository name>`"
182 "msg": "Pulled from `<repository name>`"
183 "repository": "<repository name>"
183 "repository": "<repository name>"
184 }
184 }
185 error : null
185 error : null
186
186
187 ERROR OUTPUT::
187 ERROR OUTPUT::
188
188
189 id : <id_given_in_input>
189 id : <id_given_in_input>
190 result : null
190 result : null
191 error : {
191 error : {
192 "Unable to pull changes from `<reponame>`"
192 "Unable to pull changes from `<reponame>`"
193 }
193 }
194
194
195 """
195 """
196
196
197 repo = get_repo_or_error(repoid)
197 repo = get_repo_or_error(repoid)
198
198
199 try:
199 try:
200 ScmModel().pull_changes(repo.repo_name,
200 ScmModel().pull_changes(repo.repo_name,
201 request.authuser.username)
201 request.authuser.username)
202 return dict(
202 return dict(
203 msg='Pulled from `%s`' % repo.repo_name,
203 msg='Pulled from `%s`' % repo.repo_name,
204 repository=repo.repo_name
204 repository=repo.repo_name
205 )
205 )
206 except Exception:
206 except Exception:
207 log.error(traceback.format_exc())
207 log.error(traceback.format_exc())
208 raise JSONRPCError(
208 raise JSONRPCError(
209 'Unable to pull changes from `%s`' % repo.repo_name
209 'Unable to pull changes from `%s`' % repo.repo_name
210 )
210 )
211
211
212 @HasPermissionAnyDecorator('hg.admin')
212 @HasPermissionAnyDecorator('hg.admin')
213 def rescan_repos(self, remove_obsolete=Optional(False)):
213 def rescan_repos(self, remove_obsolete=Optional(False)):
214 """
214 """
215 Triggers rescan repositories action. If remove_obsolete is set
215 Triggers rescan repositories action. If remove_obsolete is set
216 than also delete repos that are in database but not in the filesystem.
216 than also delete repos that are in database but not in the filesystem.
217 aka "clean zombies". This command can be executed only using api_key
217 aka "clean zombies". This command can be executed only using api_key
218 belonging to user with admin rights.
218 belonging to user with admin rights.
219
219
220 :param remove_obsolete: deletes repositories from
220 :param remove_obsolete: deletes repositories from
221 database that are not found on the filesystem
221 database that are not found on the filesystem
222 :type remove_obsolete: Optional(bool)
222 :type remove_obsolete: Optional(bool)
223
223
224 OUTPUT::
224 OUTPUT::
225
225
226 id : <id_given_in_input>
226 id : <id_given_in_input>
227 result : {
227 result : {
228 'added': [<added repository name>,...]
228 'added': [<added repository name>,...]
229 'removed': [<removed repository name>,...]
229 'removed': [<removed repository name>,...]
230 }
230 }
231 error : null
231 error : null
232
232
233 ERROR OUTPUT::
233 ERROR OUTPUT::
234
234
235 id : <id_given_in_input>
235 id : <id_given_in_input>
236 result : null
236 result : null
237 error : {
237 error : {
238 'Error occurred during rescan repositories action'
238 'Error occurred during rescan repositories action'
239 }
239 }
240
240
241 """
241 """
242
242
243 try:
243 try:
244 rm_obsolete = Optional.extract(remove_obsolete)
244 rm_obsolete = Optional.extract(remove_obsolete)
245 added, removed = repo2db_mapper(ScmModel().repo_scan(),
245 added, removed = repo2db_mapper(ScmModel().repo_scan(),
246 remove_obsolete=rm_obsolete)
246 remove_obsolete=rm_obsolete)
247 return {'added': added, 'removed': removed}
247 return {'added': added, 'removed': removed}
248 except Exception:
248 except Exception:
249 log.error(traceback.format_exc())
249 log.error(traceback.format_exc())
250 raise JSONRPCError(
250 raise JSONRPCError(
251 'Error occurred during rescan repositories action'
251 'Error occurred during rescan repositories action'
252 )
252 )
253
253
254 def invalidate_cache(self, repoid):
254 def invalidate_cache(self, repoid):
255 """
255 """
256 Invalidate cache for repository.
256 Invalidate cache for repository.
257 This command can be executed only using api_key belonging to user with admin
257 This command can be executed only using api_key belonging to user with admin
258 rights or regular user that have write or admin or write access to repository.
258 rights or regular user that have write or admin or write access to repository.
259
259
260 :param repoid: repository name or repository id
260 :param repoid: repository name or repository id
261 :type repoid: str or int
261 :type repoid: str or int
262
262
263 OUTPUT::
263 OUTPUT::
264
264
265 id : <id_given_in_input>
265 id : <id_given_in_input>
266 result : {
266 result : {
267 'msg': Cache for repository `<repository name>` was invalidated,
267 'msg': Cache for repository `<repository name>` was invalidated,
268 'repository': <repository name>
268 'repository': <repository name>
269 }
269 }
270 error : null
270 error : null
271
271
272 ERROR OUTPUT::
272 ERROR OUTPUT::
273
273
274 id : <id_given_in_input>
274 id : <id_given_in_input>
275 result : null
275 result : null
276 error : {
276 error : {
277 'Error occurred during cache invalidation action'
277 'Error occurred during cache invalidation action'
278 }
278 }
279
279
280 """
280 """
281 repo = get_repo_or_error(repoid)
281 repo = get_repo_or_error(repoid)
282 if not HasPermissionAny('hg.admin')():
282 if not HasPermissionAny('hg.admin')():
283 if not HasRepoPermissionLevel('write')(repo.repo_name):
283 if not HasRepoPermissionLevel('write')(repo.repo_name):
284 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
284 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
285
285
286 try:
286 try:
287 ScmModel().mark_for_invalidation(repo.repo_name)
287 ScmModel().mark_for_invalidation(repo.repo_name)
288 return dict(
288 return dict(
289 msg='Cache for repository `%s` was invalidated' % (repoid,),
289 msg='Cache for repository `%s` was invalidated' % (repoid,),
290 repository=repo.repo_name
290 repository=repo.repo_name
291 )
291 )
292 except Exception:
292 except Exception:
293 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
294 raise JSONRPCError(
294 raise JSONRPCError(
295 'Error occurred during cache invalidation action'
295 'Error occurred during cache invalidation action'
296 )
296 )
297
297
298 # permission check inside
298 # permission check inside
299 def lock(self, repoid, locked=Optional(None),
299 def lock(self, repoid, locked=Optional(None),
300 userid=Optional(OAttr('apiuser'))):
300 userid=Optional(OAttr('apiuser'))):
301 """
301 """
302 Set locking state on given repository by given user. If userid param
302 Set locking state on given repository by given user. If userid param
303 is skipped, then it is set to id of user who is calling this method.
303 is skipped, then it is set to id of user who is calling this method.
304 If locked param is skipped then function shows current lock state of
304 If locked param is skipped then function shows current lock state of
305 given repo. This command can be executed only using api_key belonging
305 given repo. This command can be executed only using api_key belonging
306 to user with admin rights or regular user that have admin or write
306 to user with admin rights or regular user that have admin or write
307 access to repository.
307 access to repository.
308
308
309 :param repoid: repository name or repository id
309 :param repoid: repository name or repository id
310 :type repoid: str or int
310 :type repoid: str or int
311 :param locked: lock state to be set
311 :param locked: lock state to be set
312 :type locked: Optional(bool)
312 :type locked: Optional(bool)
313 :param userid: set lock as user
313 :param userid: set lock as user
314 :type userid: Optional(str or int)
314 :type userid: Optional(str or int)
315
315
316 OUTPUT::
316 OUTPUT::
317
317
318 id : <id_given_in_input>
318 id : <id_given_in_input>
319 result : {
319 result : {
320 'repo': '<reponame>',
320 'repo': '<reponame>',
321 'locked': <bool: lock state>,
321 'locked': <bool: lock state>,
322 'locked_since': <int: lock timestamp>,
322 'locked_since': <int: lock timestamp>,
323 'locked_by': <username of person who made the lock>,
323 'locked_by': <username of person who made the lock>,
324 'lock_state_changed': <bool: True if lock state has been changed in this request>,
324 'lock_state_changed': <bool: True if lock state has been changed in this request>,
325 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
325 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
326 or
326 or
327 'msg': 'Repo `<repository name>` not locked.'
327 'msg': 'Repo `<repository name>` not locked.'
328 or
328 or
329 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
329 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
330 }
330 }
331 error : null
331 error : null
332
332
333 ERROR OUTPUT::
333 ERROR OUTPUT::
334
334
335 id : <id_given_in_input>
335 id : <id_given_in_input>
336 result : null
336 result : null
337 error : {
337 error : {
338 'Error occurred locking repository `<reponame>`
338 'Error occurred locking repository `<reponame>`
339 }
339 }
340
340
341 """
341 """
342 repo = get_repo_or_error(repoid)
342 repo = get_repo_or_error(repoid)
343 if HasPermissionAny('hg.admin')():
343 if HasPermissionAny('hg.admin')():
344 pass
344 pass
345 elif HasRepoPermissionLevel('write')(repo.repo_name):
345 elif HasRepoPermissionLevel('write')(repo.repo_name):
346 # make sure normal user does not pass someone else userid,
346 # make sure normal user does not pass someone else userid,
347 # he is not allowed to do that
347 # he is not allowed to do that
348 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
348 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
349 raise JSONRPCError(
349 raise JSONRPCError(
350 'userid is not the same as your user'
350 'userid is not the same as your user'
351 )
351 )
352 else:
352 else:
353 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
353 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
354
354
355 if isinstance(userid, Optional):
355 if isinstance(userid, Optional):
356 userid = request.authuser.user_id
356 userid = request.authuser.user_id
357
357
358 user = get_user_or_error(userid)
358 user = get_user_or_error(userid)
359
359
360 if isinstance(locked, Optional):
360 if isinstance(locked, Optional):
361 lockobj = Repository.getlock(repo)
361 lockobj = Repository.getlock(repo)
362
362
363 if lockobj[0] is None:
363 if lockobj[0] is None:
364 _d = {
364 _d = {
365 'repo': repo.repo_name,
365 'repo': repo.repo_name,
366 'locked': False,
366 'locked': False,
367 'locked_since': None,
367 'locked_since': None,
368 'locked_by': None,
368 'locked_by': None,
369 'lock_state_changed': False,
369 'lock_state_changed': False,
370 'msg': 'Repo `%s` not locked.' % repo.repo_name
370 'msg': 'Repo `%s` not locked.' % repo.repo_name
371 }
371 }
372 return _d
372 return _d
373 else:
373 else:
374 userid, time_ = lockobj
374 userid, time_ = lockobj
375 lock_user = get_user_or_error(userid)
375 lock_user = get_user_or_error(userid)
376 _d = {
376 _d = {
377 'repo': repo.repo_name,
377 'repo': repo.repo_name,
378 'locked': True,
378 'locked': True,
379 'locked_since': time_,
379 'locked_since': time_,
380 'locked_by': lock_user.username,
380 'locked_by': lock_user.username,
381 'lock_state_changed': False,
381 'lock_state_changed': False,
382 'msg': ('Repo `%s` locked by `%s` on `%s`.'
382 'msg': ('Repo `%s` locked by `%s` on `%s`.'
383 % (repo.repo_name, lock_user.username,
383 % (repo.repo_name, lock_user.username,
384 json.dumps(time_to_datetime(time_))))
384 json.dumps(time_to_datetime(time_))))
385 }
385 }
386 return _d
386 return _d
387
387
388 # force locked state through a flag
388 # force locked state through a flag
389 else:
389 else:
390 locked = str2bool(locked)
390 locked = str2bool(locked)
391 try:
391 try:
392 if locked:
392 if locked:
393 lock_time = time.time()
393 lock_time = time.time()
394 Repository.lock(repo, user.user_id, lock_time)
394 Repository.lock(repo, user.user_id, lock_time)
395 else:
395 else:
396 lock_time = None
396 lock_time = None
397 Repository.unlock(repo)
397 Repository.unlock(repo)
398 _d = {
398 _d = {
399 'repo': repo.repo_name,
399 'repo': repo.repo_name,
400 'locked': locked,
400 'locked': locked,
401 'locked_since': lock_time,
401 'locked_since': lock_time,
402 'locked_by': user.username,
402 'locked_by': user.username,
403 'lock_state_changed': True,
403 'lock_state_changed': True,
404 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
404 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
405 % (user.username, repo.repo_name, locked))
405 % (user.username, repo.repo_name, locked))
406 }
406 }
407 return _d
407 return _d
408 except Exception:
408 except Exception:
409 log.error(traceback.format_exc())
409 log.error(traceback.format_exc())
410 raise JSONRPCError(
410 raise JSONRPCError(
411 'Error occurred locking repository `%s`' % repo.repo_name
411 'Error occurred locking repository `%s`' % repo.repo_name
412 )
412 )
413
413
414 def get_locks(self, userid=Optional(OAttr('apiuser'))):
414 def get_locks(self, userid=Optional(OAttr('apiuser'))):
415 """
415 """
416 Get all repositories with locks for given userid, if
416 Get all repositories with locks for given userid, if
417 this command is run by non-admin account userid is set to user
417 this command is run by non-admin account userid is set to user
418 who is calling this method, thus returning locks for himself.
418 who is calling this method, thus returning locks for himself.
419
419
420 :param userid: User to get locks for
420 :param userid: User to get locks for
421 :type userid: Optional(str or int)
421 :type userid: Optional(str or int)
422
422
423 OUTPUT::
423 OUTPUT::
424
424
425 id : <id_given_in_input>
425 id : <id_given_in_input>
426 result : {
426 result : {
427 [repo_object, repo_object,...]
427 [repo_object, repo_object,...]
428 }
428 }
429 error : null
429 error : null
430 """
430 """
431
431
432 if not HasPermissionAny('hg.admin')():
432 if not HasPermissionAny('hg.admin')():
433 # make sure normal user does not pass someone else userid,
433 # make sure normal user does not pass someone else userid,
434 # he is not allowed to do that
434 # he is not allowed to do that
435 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
435 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
436 raise JSONRPCError(
436 raise JSONRPCError(
437 'userid is not the same as your user'
437 'userid is not the same as your user'
438 )
438 )
439
439
440 ret = []
440 ret = []
441 if isinstance(userid, Optional):
441 if isinstance(userid, Optional):
442 user = None
442 user = None
443 else:
443 else:
444 user = get_user_or_error(userid)
444 user = get_user_or_error(userid)
445
445
446 # show all locks
446 # show all locks
447 for r in Repository.query():
447 for r in Repository.query():
448 userid, time_ = r.locked
448 userid, time_ = r.locked
449 if time_:
449 if time_:
450 _api_data = r.get_api_data()
450 _api_data = r.get_api_data()
451 # if we use userfilter just show the locks for this user
451 # if we use userfilter just show the locks for this user
452 if user is not None:
452 if user is not None:
453 if safe_int(userid) == user.user_id:
453 if safe_int(userid) == user.user_id:
454 ret.append(_api_data)
454 ret.append(_api_data)
455 else:
455 else:
456 ret.append(_api_data)
456 ret.append(_api_data)
457
457
458 return ret
458 return ret
459
459
460 @HasPermissionAnyDecorator('hg.admin')
460 @HasPermissionAnyDecorator('hg.admin')
461 def get_ip(self, userid=Optional(OAttr('apiuser'))):
461 def get_ip(self, userid=Optional(OAttr('apiuser'))):
462 """
462 """
463 Shows IP address as seen from Kallithea server, together with all
463 Shows IP address as seen from Kallithea server, together with all
464 defined IP addresses for given user. If userid is not passed data is
464 defined IP addresses for given user. If userid is not passed data is
465 returned for user who's calling this function.
465 returned for user who's calling this function.
466 This command can be executed only using api_key belonging to user with
466 This command can be executed only using api_key belonging to user with
467 admin rights.
467 admin rights.
468
468
469 :param userid: username to show ips for
469 :param userid: username to show ips for
470 :type userid: Optional(str or int)
470 :type userid: Optional(str or int)
471
471
472 OUTPUT::
472 OUTPUT::
473
473
474 id : <id_given_in_input>
474 id : <id_given_in_input>
475 result : {
475 result : {
476 "server_ip_addr": "<ip_from_clien>",
476 "server_ip_addr": "<ip_from_clien>",
477 "user_ips": [
477 "user_ips": [
478 {
478 {
479 "ip_addr": "<ip_with_mask>",
479 "ip_addr": "<ip_with_mask>",
480 "ip_range": ["<start_ip>", "<end_ip>"],
480 "ip_range": ["<start_ip>", "<end_ip>"],
481 },
481 },
482 ...
482 ...
483 ]
483 ]
484 }
484 }
485
485
486 """
486 """
487 if isinstance(userid, Optional):
487 if isinstance(userid, Optional):
488 userid = request.authuser.user_id
488 userid = request.authuser.user_id
489 user = get_user_or_error(userid)
489 user = get_user_or_error(userid)
490 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
490 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
491 return dict(
491 return dict(
492 server_ip_addr=request.ip_addr,
492 server_ip_addr=request.ip_addr,
493 user_ips=ips
493 user_ips=ips
494 )
494 )
495
495
496 # alias for old
496 # alias for old
497 show_ip = get_ip
497 show_ip = get_ip
498
498
499 @HasPermissionAnyDecorator('hg.admin')
499 @HasPermissionAnyDecorator('hg.admin')
500 def get_server_info(self):
500 def get_server_info(self):
501 """
501 """
502 return server info, including Kallithea version and installed packages
502 return server info, including Kallithea version and installed packages
503
503
504
504
505 OUTPUT::
505 OUTPUT::
506
506
507 id : <id_given_in_input>
507 id : <id_given_in_input>
508 result : {
508 result : {
509 'modules': [<module name>,...]
509 'modules': [<module name>,...]
510 'py_version': <python version>,
510 'py_version': <python version>,
511 'platform': <platform type>,
511 'platform': <platform type>,
512 'kallithea_version': <kallithea version>
512 'kallithea_version': <kallithea version>
513 }
513 }
514 error : null
514 error : null
515 """
515 """
516 return Setting.get_server_info()
516 return Setting.get_server_info()
517
517
518 def get_user(self, userid=Optional(OAttr('apiuser'))):
518 def get_user(self, userid=Optional(OAttr('apiuser'))):
519 """
519 """
520 Gets a user by username or user_id, Returns empty result if user is
520 Gets a user by username or user_id, Returns empty result if user is
521 not found. If userid param is skipped it is set to id of user who is
521 not found. If userid param is skipped it is set to id of user who is
522 calling this method. This command can be executed only using api_key
522 calling this method. This command can be executed only using api_key
523 belonging to user with admin rights, or regular users that cannot
523 belonging to user with admin rights, or regular users that cannot
524 specify different userid than theirs
524 specify different userid than theirs
525
525
526 :param userid: user to get data for
526 :param userid: user to get data for
527 :type userid: Optional(str or int)
527 :type userid: Optional(str or int)
528
528
529 OUTPUT::
529 OUTPUT::
530
530
531 id : <id_given_in_input>
531 id : <id_given_in_input>
532 result: None if user does not exist or
532 result: None if user does not exist or
533 {
533 {
534 "user_id" : "<user_id>",
534 "user_id" : "<user_id>",
535 "api_key" : "<api_key>",
535 "api_key" : "<api_key>",
536 "api_keys": "[<list of all API keys including additional ones>]"
536 "api_keys": "[<list of all API keys including additional ones>]"
537 "username" : "<username>",
537 "username" : "<username>",
538 "firstname": "<firstname>",
538 "firstname": "<firstname>",
539 "lastname" : "<lastname>",
539 "lastname" : "<lastname>",
540 "email" : "<email>",
540 "email" : "<email>",
541 "emails": "[<list of all emails including additional ones>]",
541 "emails": "[<list of all emails including additional ones>]",
542 "ip_addresses": "[<ip_address_for_user>,...]",
542 "ip_addresses": "[<ip_address_for_user>,...]",
543 "active" : "<bool: user active>",
543 "active" : "<bool: user active>",
544 "admin" :Β  "<bool: user is admin>",
544 "admin" :Β  "<bool: user is admin>",
545 "extern_name" : "<extern_name>",
545 "extern_name" : "<extern_name>",
546 "extern_type" : "<extern type>
546 "extern_type" : "<extern type>
547 "last_login": "<last_login>",
547 "last_login": "<last_login>",
548 "permissions": {
548 "permissions": {
549 "global": ["hg.create.repository",
549 "global": ["hg.create.repository",
550 "repository.read",
550 "repository.read",
551 "hg.register.manual_activate"],
551 "hg.register.manual_activate"],
552 "repositories": {"repo1": "repository.none"},
552 "repositories": {"repo1": "repository.none"},
553 "repositories_groups": {"Group1": "group.read"}
553 "repositories_groups": {"Group1": "group.read"}
554 },
554 },
555 }
555 }
556
556
557 error: null
557 error: null
558
558
559 """
559 """
560 if not HasPermissionAny('hg.admin')():
560 if not HasPermissionAny('hg.admin')():
561 # make sure normal user does not pass someone else userid,
561 # make sure normal user does not pass someone else userid,
562 # he is not allowed to do that
562 # he is not allowed to do that
563 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
563 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
564 raise JSONRPCError(
564 raise JSONRPCError(
565 'userid is not the same as your user'
565 'userid is not the same as your user'
566 )
566 )
567
567
568 if isinstance(userid, Optional):
568 if isinstance(userid, Optional):
569 userid = request.authuser.user_id
569 userid = request.authuser.user_id
570
570
571 user = get_user_or_error(userid)
571 user = get_user_or_error(userid)
572 data = user.get_api_data()
572 data = user.get_api_data()
573 data['permissions'] = AuthUser(user_id=user.user_id).permissions
573 data['permissions'] = AuthUser(user_id=user.user_id).permissions
574 return data
574 return data
575
575
576 @HasPermissionAnyDecorator('hg.admin')
576 @HasPermissionAnyDecorator('hg.admin')
577 def get_users(self):
577 def get_users(self):
578 """
578 """
579 Lists all existing users. This command can be executed only using api_key
579 Lists all existing users. This command can be executed only using api_key
580 belonging to user with admin rights.
580 belonging to user with admin rights.
581
581
582
582
583 OUTPUT::
583 OUTPUT::
584
584
585 id : <id_given_in_input>
585 id : <id_given_in_input>
586 result: [<user_object>, ...]
586 result: [<user_object>, ...]
587 error: null
587 error: null
588 """
588 """
589
589
590 return [
590 return [
591 user.get_api_data()
591 user.get_api_data()
592 for user in User.query()
592 for user in User.query()
593 .order_by(User.username)
593 .order_by(User.username)
594 .filter_by(is_default_user=False)
594 .filter_by(is_default_user=False)
595 ]
595 ]
596
596
597 @HasPermissionAnyDecorator('hg.admin')
597 @HasPermissionAnyDecorator('hg.admin')
598 def create_user(self, username, email, password=Optional(''),
598 def create_user(self, username, email, password=Optional(''),
599 firstname=Optional(''), lastname=Optional(''),
599 firstname=Optional(''), lastname=Optional(''),
600 active=Optional(True), admin=Optional(False),
600 active=Optional(True), admin=Optional(False),
601 extern_type=Optional(User.DEFAULT_AUTH_TYPE),
601 extern_type=Optional(User.DEFAULT_AUTH_TYPE),
602 extern_name=Optional('')):
602 extern_name=Optional('')):
603 """
603 """
604 Creates new user. Returns new user object. This command can
604 Creates new user. Returns new user object. This command can
605 be executed only using api_key belonging to user with admin rights.
605 be executed only using api_key belonging to user with admin rights.
606
606
607 :param username: new username
607 :param username: new username
608 :type username: str or int
608 :type username: str or int
609 :param email: email
609 :param email: email
610 :type email: str
610 :type email: str
611 :param password: password
611 :param password: password
612 :type password: Optional(str)
612 :type password: Optional(str)
613 :param firstname: firstname
613 :param firstname: firstname
614 :type firstname: Optional(str)
614 :type firstname: Optional(str)
615 :param lastname: lastname
615 :param lastname: lastname
616 :type lastname: Optional(str)
616 :type lastname: Optional(str)
617 :param active: active
617 :param active: active
618 :type active: Optional(bool)
618 :type active: Optional(bool)
619 :param admin: admin
619 :param admin: admin
620 :type admin: Optional(bool)
620 :type admin: Optional(bool)
621 :param extern_name: name of extern
621 :param extern_name: name of extern
622 :type extern_name: Optional(str)
622 :type extern_name: Optional(str)
623 :param extern_type: extern_type
623 :param extern_type: extern_type
624 :type extern_type: Optional(str)
624 :type extern_type: Optional(str)
625
625
626
626
627 OUTPUT::
627 OUTPUT::
628
628
629 id : <id_given_in_input>
629 id : <id_given_in_input>
630 result: {
630 result: {
631 "msg" : "created new user `<username>`",
631 "msg" : "created new user `<username>`",
632 "user": <user_obj>
632 "user": <user_obj>
633 }
633 }
634 error: null
634 error: null
635
635
636 ERROR OUTPUT::
636 ERROR OUTPUT::
637
637
638 id : <id_given_in_input>
638 id : <id_given_in_input>
639 result : null
639 result : null
640 error : {
640 error : {
641 "user `<username>` already exist"
641 "user `<username>` already exist"
642 or
642 or
643 "email `<email>` already exist"
643 "email `<email>` already exist"
644 or
644 or
645 "failed to create user `<username>`"
645 "failed to create user `<username>`"
646 }
646 }
647
647
648 """
648 """
649
649
650 if User.get_by_username(username):
650 if User.get_by_username(username):
651 raise JSONRPCError("user `%s` already exist" % (username,))
651 raise JSONRPCError("user `%s` already exist" % (username,))
652
652
653 if User.get_by_email(email):
653 if User.get_by_email(email):
654 raise JSONRPCError("email `%s` already exist" % (email,))
654 raise JSONRPCError("email `%s` already exist" % (email,))
655
655
656 try:
656 try:
657 user = UserModel().create_or_update(
657 user = UserModel().create_or_update(
658 username=Optional.extract(username),
658 username=Optional.extract(username),
659 password=Optional.extract(password),
659 password=Optional.extract(password),
660 email=Optional.extract(email),
660 email=Optional.extract(email),
661 firstname=Optional.extract(firstname),
661 firstname=Optional.extract(firstname),
662 lastname=Optional.extract(lastname),
662 lastname=Optional.extract(lastname),
663 active=Optional.extract(active),
663 active=Optional.extract(active),
664 admin=Optional.extract(admin),
664 admin=Optional.extract(admin),
665 extern_type=Optional.extract(extern_type),
665 extern_type=Optional.extract(extern_type),
666 extern_name=Optional.extract(extern_name)
666 extern_name=Optional.extract(extern_name)
667 )
667 )
668 Session().commit()
668 Session().commit()
669 return dict(
669 return dict(
670 msg='created new user `%s`' % username,
670 msg='created new user `%s`' % username,
671 user=user.get_api_data()
671 user=user.get_api_data()
672 )
672 )
673 except Exception:
673 except Exception:
674 log.error(traceback.format_exc())
674 log.error(traceback.format_exc())
675 raise JSONRPCError('failed to create user `%s`' % (username,))
675 raise JSONRPCError('failed to create user `%s`' % (username,))
676
676
677 @HasPermissionAnyDecorator('hg.admin')
677 @HasPermissionAnyDecorator('hg.admin')
678 def update_user(self, userid, username=Optional(None),
678 def update_user(self, userid, username=Optional(None),
679 email=Optional(None), password=Optional(None),
679 email=Optional(None), password=Optional(None),
680 firstname=Optional(None), lastname=Optional(None),
680 firstname=Optional(None), lastname=Optional(None),
681 active=Optional(None), admin=Optional(None),
681 active=Optional(None), admin=Optional(None),
682 extern_type=Optional(None), extern_name=Optional(None)):
682 extern_type=Optional(None), extern_name=Optional(None)):
683 """
683 """
684 updates given user if such user exists. This command can
684 updates given user if such user exists. This command can
685 be executed only using api_key belonging to user with admin rights.
685 be executed only using api_key belonging to user with admin rights.
686
686
687 :param userid: userid to update
687 :param userid: userid to update
688 :type userid: str or int
688 :type userid: str or int
689 :param username: new username
689 :param username: new username
690 :type username: str or int
690 :type username: str or int
691 :param email: email
691 :param email: email
692 :type email: str
692 :type email: str
693 :param password: password
693 :param password: password
694 :type password: Optional(str)
694 :type password: Optional(str)
695 :param firstname: firstname
695 :param firstname: firstname
696 :type firstname: Optional(str)
696 :type firstname: Optional(str)
697 :param lastname: lastname
697 :param lastname: lastname
698 :type lastname: Optional(str)
698 :type lastname: Optional(str)
699 :param active: active
699 :param active: active
700 :type active: Optional(bool)
700 :type active: Optional(bool)
701 :param admin: admin
701 :param admin: admin
702 :type admin: Optional(bool)
702 :type admin: Optional(bool)
703 :param extern_name:
703 :param extern_name:
704 :type extern_name: Optional(str)
704 :type extern_name: Optional(str)
705 :param extern_type:
705 :param extern_type:
706 :type extern_type: Optional(str)
706 :type extern_type: Optional(str)
707
707
708
708
709 OUTPUT::
709 OUTPUT::
710
710
711 id : <id_given_in_input>
711 id : <id_given_in_input>
712 result: {
712 result: {
713 "msg" : "updated user ID:<userid> <username>",
713 "msg" : "updated user ID:<userid> <username>",
714 "user": <user_object>,
714 "user": <user_object>,
715 }
715 }
716 error: null
716 error: null
717
717
718 ERROR OUTPUT::
718 ERROR OUTPUT::
719
719
720 id : <id_given_in_input>
720 id : <id_given_in_input>
721 result : null
721 result : null
722 error : {
722 error : {
723 "failed to update user `<username>`"
723 "failed to update user `<username>`"
724 }
724 }
725
725
726 """
726 """
727
727
728 user = get_user_or_error(userid)
728 user = get_user_or_error(userid)
729
729
730 # only non optional arguments will be stored in updates
730 # only non optional arguments will be stored in updates
731 updates = {}
731 updates = {}
732
732
733 try:
733 try:
734
734
735 store_update(updates, username, 'username')
735 store_update(updates, username, 'username')
736 store_update(updates, password, 'password')
736 store_update(updates, password, 'password')
737 store_update(updates, email, 'email')
737 store_update(updates, email, 'email')
738 store_update(updates, firstname, 'name')
738 store_update(updates, firstname, 'name')
739 store_update(updates, lastname, 'lastname')
739 store_update(updates, lastname, 'lastname')
740 store_update(updates, active, 'active')
740 store_update(updates, active, 'active')
741 store_update(updates, admin, 'admin')
741 store_update(updates, admin, 'admin')
742 store_update(updates, extern_name, 'extern_name')
742 store_update(updates, extern_name, 'extern_name')
743 store_update(updates, extern_type, 'extern_type')
743 store_update(updates, extern_type, 'extern_type')
744
744
745 user = UserModel().update_user(user, **updates)
745 user = UserModel().update_user(user, **updates)
746 Session().commit()
746 Session().commit()
747 return dict(
747 return dict(
748 msg='updated user ID:%s %s' % (user.user_id, user.username),
748 msg='updated user ID:%s %s' % (user.user_id, user.username),
749 user=user.get_api_data()
749 user=user.get_api_data()
750 )
750 )
751 except DefaultUserException:
751 except DefaultUserException:
752 log.error(traceback.format_exc())
752 log.error(traceback.format_exc())
753 raise JSONRPCError('editing default user is forbidden')
753 raise JSONRPCError('editing default user is forbidden')
754 except Exception:
754 except Exception:
755 log.error(traceback.format_exc())
755 log.error(traceback.format_exc())
756 raise JSONRPCError('failed to update user `%s`' % (userid,))
756 raise JSONRPCError('failed to update user `%s`' % (userid,))
757
757
758 @HasPermissionAnyDecorator('hg.admin')
758 @HasPermissionAnyDecorator('hg.admin')
759 def delete_user(self, userid):
759 def delete_user(self, userid):
760 """
760 """
761 deletes given user if such user exists. This command can
761 deletes given user if such user exists. This command can
762 be executed only using api_key belonging to user with admin rights.
762 be executed only using api_key belonging to user with admin rights.
763
763
764 :param userid: user to delete
764 :param userid: user to delete
765 :type userid: str or int
765 :type userid: str or int
766
766
767 OUTPUT::
767 OUTPUT::
768
768
769 id : <id_given_in_input>
769 id : <id_given_in_input>
770 result: {
770 result: {
771 "msg" : "deleted user ID:<userid> <username>",
771 "msg" : "deleted user ID:<userid> <username>",
772 "user": null
772 "user": null
773 }
773 }
774 error: null
774 error: null
775
775
776 ERROR OUTPUT::
776 ERROR OUTPUT::
777
777
778 id : <id_given_in_input>
778 id : <id_given_in_input>
779 result : null
779 result : null
780 error : {
780 error : {
781 "failed to delete user ID:<userid> <username>"
781 "failed to delete user ID:<userid> <username>"
782 }
782 }
783
783
784 """
784 """
785 user = get_user_or_error(userid)
785 user = get_user_or_error(userid)
786
786
787 try:
787 try:
788 UserModel().delete(userid)
788 UserModel().delete(userid)
789 Session().commit()
789 Session().commit()
790 return dict(
790 return dict(
791 msg='deleted user ID:%s %s' % (user.user_id, user.username),
791 msg='deleted user ID:%s %s' % (user.user_id, user.username),
792 user=None
792 user=None
793 )
793 )
794 except Exception:
794 except Exception:
795
795
796 log.error(traceback.format_exc())
796 log.error(traceback.format_exc())
797 raise JSONRPCError('failed to delete user ID:%s %s'
797 raise JSONRPCError('failed to delete user ID:%s %s'
798 % (user.user_id, user.username))
798 % (user.user_id, user.username))
799
799
800 # permission check inside
800 # permission check inside
801 def get_user_group(self, usergroupid):
801 def get_user_group(self, usergroupid):
802 """
802 """
803 Gets an existing user group. This command can be executed only using api_key
803 Gets an existing user group. This command can be executed only using api_key
804 belonging to user with admin rights or user who has at least
804 belonging to user with admin rights or user who has at least
805 read access to user group.
805 read access to user group.
806
806
807 :param usergroupid: id of user_group to edit
807 :param usergroupid: id of user_group to edit
808 :type usergroupid: str or int
808 :type usergroupid: str or int
809
809
810 OUTPUT::
810 OUTPUT::
811
811
812 id : <id_given_in_input>
812 id : <id_given_in_input>
813 result : None if group not exist
813 result : None if group not exist
814 {
814 {
815 "users_group_id" : "<id>",
815 "users_group_id" : "<id>",
816 "group_name" : "<groupname>",
816 "group_name" : "<groupname>",
817 "active": "<bool>",
817 "active": "<bool>",
818 "members" : [<user_obj>,...]
818 "members" : [<user_obj>,...]
819 }
819 }
820 error : null
820 error : null
821
821
822 """
822 """
823 user_group = get_user_group_or_error(usergroupid)
823 user_group = get_user_group_or_error(usergroupid)
824 if not HasPermissionAny('hg.admin')():
824 if not HasPermissionAny('hg.admin')():
825 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
825 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
826 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
826 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
827
827
828 data = user_group.get_api_data()
828 data = user_group.get_api_data()
829 return data
829 return data
830
830
831 # permission check inside
831 # permission check inside
832 def get_user_groups(self):
832 def get_user_groups(self):
833 """
833 """
834 Lists all existing user groups. This command can be executed only using
834 Lists all existing user groups. This command can be executed only using
835 api_key belonging to user with admin rights or user who has at least
835 api_key belonging to user with admin rights or user who has at least
836 read access to user group.
836 read access to user group.
837
837
838
838
839 OUTPUT::
839 OUTPUT::
840
840
841 id : <id_given_in_input>
841 id : <id_given_in_input>
842 result : [<user_group_obj>,...]
842 result : [<user_group_obj>,...]
843 error : null
843 error : null
844 """
844 """
845
845
846 return [
846 return [
847 user_group.get_api_data()
847 user_group.get_api_data()
848 for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
848 for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
849 ]
849 ]
850
850
851 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
851 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
852 def create_user_group(self, group_name, description=Optional(''),
852 def create_user_group(self, group_name, description=Optional(''),
853 owner=Optional(OAttr('apiuser')), active=Optional(True)):
853 owner=Optional(OAttr('apiuser')), active=Optional(True)):
854 """
854 """
855 Creates new user group. This command can be executed only using api_key
855 Creates new user group. This command can be executed only using api_key
856 belonging to user with admin rights or an user who has create user group
856 belonging to user with admin rights or an user who has create user group
857 permission
857 permission
858
858
859 :param group_name: name of new user group
859 :param group_name: name of new user group
860 :type group_name: str
860 :type group_name: str
861 :param description: group description
861 :param description: group description
862 :type description: str
862 :type description: str
863 :param owner: owner of group. If not passed apiuser is the owner
863 :param owner: owner of group. If not passed apiuser is the owner
864 :type owner: Optional(str or int)
864 :type owner: Optional(str or int)
865 :param active: group is active
865 :param active: group is active
866 :type active: Optional(bool)
866 :type active: Optional(bool)
867
867
868 OUTPUT::
868 OUTPUT::
869
869
870 id : <id_given_in_input>
870 id : <id_given_in_input>
871 result: {
871 result: {
872 "msg": "created new user group `<groupname>`",
872 "msg": "created new user group `<groupname>`",
873 "user_group": <user_group_object>
873 "user_group": <user_group_object>
874 }
874 }
875 error: null
875 error: null
876
876
877 ERROR OUTPUT::
877 ERROR OUTPUT::
878
878
879 id : <id_given_in_input>
879 id : <id_given_in_input>
880 result : null
880 result : null
881 error : {
881 error : {
882 "user group `<group name>` already exist"
882 "user group `<group name>` already exist"
883 or
883 or
884 "failed to create group `<group name>`"
884 "failed to create group `<group name>`"
885 }
885 }
886
886
887 """
887 """
888
888
889 if UserGroupModel().get_by_name(group_name):
889 if UserGroupModel().get_by_name(group_name):
890 raise JSONRPCError("user group `%s` already exist" % (group_name,))
890 raise JSONRPCError("user group `%s` already exist" % (group_name,))
891
891
892 try:
892 try:
893 if isinstance(owner, Optional):
893 if isinstance(owner, Optional):
894 owner = request.authuser.user_id
894 owner = request.authuser.user_id
895
895
896 owner = get_user_or_error(owner)
896 owner = get_user_or_error(owner)
897 active = Optional.extract(active)
897 active = Optional.extract(active)
898 description = Optional.extract(description)
898 description = Optional.extract(description)
899 ug = UserGroupModel().create(name=group_name, description=description,
899 ug = UserGroupModel().create(name=group_name, description=description,
900 owner=owner, active=active)
900 owner=owner, active=active)
901 Session().commit()
901 Session().commit()
902 return dict(
902 return dict(
903 msg='created new user group `%s`' % group_name,
903 msg='created new user group `%s`' % group_name,
904 user_group=ug.get_api_data()
904 user_group=ug.get_api_data()
905 )
905 )
906 except Exception:
906 except Exception:
907 log.error(traceback.format_exc())
907 log.error(traceback.format_exc())
908 raise JSONRPCError('failed to create group `%s`' % (group_name,))
908 raise JSONRPCError('failed to create group `%s`' % (group_name,))
909
909
910 # permission check inside
910 # permission check inside
911 def update_user_group(self, usergroupid, group_name=Optional(''),
911 def update_user_group(self, usergroupid, group_name=Optional(''),
912 description=Optional(''), owner=Optional(None),
912 description=Optional(''), owner=Optional(None),
913 active=Optional(True)):
913 active=Optional(True)):
914 """
914 """
915 Updates given usergroup. This command can be executed only using api_key
915 Updates given usergroup. This command can be executed only using api_key
916 belonging to user with admin rights or an admin of given user group
916 belonging to user with admin rights or an admin of given user group
917
917
918 :param usergroupid: id of user group to update
918 :param usergroupid: id of user group to update
919 :type usergroupid: str or int
919 :type usergroupid: str or int
920 :param group_name: name of new user group
920 :param group_name: name of new user group
921 :type group_name: str
921 :type group_name: str
922 :param description: group description
922 :param description: group description
923 :type description: str
923 :type description: str
924 :param owner: owner of group.
924 :param owner: owner of group.
925 :type owner: Optional(str or int)
925 :type owner: Optional(str or int)
926 :param active: group is active
926 :param active: group is active
927 :type active: Optional(bool)
927 :type active: Optional(bool)
928
928
929 OUTPUT::
929 OUTPUT::
930
930
931 id : <id_given_in_input>
931 id : <id_given_in_input>
932 result : {
932 result : {
933 "msg": 'updated user group ID:<user group id> <user group name>',
933 "msg": 'updated user group ID:<user group id> <user group name>',
934 "user_group": <user_group_object>
934 "user_group": <user_group_object>
935 }
935 }
936 error : null
936 error : null
937
937
938 ERROR OUTPUT::
938 ERROR OUTPUT::
939
939
940 id : <id_given_in_input>
940 id : <id_given_in_input>
941 result : null
941 result : null
942 error : {
942 error : {
943 "failed to update user group `<user group name>`"
943 "failed to update user group `<user group name>`"
944 }
944 }
945
945
946 """
946 """
947 user_group = get_user_group_or_error(usergroupid)
947 user_group = get_user_group_or_error(usergroupid)
948 if not HasPermissionAny('hg.admin')():
948 if not HasPermissionAny('hg.admin')():
949 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
949 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
950 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
950 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
951
951
952 if not isinstance(owner, Optional):
952 if not isinstance(owner, Optional):
953 owner = get_user_or_error(owner)
953 owner = get_user_or_error(owner)
954
954
955 updates = {}
955 updates = {}
956 store_update(updates, group_name, 'users_group_name')
956 store_update(updates, group_name, 'users_group_name')
957 store_update(updates, description, 'user_group_description')
957 store_update(updates, description, 'user_group_description')
958 store_update(updates, owner, 'owner')
958 store_update(updates, owner, 'owner')
959 store_update(updates, active, 'users_group_active')
959 store_update(updates, active, 'users_group_active')
960 try:
960 try:
961 UserGroupModel().update(user_group, updates)
961 UserGroupModel().update(user_group, updates)
962 Session().commit()
962 Session().commit()
963 return dict(
963 return dict(
964 msg='updated user group ID:%s %s' % (user_group.users_group_id,
964 msg='updated user group ID:%s %s' % (user_group.users_group_id,
965 user_group.users_group_name),
965 user_group.users_group_name),
966 user_group=user_group.get_api_data()
966 user_group=user_group.get_api_data()
967 )
967 )
968 except Exception:
968 except Exception:
969 log.error(traceback.format_exc())
969 log.error(traceback.format_exc())
970 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
970 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
971
971
972 # permission check inside
972 # permission check inside
973 def delete_user_group(self, usergroupid):
973 def delete_user_group(self, usergroupid):
974 """
974 """
975 Delete given user group by user group id or name.
975 Delete given user group by user group id or name.
976 This command can be executed only using api_key
976 This command can be executed only using api_key
977 belonging to user with admin rights or an admin of given user group
977 belonging to user with admin rights or an admin of given user group
978
978
979 :param usergroupid:
979 :param usergroupid:
980 :type usergroupid: int
980 :type usergroupid: int
981
981
982 OUTPUT::
982 OUTPUT::
983
983
984 id : <id_given_in_input>
984 id : <id_given_in_input>
985 result : {
985 result : {
986 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
986 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
987 }
987 }
988 error : null
988 error : null
989
989
990 ERROR OUTPUT::
990 ERROR OUTPUT::
991
991
992 id : <id_given_in_input>
992 id : <id_given_in_input>
993 result : null
993 result : null
994 error : {
994 error : {
995 "failed to delete user group ID:<user_group_id> <user_group_name>"
995 "failed to delete user group ID:<user_group_id> <user_group_name>"
996 or
996 or
997 "RepoGroup assigned to <repo_groups_list>"
997 "RepoGroup assigned to <repo_groups_list>"
998 }
998 }
999
999
1000 """
1000 """
1001 user_group = get_user_group_or_error(usergroupid)
1001 user_group = get_user_group_or_error(usergroupid)
1002 if not HasPermissionAny('hg.admin')():
1002 if not HasPermissionAny('hg.admin')():
1003 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1003 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1004 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1004 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1005
1005
1006 try:
1006 try:
1007 UserGroupModel().delete(user_group)
1007 UserGroupModel().delete(user_group)
1008 Session().commit()
1008 Session().commit()
1009 return dict(
1009 return dict(
1010 msg='deleted user group ID:%s %s' %
1010 msg='deleted user group ID:%s %s' %
1011 (user_group.users_group_id, user_group.users_group_name),
1011 (user_group.users_group_id, user_group.users_group_name),
1012 user_group=None
1012 user_group=None
1013 )
1013 )
1014 except UserGroupsAssignedException as e:
1014 except UserGroupsAssignedException as e:
1015 log.error(traceback.format_exc())
1015 log.error(traceback.format_exc())
1016 raise JSONRPCError(str(e))
1016 raise JSONRPCError(str(e))
1017 except Exception:
1017 except Exception:
1018 log.error(traceback.format_exc())
1018 log.error(traceback.format_exc())
1019 raise JSONRPCError('failed to delete user group ID:%s %s' %
1019 raise JSONRPCError('failed to delete user group ID:%s %s' %
1020 (user_group.users_group_id,
1020 (user_group.users_group_id,
1021 user_group.users_group_name)
1021 user_group.users_group_name)
1022 )
1022 )
1023
1023
1024 # permission check inside
1024 # permission check inside
1025 def add_user_to_user_group(self, usergroupid, userid):
1025 def add_user_to_user_group(self, usergroupid, userid):
1026 """
1026 """
1027 Adds a user to a user group. If user exists in that group success will be
1027 Adds a user to a user group. If user exists in that group success will be
1028 `false`. This command can be executed only using api_key
1028 `false`. This command can be executed only using api_key
1029 belonging to user with admin rights or an admin of given user group
1029 belonging to user with admin rights or an admin of given user group
1030
1030
1031 :param usergroupid:
1031 :param usergroupid:
1032 :type usergroupid: int
1032 :type usergroupid: int
1033 :param userid:
1033 :param userid:
1034 :type userid: int
1034 :type userid: int
1035
1035
1036 OUTPUT::
1036 OUTPUT::
1037
1037
1038 id : <id_given_in_input>
1038 id : <id_given_in_input>
1039 result : {
1039 result : {
1040 "success": True|False # depends on if member is in group
1040 "success": True|False # depends on if member is in group
1041 "msg": "added member `<username>` to user group `<groupname>` |
1041 "msg": "added member `<username>` to user group `<groupname>` |
1042 User is already in that group"
1042 User is already in that group"
1043
1043
1044 }
1044 }
1045 error : null
1045 error : null
1046
1046
1047 ERROR OUTPUT::
1047 ERROR OUTPUT::
1048
1048
1049 id : <id_given_in_input>
1049 id : <id_given_in_input>
1050 result : null
1050 result : null
1051 error : {
1051 error : {
1052 "failed to add member to user group `<user_group_name>`"
1052 "failed to add member to user group `<user_group_name>`"
1053 }
1053 }
1054
1054
1055 """
1055 """
1056 user = get_user_or_error(userid)
1056 user = get_user_or_error(userid)
1057 user_group = get_user_group_or_error(usergroupid)
1057 user_group = get_user_group_or_error(usergroupid)
1058 if not HasPermissionAny('hg.admin')():
1058 if not HasPermissionAny('hg.admin')():
1059 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1059 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1060 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1060 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1061
1061
1062 try:
1062 try:
1063 ugm = UserGroupModel().add_user_to_group(user_group, user)
1063 ugm = UserGroupModel().add_user_to_group(user_group, user)
1064 success = True if ugm != True else False
1064 success = True if ugm != True else False
1065 msg = 'added member `%s` to user group `%s`' % (
1065 msg = 'added member `%s` to user group `%s`' % (
1066 user.username, user_group.users_group_name
1066 user.username, user_group.users_group_name
1067 )
1067 )
1068 msg = msg if success else 'User is already in that group'
1068 msg = msg if success else 'User is already in that group'
1069 Session().commit()
1069 Session().commit()
1070
1070
1071 return dict(
1071 return dict(
1072 success=success,
1072 success=success,
1073 msg=msg
1073 msg=msg
1074 )
1074 )
1075 except Exception:
1075 except Exception:
1076 log.error(traceback.format_exc())
1076 log.error(traceback.format_exc())
1077 raise JSONRPCError(
1077 raise JSONRPCError(
1078 'failed to add member to user group `%s`' % (
1078 'failed to add member to user group `%s`' % (
1079 user_group.users_group_name,
1079 user_group.users_group_name,
1080 )
1080 )
1081 )
1081 )
1082
1082
1083 # permission check inside
1083 # permission check inside
1084 def remove_user_from_user_group(self, usergroupid, userid):
1084 def remove_user_from_user_group(self, usergroupid, userid):
1085 """
1085 """
1086 Removes a user from a user group. If user is not in given group success will
1086 Removes a user from a user group. If user is not in given group success will
1087 be `false`. This command can be executed only
1087 be `false`. This command can be executed only
1088 using api_key belonging to user with admin rights or an admin of given user group
1088 using api_key belonging to user with admin rights or an admin of given user group
1089
1089
1090 :param usergroupid:
1090 :param usergroupid:
1091 :param userid:
1091 :param userid:
1092
1092
1093
1093
1094 OUTPUT::
1094 OUTPUT::
1095
1095
1096 id : <id_given_in_input>
1096 id : <id_given_in_input>
1097 result: {
1097 result: {
1098 "success": True|False, # depends on if member is in group
1098 "success": True|False, # depends on if member is in group
1099 "msg": "removed member <username> from user group <groupname> |
1099 "msg": "removed member <username> from user group <groupname> |
1100 User wasn't in group"
1100 User wasn't in group"
1101 }
1101 }
1102 error: null
1102 error: null
1103
1103
1104 """
1104 """
1105 user = get_user_or_error(userid)
1105 user = get_user_or_error(userid)
1106 user_group = get_user_group_or_error(usergroupid)
1106 user_group = get_user_group_or_error(usergroupid)
1107 if not HasPermissionAny('hg.admin')():
1107 if not HasPermissionAny('hg.admin')():
1108 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1108 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1109 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1109 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1110
1110
1111 try:
1111 try:
1112 success = UserGroupModel().remove_user_from_group(user_group, user)
1112 success = UserGroupModel().remove_user_from_group(user_group, user)
1113 msg = 'removed member `%s` from user group `%s`' % (
1113 msg = 'removed member `%s` from user group `%s`' % (
1114 user.username, user_group.users_group_name
1114 user.username, user_group.users_group_name
1115 )
1115 )
1116 msg = msg if success else "User wasn't in group"
1116 msg = msg if success else "User wasn't in group"
1117 Session().commit()
1117 Session().commit()
1118 return dict(success=success, msg=msg)
1118 return dict(success=success, msg=msg)
1119 except Exception:
1119 except Exception:
1120 log.error(traceback.format_exc())
1120 log.error(traceback.format_exc())
1121 raise JSONRPCError(
1121 raise JSONRPCError(
1122 'failed to remove member from user group `%s`' % (
1122 'failed to remove member from user group `%s`' % (
1123 user_group.users_group_name,
1123 user_group.users_group_name,
1124 )
1124 )
1125 )
1125 )
1126
1126
1127 # permission check inside
1127 # permission check inside
1128 def get_repo(self, repoid,
1128 def get_repo(self, repoid,
1129 with_revision_names=Optional(False)):
1129 with_revision_names=Optional(False),
1130 with_pullrequests=Optional(False)):
1130 """
1131 """
1131 Gets an existing repository by it's name or repository_id. Members will return
1132 Gets an existing repository by it's name or repository_id. Members will return
1132 either users_group or user associated to that repository. This command can be
1133 either users_group or user associated to that repository. This command can be
1133 executed only using api_key belonging to user with admin
1134 executed only using api_key belonging to user with admin
1134 rights or regular user that have at least read access to repository.
1135 rights or regular user that have at least read access to repository.
1135
1136
1136 :param repoid: repository name or repository id
1137 :param repoid: repository name or repository id
1137 :type repoid: str or int
1138 :type repoid: str or int
1138
1139
1139 OUTPUT::
1140 OUTPUT::
1140
1141
1141 id : <id_given_in_input>
1142 id : <id_given_in_input>
1142 result : {
1143 result : {
1143 {
1144 {
1144 "repo_id" : "<repo_id>",
1145 "repo_id" : "<repo_id>",
1145 "repo_name" : "<reponame>"
1146 "repo_name" : "<reponame>"
1146 "repo_type" : "<repo_type>",
1147 "repo_type" : "<repo_type>",
1147 "clone_uri" : "<clone_uri>",
1148 "clone_uri" : "<clone_uri>",
1148 "enable_downloads": "<bool>",
1149 "enable_downloads": "<bool>",
1149 "enable_locking": "<bool>",
1150 "enable_locking": "<bool>",
1150 "enable_statistics": "<bool>",
1151 "enable_statistics": "<bool>",
1151 "private": "<bool>",
1152 "private": "<bool>",
1152 "created_on" : "<date_time_created>",
1153 "created_on" : "<date_time_created>",
1153 "description" : "<description>",
1154 "description" : "<description>",
1154 "landing_rev": "<landing_rev>",
1155 "landing_rev": "<landing_rev>",
1155 "last_changeset": {
1156 "last_changeset": {
1156 "author": "<full_author>",
1157 "author": "<full_author>",
1157 "date": "<date_time_of_commit>",
1158 "date": "<date_time_of_commit>",
1158 "message": "<commit_message>",
1159 "message": "<commit_message>",
1159 "raw_id": "<raw_id>",
1160 "raw_id": "<raw_id>",
1160 "revision": "<numeric_revision>",
1161 "revision": "<numeric_revision>",
1161 "short_id": "<short_id>"
1162 "short_id": "<short_id>"
1162 }
1163 }
1163 "owner": "<repo_owner>",
1164 "owner": "<repo_owner>",
1164 "fork_of": "<name_of_fork_parent>",
1165 "fork_of": "<name_of_fork_parent>",
1165 "members" : [
1166 "members" : [
1166 {
1167 {
1167 "name": "<username>",
1168 "name": "<username>",
1168 "type" : "user",
1169 "type" : "user",
1169 "permission" : "repository.(read|write|admin)"
1170 "permission" : "repository.(read|write|admin)"
1170 },
1171 },
1171 …
1172 …
1172 {
1173 {
1173 "name": "<usergroup name>",
1174 "name": "<usergroup name>",
1174 "type" : "user_group",
1175 "type" : "user_group",
1175 "permission" : "usergroup.(read|write|admin)"
1176 "permission" : "usergroup.(read|write|admin)"
1176 },
1177 },
1177 …
1178 …
1178 ]
1179 ]
1179 "followers": [<user_obj>, ...],
1180 "followers": [<user_obj>, ...],
1180 <if with_revision_names == True>
1181 <if with_revision_names == True>
1181 "tags": {
1182 "tags": {
1182 "<tagname>": "<raw_id>",
1183 "<tagname>": "<raw_id>",
1183 ...
1184 ...
1184 },
1185 },
1185 "branches": {
1186 "branches": {
1186 "<branchname>": "<raw_id>",
1187 "<branchname>": "<raw_id>",
1187 ...
1188 ...
1188 },
1189 },
1189 "bookmarks": {
1190 "bookmarks": {
1190 "<bookmarkname>": "<raw_id>",
1191 "<bookmarkname>": "<raw_id>",
1191 ...
1192 ...
1192 },
1193 },
1193 }
1194 }
1194 }
1195 }
1195 error : null
1196 error : null
1196
1197
1197 """
1198 """
1198 repo = get_repo_or_error(repoid)
1199 repo = get_repo_or_error(repoid)
1199
1200
1200 if not HasPermissionAny('hg.admin')():
1201 if not HasPermissionAny('hg.admin')():
1201 if not HasRepoPermissionLevel('read')(repo.repo_name):
1202 if not HasRepoPermissionLevel('read')(repo.repo_name):
1202 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1203 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1203
1204
1204 members = []
1205 members = []
1205 for user in repo.repo_to_perm:
1206 for user in repo.repo_to_perm:
1206 perm = user.permission.permission_name
1207 perm = user.permission.permission_name
1207 user = user.user
1208 user = user.user
1208 user_data = {
1209 user_data = {
1209 'name': user.username,
1210 'name': user.username,
1210 'type': "user",
1211 'type': "user",
1211 'permission': perm
1212 'permission': perm
1212 }
1213 }
1213 members.append(user_data)
1214 members.append(user_data)
1214
1215
1215 for user_group in repo.users_group_to_perm:
1216 for user_group in repo.users_group_to_perm:
1216 perm = user_group.permission.permission_name
1217 perm = user_group.permission.permission_name
1217 user_group = user_group.users_group
1218 user_group = user_group.users_group
1218 user_group_data = {
1219 user_group_data = {
1219 'name': user_group.users_group_name,
1220 'name': user_group.users_group_name,
1220 'type': "user_group",
1221 'type': "user_group",
1221 'permission': perm
1222 'permission': perm
1222 }
1223 }
1223 members.append(user_group_data)
1224 members.append(user_group_data)
1224
1225
1225 followers = [
1226 followers = [
1226 uf.user.get_api_data()
1227 uf.user.get_api_data()
1227 for uf in repo.followers
1228 for uf in repo.followers
1228 ]
1229 ]
1229
1230
1230 data = repo.get_api_data(with_revision_names=Optional.extract(with_revision_names))
1231 data = repo.get_api_data(with_revision_names=Optional.extract(with_revision_names),
1232 with_pullrequests=Optional.extract(with_pullrequests))
1231 data['members'] = members
1233 data['members'] = members
1232 data['followers'] = followers
1234 data['followers'] = followers
1233 return data
1235 return data
1234
1236
1235 # permission check inside
1237 # permission check inside
1236 def get_repos(self):
1238 def get_repos(self):
1237 """
1239 """
1238 Lists all existing repositories. This command can be executed only using
1240 Lists all existing repositories. This command can be executed only using
1239 api_key belonging to user with admin rights or regular user that have
1241 api_key belonging to user with admin rights or regular user that have
1240 admin, write or read access to repository.
1242 admin, write or read access to repository.
1241
1243
1242
1244
1243 OUTPUT::
1245 OUTPUT::
1244
1246
1245 id : <id_given_in_input>
1247 id : <id_given_in_input>
1246 result: [
1248 result: [
1247 {
1249 {
1248 "repo_id" : "<repo_id>",
1250 "repo_id" : "<repo_id>",
1249 "repo_name" : "<reponame>"
1251 "repo_name" : "<reponame>"
1250 "repo_type" : "<repo_type>",
1252 "repo_type" : "<repo_type>",
1251 "clone_uri" : "<clone_uri>",
1253 "clone_uri" : "<clone_uri>",
1252 "private": : "<bool>",
1254 "private": : "<bool>",
1253 "created_on" : "<datetimecreated>",
1255 "created_on" : "<datetimecreated>",
1254 "description" : "<description>",
1256 "description" : "<description>",
1255 "landing_rev": "<landing_rev>",
1257 "landing_rev": "<landing_rev>",
1256 "owner": "<repo_owner>",
1258 "owner": "<repo_owner>",
1257 "fork_of": "<name_of_fork_parent>",
1259 "fork_of": "<name_of_fork_parent>",
1258 "enable_downloads": "<bool>",
1260 "enable_downloads": "<bool>",
1259 "enable_locking": "<bool>",
1261 "enable_locking": "<bool>",
1260 "enable_statistics": "<bool>",
1262 "enable_statistics": "<bool>",
1261 },
1263 },
1262 …
1264 …
1263 ]
1265 ]
1264 error: null
1266 error: null
1265 """
1267 """
1266 if not HasPermissionAny('hg.admin')():
1268 if not HasPermissionAny('hg.admin')():
1267 repos = RepoModel().get_all_user_repos(user=request.authuser.user_id)
1269 repos = RepoModel().get_all_user_repos(user=request.authuser.user_id)
1268 else:
1270 else:
1269 repos = Repository.query()
1271 repos = Repository.query()
1270
1272
1271 return [
1273 return [
1272 repo.get_api_data()
1274 repo.get_api_data()
1273 for repo in repos
1275 for repo in repos
1274 ]
1276 ]
1275
1277
1276 # permission check inside
1278 # permission check inside
1277 def get_repo_nodes(self, repoid, revision, root_path,
1279 def get_repo_nodes(self, repoid, revision, root_path,
1278 ret_type=Optional('all')):
1280 ret_type=Optional('all')):
1279 """
1281 """
1280 returns a list of nodes and it's children in a flat list for a given path
1282 returns a list of nodes and it's children in a flat list for a given path
1281 at given revision. It's possible to specify ret_type to show only `files` or
1283 at given revision. It's possible to specify ret_type to show only `files` or
1282 `dirs`. This command can be executed only using api_key belonging to
1284 `dirs`. This command can be executed only using api_key belonging to
1283 user with admin rights or regular user that have at least read access to repository.
1285 user with admin rights or regular user that have at least read access to repository.
1284
1286
1285 :param repoid: repository name or repository id
1287 :param repoid: repository name or repository id
1286 :type repoid: str or int
1288 :type repoid: str or int
1287 :param revision: revision for which listing should be done
1289 :param revision: revision for which listing should be done
1288 :type revision: str
1290 :type revision: str
1289 :param root_path: path from which start displaying
1291 :param root_path: path from which start displaying
1290 :type root_path: str
1292 :type root_path: str
1291 :param ret_type: return type 'all|files|dirs' nodes
1293 :param ret_type: return type 'all|files|dirs' nodes
1292 :type ret_type: Optional(str)
1294 :type ret_type: Optional(str)
1293
1295
1294
1296
1295 OUTPUT::
1297 OUTPUT::
1296
1298
1297 id : <id_given_in_input>
1299 id : <id_given_in_input>
1298 result: [
1300 result: [
1299 {
1301 {
1300 "name" : "<name>"
1302 "name" : "<name>"
1301 "type" : "<type>",
1303 "type" : "<type>",
1302 },
1304 },
1303 …
1305 …
1304 ]
1306 ]
1305 error: null
1307 error: null
1306 """
1308 """
1307 repo = get_repo_or_error(repoid)
1309 repo = get_repo_or_error(repoid)
1308
1310
1309 if not HasPermissionAny('hg.admin')():
1311 if not HasPermissionAny('hg.admin')():
1310 if not HasRepoPermissionLevel('read')(repo.repo_name):
1312 if not HasRepoPermissionLevel('read')(repo.repo_name):
1311 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1313 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1312
1314
1313 ret_type = Optional.extract(ret_type)
1315 ret_type = Optional.extract(ret_type)
1314 _map = {}
1316 _map = {}
1315 try:
1317 try:
1316 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1318 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1317 flat=False)
1319 flat=False)
1318 _map = {
1320 _map = {
1319 'all': _d + _f,
1321 'all': _d + _f,
1320 'files': _f,
1322 'files': _f,
1321 'dirs': _d,
1323 'dirs': _d,
1322 }
1324 }
1323 return _map[ret_type]
1325 return _map[ret_type]
1324 except KeyError:
1326 except KeyError:
1325 raise JSONRPCError('ret_type must be one of %s'
1327 raise JSONRPCError('ret_type must be one of %s'
1326 % (','.join(_map.keys())))
1328 % (','.join(_map.keys())))
1327 except Exception:
1329 except Exception:
1328 log.error(traceback.format_exc())
1330 log.error(traceback.format_exc())
1329 raise JSONRPCError(
1331 raise JSONRPCError(
1330 'failed to get repo: `%s` nodes' % repo.repo_name
1332 'failed to get repo: `%s` nodes' % repo.repo_name
1331 )
1333 )
1332
1334
1333 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1335 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1334 def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')),
1336 def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')),
1335 repo_type=Optional('hg'), description=Optional(''),
1337 repo_type=Optional('hg'), description=Optional(''),
1336 private=Optional(False), clone_uri=Optional(None),
1338 private=Optional(False), clone_uri=Optional(None),
1337 landing_rev=Optional('rev:tip'),
1339 landing_rev=Optional('rev:tip'),
1338 enable_statistics=Optional(False),
1340 enable_statistics=Optional(False),
1339 enable_locking=Optional(False),
1341 enable_locking=Optional(False),
1340 enable_downloads=Optional(False),
1342 enable_downloads=Optional(False),
1341 copy_permissions=Optional(False)):
1343 copy_permissions=Optional(False)):
1342 """
1344 """
1343 Creates a repository. If repository name contains "/", all needed repository
1345 Creates a repository. If repository name contains "/", all needed repository
1344 groups will be created. For example "foo/bar/baz" will create groups
1346 groups will be created. For example "foo/bar/baz" will create groups
1345 "foo", "bar" (with "foo" as parent), and create "baz" repository with
1347 "foo", "bar" (with "foo" as parent), and create "baz" repository with
1346 "bar" as group. This command can be executed only using api_key
1348 "bar" as group. This command can be executed only using api_key
1347 belonging to user with admin rights or regular user that have create
1349 belonging to user with admin rights or regular user that have create
1348 repository permission. Regular users cannot specify owner parameter
1350 repository permission. Regular users cannot specify owner parameter
1349
1351
1350 :param repo_name: repository name
1352 :param repo_name: repository name
1351 :type repo_name: str
1353 :type repo_name: str
1352 :param owner: user_id or username
1354 :param owner: user_id or username
1353 :type owner: Optional(str)
1355 :type owner: Optional(str)
1354 :param repo_type: 'hg' or 'git'
1356 :param repo_type: 'hg' or 'git'
1355 :type repo_type: Optional(str)
1357 :type repo_type: Optional(str)
1356 :param description: repository description
1358 :param description: repository description
1357 :type description: Optional(str)
1359 :type description: Optional(str)
1358 :param private:
1360 :param private:
1359 :type private: bool
1361 :type private: bool
1360 :param clone_uri:
1362 :param clone_uri:
1361 :type clone_uri: str
1363 :type clone_uri: str
1362 :param landing_rev: <rev_type>:<rev>
1364 :param landing_rev: <rev_type>:<rev>
1363 :type landing_rev: str
1365 :type landing_rev: str
1364 :param enable_locking:
1366 :param enable_locking:
1365 :type enable_locking: bool
1367 :type enable_locking: bool
1366 :param enable_downloads:
1368 :param enable_downloads:
1367 :type enable_downloads: bool
1369 :type enable_downloads: bool
1368 :param enable_statistics:
1370 :param enable_statistics:
1369 :type enable_statistics: bool
1371 :type enable_statistics: bool
1370 :param copy_permissions: Copy permission from group that repository is
1372 :param copy_permissions: Copy permission from group that repository is
1371 being created.
1373 being created.
1372 :type copy_permissions: bool
1374 :type copy_permissions: bool
1373
1375
1374 OUTPUT::
1376 OUTPUT::
1375
1377
1376 id : <id_given_in_input>
1378 id : <id_given_in_input>
1377 result: {
1379 result: {
1378 "msg": "Created new repository `<reponame>`",
1380 "msg": "Created new repository `<reponame>`",
1379 "success": true,
1381 "success": true,
1380 "task": "<celery task id or None if done sync>"
1382 "task": "<celery task id or None if done sync>"
1381 }
1383 }
1382 error: null
1384 error: null
1383
1385
1384 ERROR OUTPUT::
1386 ERROR OUTPUT::
1385
1387
1386 id : <id_given_in_input>
1388 id : <id_given_in_input>
1387 result : null
1389 result : null
1388 error : {
1390 error : {
1389 'failed to create repository `<repo_name>`
1391 'failed to create repository `<repo_name>`
1390 }
1392 }
1391
1393
1392 """
1394 """
1393 if not HasPermissionAny('hg.admin')():
1395 if not HasPermissionAny('hg.admin')():
1394 if not isinstance(owner, Optional):
1396 if not isinstance(owner, Optional):
1395 # forbid setting owner for non-admins
1397 # forbid setting owner for non-admins
1396 raise JSONRPCError(
1398 raise JSONRPCError(
1397 'Only Kallithea admin can specify `owner` param'
1399 'Only Kallithea admin can specify `owner` param'
1398 )
1400 )
1399 if isinstance(owner, Optional):
1401 if isinstance(owner, Optional):
1400 owner = request.authuser.user_id
1402 owner = request.authuser.user_id
1401
1403
1402 owner = get_user_or_error(owner)
1404 owner = get_user_or_error(owner)
1403
1405
1404 if RepoModel().get_by_repo_name(repo_name):
1406 if RepoModel().get_by_repo_name(repo_name):
1405 raise JSONRPCError("repo `%s` already exist" % repo_name)
1407 raise JSONRPCError("repo `%s` already exist" % repo_name)
1406
1408
1407 defs = Setting.get_default_repo_settings(strip_prefix=True)
1409 defs = Setting.get_default_repo_settings(strip_prefix=True)
1408 if isinstance(private, Optional):
1410 if isinstance(private, Optional):
1409 private = defs.get('repo_private') or Optional.extract(private)
1411 private = defs.get('repo_private') or Optional.extract(private)
1410 if isinstance(repo_type, Optional):
1412 if isinstance(repo_type, Optional):
1411 repo_type = defs.get('repo_type')
1413 repo_type = defs.get('repo_type')
1412 if isinstance(enable_statistics, Optional):
1414 if isinstance(enable_statistics, Optional):
1413 enable_statistics = defs.get('repo_enable_statistics')
1415 enable_statistics = defs.get('repo_enable_statistics')
1414 if isinstance(enable_locking, Optional):
1416 if isinstance(enable_locking, Optional):
1415 enable_locking = defs.get('repo_enable_locking')
1417 enable_locking = defs.get('repo_enable_locking')
1416 if isinstance(enable_downloads, Optional):
1418 if isinstance(enable_downloads, Optional):
1417 enable_downloads = defs.get('repo_enable_downloads')
1419 enable_downloads = defs.get('repo_enable_downloads')
1418
1420
1419 clone_uri = Optional.extract(clone_uri)
1421 clone_uri = Optional.extract(clone_uri)
1420 description = Optional.extract(description)
1422 description = Optional.extract(description)
1421 landing_rev = Optional.extract(landing_rev)
1423 landing_rev = Optional.extract(landing_rev)
1422 copy_permissions = Optional.extract(copy_permissions)
1424 copy_permissions = Optional.extract(copy_permissions)
1423
1425
1424 try:
1426 try:
1425 repo_name_cleaned = repo_name.split('/')[-1]
1427 repo_name_cleaned = repo_name.split('/')[-1]
1426 # create structure of groups and return the last group
1428 # create structure of groups and return the last group
1427 repo_group = map_groups(repo_name)
1429 repo_group = map_groups(repo_name)
1428 data = dict(
1430 data = dict(
1429 repo_name=repo_name_cleaned,
1431 repo_name=repo_name_cleaned,
1430 repo_name_full=repo_name,
1432 repo_name_full=repo_name,
1431 repo_type=repo_type,
1433 repo_type=repo_type,
1432 repo_description=description,
1434 repo_description=description,
1433 owner=owner,
1435 owner=owner,
1434 repo_private=private,
1436 repo_private=private,
1435 clone_uri=clone_uri,
1437 clone_uri=clone_uri,
1436 repo_group=repo_group,
1438 repo_group=repo_group,
1437 repo_landing_rev=landing_rev,
1439 repo_landing_rev=landing_rev,
1438 enable_statistics=enable_statistics,
1440 enable_statistics=enable_statistics,
1439 enable_locking=enable_locking,
1441 enable_locking=enable_locking,
1440 enable_downloads=enable_downloads,
1442 enable_downloads=enable_downloads,
1441 repo_copy_permissions=copy_permissions,
1443 repo_copy_permissions=copy_permissions,
1442 )
1444 )
1443
1445
1444 task = RepoModel().create(form_data=data, cur_user=owner)
1446 task = RepoModel().create(form_data=data, cur_user=owner)
1445 task_id = task.task_id
1447 task_id = task.task_id
1446 # no commit, it's done in RepoModel, or async via celery
1448 # no commit, it's done in RepoModel, or async via celery
1447 return dict(
1449 return dict(
1448 msg="Created new repository `%s`" % (repo_name,),
1450 msg="Created new repository `%s`" % (repo_name,),
1449 success=True, # cannot return the repo data here since fork
1451 success=True, # cannot return the repo data here since fork
1450 # can be done async
1452 # can be done async
1451 task=task_id
1453 task=task_id
1452 )
1454 )
1453 except Exception:
1455 except Exception:
1454 log.error(traceback.format_exc())
1456 log.error(traceback.format_exc())
1455 raise JSONRPCError(
1457 raise JSONRPCError(
1456 'failed to create repository `%s`' % (repo_name,))
1458 'failed to create repository `%s`' % (repo_name,))
1457
1459
1458 # permission check inside
1460 # permission check inside
1459 def update_repo(self, repoid, name=Optional(None),
1461 def update_repo(self, repoid, name=Optional(None),
1460 owner=Optional(OAttr('apiuser')),
1462 owner=Optional(OAttr('apiuser')),
1461 group=Optional(None),
1463 group=Optional(None),
1462 description=Optional(''), private=Optional(False),
1464 description=Optional(''), private=Optional(False),
1463 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
1465 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
1464 enable_statistics=Optional(False),
1466 enable_statistics=Optional(False),
1465 enable_locking=Optional(False),
1467 enable_locking=Optional(False),
1466 enable_downloads=Optional(False)):
1468 enable_downloads=Optional(False)):
1467
1469
1468 """
1470 """
1469 Updates repo
1471 Updates repo
1470
1472
1471 :param repoid: repository name or repository id
1473 :param repoid: repository name or repository id
1472 :type repoid: str or int
1474 :type repoid: str or int
1473 :param name:
1475 :param name:
1474 :param owner:
1476 :param owner:
1475 :param group:
1477 :param group:
1476 :param description:
1478 :param description:
1477 :param private:
1479 :param private:
1478 :param clone_uri:
1480 :param clone_uri:
1479 :param landing_rev:
1481 :param landing_rev:
1480 :param enable_statistics:
1482 :param enable_statistics:
1481 :param enable_locking:
1483 :param enable_locking:
1482 :param enable_downloads:
1484 :param enable_downloads:
1483 """
1485 """
1484 repo = get_repo_or_error(repoid)
1486 repo = get_repo_or_error(repoid)
1485 if not HasPermissionAny('hg.admin')():
1487 if not HasPermissionAny('hg.admin')():
1486 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1488 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1489 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1488
1490
1489 if (name != repo.repo_name and
1491 if (name != repo.repo_name and
1490 not HasPermissionAny('hg.create.repository')()
1492 not HasPermissionAny('hg.create.repository')()
1491 ):
1493 ):
1492 raise JSONRPCError('no permission to create (or move) repositories')
1494 raise JSONRPCError('no permission to create (or move) repositories')
1493
1495
1494 if not isinstance(owner, Optional):
1496 if not isinstance(owner, Optional):
1495 # forbid setting owner for non-admins
1497 # forbid setting owner for non-admins
1496 raise JSONRPCError(
1498 raise JSONRPCError(
1497 'Only Kallithea admin can specify `owner` param'
1499 'Only Kallithea admin can specify `owner` param'
1498 )
1500 )
1499
1501
1500 updates = {}
1502 updates = {}
1501 repo_group = group
1503 repo_group = group
1502 if not isinstance(repo_group, Optional):
1504 if not isinstance(repo_group, Optional):
1503 repo_group = get_repo_group_or_error(repo_group)
1505 repo_group = get_repo_group_or_error(repo_group)
1504 repo_group = repo_group.group_id
1506 repo_group = repo_group.group_id
1505 try:
1507 try:
1506 store_update(updates, name, 'repo_name')
1508 store_update(updates, name, 'repo_name')
1507 store_update(updates, repo_group, 'repo_group')
1509 store_update(updates, repo_group, 'repo_group')
1508 store_update(updates, owner, 'owner')
1510 store_update(updates, owner, 'owner')
1509 store_update(updates, description, 'repo_description')
1511 store_update(updates, description, 'repo_description')
1510 store_update(updates, private, 'repo_private')
1512 store_update(updates, private, 'repo_private')
1511 store_update(updates, clone_uri, 'clone_uri')
1513 store_update(updates, clone_uri, 'clone_uri')
1512 store_update(updates, landing_rev, 'repo_landing_rev')
1514 store_update(updates, landing_rev, 'repo_landing_rev')
1513 store_update(updates, enable_statistics, 'repo_enable_statistics')
1515 store_update(updates, enable_statistics, 'repo_enable_statistics')
1514 store_update(updates, enable_locking, 'repo_enable_locking')
1516 store_update(updates, enable_locking, 'repo_enable_locking')
1515 store_update(updates, enable_downloads, 'repo_enable_downloads')
1517 store_update(updates, enable_downloads, 'repo_enable_downloads')
1516
1518
1517 RepoModel().update(repo, **updates)
1519 RepoModel().update(repo, **updates)
1518 Session().commit()
1520 Session().commit()
1519 return dict(
1521 return dict(
1520 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1522 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1521 repository=repo.get_api_data()
1523 repository=repo.get_api_data()
1522 )
1524 )
1523 except Exception:
1525 except Exception:
1524 log.error(traceback.format_exc())
1526 log.error(traceback.format_exc())
1525 raise JSONRPCError('failed to update repo `%s`' % repoid)
1527 raise JSONRPCError('failed to update repo `%s`' % repoid)
1526
1528
1527 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1529 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1528 def fork_repo(self, repoid, fork_name,
1530 def fork_repo(self, repoid, fork_name,
1529 owner=Optional(OAttr('apiuser')),
1531 owner=Optional(OAttr('apiuser')),
1530 description=Optional(''), copy_permissions=Optional(False),
1532 description=Optional(''), copy_permissions=Optional(False),
1531 private=Optional(False), landing_rev=Optional('rev:tip')):
1533 private=Optional(False), landing_rev=Optional('rev:tip')):
1532 """
1534 """
1533 Creates a fork of given repo. In case of using celery this will
1535 Creates a fork of given repo. In case of using celery this will
1534 immediately return success message, while fork is going to be created
1536 immediately return success message, while fork is going to be created
1535 asynchronous. This command can be executed only using api_key belonging to
1537 asynchronous. This command can be executed only using api_key belonging to
1536 user with admin rights or regular user that have fork permission, and at least
1538 user with admin rights or regular user that have fork permission, and at least
1537 read access to forking repository. Regular users cannot specify owner parameter.
1539 read access to forking repository. Regular users cannot specify owner parameter.
1538
1540
1539 :param repoid: repository name or repository id
1541 :param repoid: repository name or repository id
1540 :type repoid: str or int
1542 :type repoid: str or int
1541 :param fork_name:
1543 :param fork_name:
1542 :param owner:
1544 :param owner:
1543 :param description:
1545 :param description:
1544 :param copy_permissions:
1546 :param copy_permissions:
1545 :param private:
1547 :param private:
1546 :param landing_rev:
1548 :param landing_rev:
1547
1549
1548 INPUT::
1550 INPUT::
1549
1551
1550 id : <id_for_response>
1552 id : <id_for_response>
1551 api_key : "<api_key>"
1553 api_key : "<api_key>"
1552 args: {
1554 args: {
1553 "repoid" : "<reponame or repo_id>",
1555 "repoid" : "<reponame or repo_id>",
1554 "fork_name": "<forkname>",
1556 "fork_name": "<forkname>",
1555 "owner": "<username or user_id = Optional(=apiuser)>",
1557 "owner": "<username or user_id = Optional(=apiuser)>",
1556 "description": "<description>",
1558 "description": "<description>",
1557 "copy_permissions": "<bool>",
1559 "copy_permissions": "<bool>",
1558 "private": "<bool>",
1560 "private": "<bool>",
1559 "landing_rev": "<landing_rev>"
1561 "landing_rev": "<landing_rev>"
1560 }
1562 }
1561
1563
1562 OUTPUT::
1564 OUTPUT::
1563
1565
1564 id : <id_given_in_input>
1566 id : <id_given_in_input>
1565 result: {
1567 result: {
1566 "msg": "Created fork of `<reponame>` as `<forkname>`",
1568 "msg": "Created fork of `<reponame>` as `<forkname>`",
1567 "success": true,
1569 "success": true,
1568 "task": "<celery task id or None if done sync>"
1570 "task": "<celery task id or None if done sync>"
1569 }
1571 }
1570 error: null
1572 error: null
1571
1573
1572 """
1574 """
1573 repo = get_repo_or_error(repoid)
1575 repo = get_repo_or_error(repoid)
1574 repo_name = repo.repo_name
1576 repo_name = repo.repo_name
1575
1577
1576 _repo = RepoModel().get_by_repo_name(fork_name)
1578 _repo = RepoModel().get_by_repo_name(fork_name)
1577 if _repo:
1579 if _repo:
1578 type_ = 'fork' if _repo.fork else 'repo'
1580 type_ = 'fork' if _repo.fork else 'repo'
1579 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1581 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1580
1582
1581 if HasPermissionAny('hg.admin')():
1583 if HasPermissionAny('hg.admin')():
1582 pass
1584 pass
1583 elif HasRepoPermissionLevel('read')(repo.repo_name):
1585 elif HasRepoPermissionLevel('read')(repo.repo_name):
1584 if not isinstance(owner, Optional):
1586 if not isinstance(owner, Optional):
1585 # forbid setting owner for non-admins
1587 # forbid setting owner for non-admins
1586 raise JSONRPCError(
1588 raise JSONRPCError(
1587 'Only Kallithea admin can specify `owner` param'
1589 'Only Kallithea admin can specify `owner` param'
1588 )
1590 )
1589
1591
1590 if not HasPermissionAny('hg.create.repository')():
1592 if not HasPermissionAny('hg.create.repository')():
1591 raise JSONRPCError('no permission to create repositories')
1593 raise JSONRPCError('no permission to create repositories')
1592 else:
1594 else:
1593 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1595 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1594
1596
1595 if isinstance(owner, Optional):
1597 if isinstance(owner, Optional):
1596 owner = request.authuser.user_id
1598 owner = request.authuser.user_id
1597
1599
1598 owner = get_user_or_error(owner)
1600 owner = get_user_or_error(owner)
1599
1601
1600 try:
1602 try:
1601 # create structure of groups and return the last group
1603 # create structure of groups and return the last group
1602 group = map_groups(fork_name)
1604 group = map_groups(fork_name)
1603 fork_base_name = fork_name.rsplit('/', 1)[-1]
1605 fork_base_name = fork_name.rsplit('/', 1)[-1]
1604
1606
1605 form_data = dict(
1607 form_data = dict(
1606 repo_name=fork_base_name,
1608 repo_name=fork_base_name,
1607 repo_name_full=fork_name,
1609 repo_name_full=fork_name,
1608 repo_group=group,
1610 repo_group=group,
1609 repo_type=repo.repo_type,
1611 repo_type=repo.repo_type,
1610 description=Optional.extract(description),
1612 description=Optional.extract(description),
1611 private=Optional.extract(private),
1613 private=Optional.extract(private),
1612 copy_permissions=Optional.extract(copy_permissions),
1614 copy_permissions=Optional.extract(copy_permissions),
1613 landing_rev=Optional.extract(landing_rev),
1615 landing_rev=Optional.extract(landing_rev),
1614 update_after_clone=False,
1616 update_after_clone=False,
1615 fork_parent_id=repo.repo_id,
1617 fork_parent_id=repo.repo_id,
1616 )
1618 )
1617 task = RepoModel().create_fork(form_data, cur_user=owner)
1619 task = RepoModel().create_fork(form_data, cur_user=owner)
1618 # no commit, it's done in RepoModel, or async via celery
1620 # no commit, it's done in RepoModel, or async via celery
1619 task_id = task.task_id
1621 task_id = task.task_id
1620 return dict(
1622 return dict(
1621 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1623 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1622 fork_name),
1624 fork_name),
1623 success=True, # cannot return the repo data here since fork
1625 success=True, # cannot return the repo data here since fork
1624 # can be done async
1626 # can be done async
1625 task=task_id
1627 task=task_id
1626 )
1628 )
1627 except Exception:
1629 except Exception:
1628 log.error(traceback.format_exc())
1630 log.error(traceback.format_exc())
1629 raise JSONRPCError(
1631 raise JSONRPCError(
1630 'failed to fork repository `%s` as `%s`' % (repo_name,
1632 'failed to fork repository `%s` as `%s`' % (repo_name,
1631 fork_name)
1633 fork_name)
1632 )
1634 )
1633
1635
1634 # permission check inside
1636 # permission check inside
1635 def delete_repo(self, repoid, forks=Optional('')):
1637 def delete_repo(self, repoid, forks=Optional('')):
1636 """
1638 """
1637 Deletes a repository. This command can be executed only using api_key belonging
1639 Deletes a repository. This command can be executed only using api_key belonging
1638 to user with admin rights or regular user that have admin access to repository.
1640 to user with admin rights or regular user that have admin access to repository.
1639 When `forks` param is set it's possible to detach or delete forks of deleting
1641 When `forks` param is set it's possible to detach or delete forks of deleting
1640 repository
1642 repository
1641
1643
1642 :param repoid: repository name or repository id
1644 :param repoid: repository name or repository id
1643 :type repoid: str or int
1645 :type repoid: str or int
1644 :param forks: `detach` or `delete`, what do do with attached forks for repo
1646 :param forks: `detach` or `delete`, what do do with attached forks for repo
1645 :type forks: Optional(str)
1647 :type forks: Optional(str)
1646
1648
1647 OUTPUT::
1649 OUTPUT::
1648
1650
1649 id : <id_given_in_input>
1651 id : <id_given_in_input>
1650 result: {
1652 result: {
1651 "msg": "Deleted repository `<reponame>`",
1653 "msg": "Deleted repository `<reponame>`",
1652 "success": true
1654 "success": true
1653 }
1655 }
1654 error: null
1656 error: null
1655
1657
1656 """
1658 """
1657 repo = get_repo_or_error(repoid)
1659 repo = get_repo_or_error(repoid)
1658
1660
1659 if not HasPermissionAny('hg.admin')():
1661 if not HasPermissionAny('hg.admin')():
1660 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1662 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1661 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1663 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1662
1664
1663 try:
1665 try:
1664 handle_forks = Optional.extract(forks)
1666 handle_forks = Optional.extract(forks)
1665 _forks_msg = ''
1667 _forks_msg = ''
1666 _forks = [f for f in repo.forks]
1668 _forks = [f for f in repo.forks]
1667 if handle_forks == 'detach':
1669 if handle_forks == 'detach':
1668 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1670 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1669 elif handle_forks == 'delete':
1671 elif handle_forks == 'delete':
1670 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1672 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1671 elif _forks:
1673 elif _forks:
1672 raise JSONRPCError(
1674 raise JSONRPCError(
1673 'Cannot delete `%s` it still contains attached forks' %
1675 'Cannot delete `%s` it still contains attached forks' %
1674 (repo.repo_name,)
1676 (repo.repo_name,)
1675 )
1677 )
1676
1678
1677 RepoModel().delete(repo, forks=forks)
1679 RepoModel().delete(repo, forks=forks)
1678 Session().commit()
1680 Session().commit()
1679 return dict(
1681 return dict(
1680 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1682 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1681 success=True
1683 success=True
1682 )
1684 )
1683 except Exception:
1685 except Exception:
1684 log.error(traceback.format_exc())
1686 log.error(traceback.format_exc())
1685 raise JSONRPCError(
1687 raise JSONRPCError(
1686 'failed to delete repository `%s`' % (repo.repo_name,)
1688 'failed to delete repository `%s`' % (repo.repo_name,)
1687 )
1689 )
1688
1690
1689 @HasPermissionAnyDecorator('hg.admin')
1691 @HasPermissionAnyDecorator('hg.admin')
1690 def grant_user_permission(self, repoid, userid, perm):
1692 def grant_user_permission(self, repoid, userid, perm):
1691 """
1693 """
1692 Grant permission for user on given repository, or update existing one
1694 Grant permission for user on given repository, or update existing one
1693 if found. This command can be executed only using api_key belonging to user
1695 if found. This command can be executed only using api_key belonging to user
1694 with admin rights.
1696 with admin rights.
1695
1697
1696 :param repoid: repository name or repository id
1698 :param repoid: repository name or repository id
1697 :type repoid: str or int
1699 :type repoid: str or int
1698 :param userid:
1700 :param userid:
1699 :param perm: (repository.(none|read|write|admin))
1701 :param perm: (repository.(none|read|write|admin))
1700 :type perm: str
1702 :type perm: str
1701
1703
1702 OUTPUT::
1704 OUTPUT::
1703
1705
1704 id : <id_given_in_input>
1706 id : <id_given_in_input>
1705 result: {
1707 result: {
1706 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1708 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1707 "success": true
1709 "success": true
1708 }
1710 }
1709 error: null
1711 error: null
1710 """
1712 """
1711 repo = get_repo_or_error(repoid)
1713 repo = get_repo_or_error(repoid)
1712 user = get_user_or_error(userid)
1714 user = get_user_or_error(userid)
1713 perm = get_perm_or_error(perm)
1715 perm = get_perm_or_error(perm)
1714
1716
1715 try:
1717 try:
1716
1718
1717 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1719 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1718
1720
1719 Session().commit()
1721 Session().commit()
1720 return dict(
1722 return dict(
1721 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1723 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1722 perm.permission_name, user.username, repo.repo_name
1724 perm.permission_name, user.username, repo.repo_name
1723 ),
1725 ),
1724 success=True
1726 success=True
1725 )
1727 )
1726 except Exception:
1728 except Exception:
1727 log.error(traceback.format_exc())
1729 log.error(traceback.format_exc())
1728 raise JSONRPCError(
1730 raise JSONRPCError(
1729 'failed to edit permission for user: `%s` in repo: `%s`' % (
1731 'failed to edit permission for user: `%s` in repo: `%s`' % (
1730 userid, repoid
1732 userid, repoid
1731 )
1733 )
1732 )
1734 )
1733
1735
1734 @HasPermissionAnyDecorator('hg.admin')
1736 @HasPermissionAnyDecorator('hg.admin')
1735 def revoke_user_permission(self, repoid, userid):
1737 def revoke_user_permission(self, repoid, userid):
1736 """
1738 """
1737 Revoke permission for user on given repository. This command can be executed
1739 Revoke permission for user on given repository. This command can be executed
1738 only using api_key belonging to user with admin rights.
1740 only using api_key belonging to user with admin rights.
1739
1741
1740 :param repoid: repository name or repository id
1742 :param repoid: repository name or repository id
1741 :type repoid: str or int
1743 :type repoid: str or int
1742 :param userid:
1744 :param userid:
1743
1745
1744 OUTPUT::
1746 OUTPUT::
1745
1747
1746 id : <id_given_in_input>
1748 id : <id_given_in_input>
1747 result: {
1749 result: {
1748 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1750 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1749 "success": true
1751 "success": true
1750 }
1752 }
1751 error: null
1753 error: null
1752
1754
1753 """
1755 """
1754
1756
1755 repo = get_repo_or_error(repoid)
1757 repo = get_repo_or_error(repoid)
1756 user = get_user_or_error(userid)
1758 user = get_user_or_error(userid)
1757 try:
1759 try:
1758 RepoModel().revoke_user_permission(repo=repo, user=user)
1760 RepoModel().revoke_user_permission(repo=repo, user=user)
1759 Session().commit()
1761 Session().commit()
1760 return dict(
1762 return dict(
1761 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1763 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1762 user.username, repo.repo_name
1764 user.username, repo.repo_name
1763 ),
1765 ),
1764 success=True
1766 success=True
1765 )
1767 )
1766 except Exception:
1768 except Exception:
1767 log.error(traceback.format_exc())
1769 log.error(traceback.format_exc())
1768 raise JSONRPCError(
1770 raise JSONRPCError(
1769 'failed to edit permission for user: `%s` in repo: `%s`' % (
1771 'failed to edit permission for user: `%s` in repo: `%s`' % (
1770 userid, repoid
1772 userid, repoid
1771 )
1773 )
1772 )
1774 )
1773
1775
1774 # permission check inside
1776 # permission check inside
1775 def grant_user_group_permission(self, repoid, usergroupid, perm):
1777 def grant_user_group_permission(self, repoid, usergroupid, perm):
1776 """
1778 """
1777 Grant permission for user group on given repository, or update
1779 Grant permission for user group on given repository, or update
1778 existing one if found. This command can be executed only using
1780 existing one if found. This command can be executed only using
1779 api_key belonging to user with admin rights.
1781 api_key belonging to user with admin rights.
1780
1782
1781 :param repoid: repository name or repository id
1783 :param repoid: repository name or repository id
1782 :type repoid: str or int
1784 :type repoid: str or int
1783 :param usergroupid: id of usergroup
1785 :param usergroupid: id of usergroup
1784 :type usergroupid: str or int
1786 :type usergroupid: str or int
1785 :param perm: (repository.(none|read|write|admin))
1787 :param perm: (repository.(none|read|write|admin))
1786 :type perm: str
1788 :type perm: str
1787
1789
1788 OUTPUT::
1790 OUTPUT::
1789
1791
1790 id : <id_given_in_input>
1792 id : <id_given_in_input>
1791 result : {
1793 result : {
1792 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1794 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1793 "success": true
1795 "success": true
1794
1796
1795 }
1797 }
1796 error : null
1798 error : null
1797
1799
1798 ERROR OUTPUT::
1800 ERROR OUTPUT::
1799
1801
1800 id : <id_given_in_input>
1802 id : <id_given_in_input>
1801 result : null
1803 result : null
1802 error : {
1804 error : {
1803 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1805 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1804 }
1806 }
1805
1807
1806 """
1808 """
1807 repo = get_repo_or_error(repoid)
1809 repo = get_repo_or_error(repoid)
1808 perm = get_perm_or_error(perm)
1810 perm = get_perm_or_error(perm)
1809 user_group = get_user_group_or_error(usergroupid)
1811 user_group = get_user_group_or_error(usergroupid)
1810 if not HasPermissionAny('hg.admin')():
1812 if not HasPermissionAny('hg.admin')():
1811 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1813 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1812 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1814 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1813
1815
1814 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1816 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1815 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1817 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1816
1818
1817 try:
1819 try:
1818 RepoModel().grant_user_group_permission(
1820 RepoModel().grant_user_group_permission(
1819 repo=repo, group_name=user_group, perm=perm)
1821 repo=repo, group_name=user_group, perm=perm)
1820
1822
1821 Session().commit()
1823 Session().commit()
1822 return dict(
1824 return dict(
1823 msg='Granted perm: `%s` for user group: `%s` in '
1825 msg='Granted perm: `%s` for user group: `%s` in '
1824 'repo: `%s`' % (
1826 'repo: `%s`' % (
1825 perm.permission_name, user_group.users_group_name,
1827 perm.permission_name, user_group.users_group_name,
1826 repo.repo_name
1828 repo.repo_name
1827 ),
1829 ),
1828 success=True
1830 success=True
1829 )
1831 )
1830 except Exception:
1832 except Exception:
1831 log.error(traceback.format_exc())
1833 log.error(traceback.format_exc())
1832 raise JSONRPCError(
1834 raise JSONRPCError(
1833 'failed to edit permission for user group: `%s` in '
1835 'failed to edit permission for user group: `%s` in '
1834 'repo: `%s`' % (
1836 'repo: `%s`' % (
1835 usergroupid, repo.repo_name
1837 usergroupid, repo.repo_name
1836 )
1838 )
1837 )
1839 )
1838
1840
1839 # permission check inside
1841 # permission check inside
1840 def revoke_user_group_permission(self, repoid, usergroupid):
1842 def revoke_user_group_permission(self, repoid, usergroupid):
1841 """
1843 """
1842 Revoke permission for user group on given repository. This command can be
1844 Revoke permission for user group on given repository. This command can be
1843 executed only using api_key belonging to user with admin rights.
1845 executed only using api_key belonging to user with admin rights.
1844
1846
1845 :param repoid: repository name or repository id
1847 :param repoid: repository name or repository id
1846 :type repoid: str or int
1848 :type repoid: str or int
1847 :param usergroupid:
1849 :param usergroupid:
1848
1850
1849 OUTPUT::
1851 OUTPUT::
1850
1852
1851 id : <id_given_in_input>
1853 id : <id_given_in_input>
1852 result: {
1854 result: {
1853 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1855 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1854 "success": true
1856 "success": true
1855 }
1857 }
1856 error: null
1858 error: null
1857 """
1859 """
1858 repo = get_repo_or_error(repoid)
1860 repo = get_repo_or_error(repoid)
1859 user_group = get_user_group_or_error(usergroupid)
1861 user_group = get_user_group_or_error(usergroupid)
1860 if not HasPermissionAny('hg.admin')():
1862 if not HasPermissionAny('hg.admin')():
1861 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1863 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1862 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1864 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1863
1865
1864 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1866 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1865 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1867 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1866
1868
1867 try:
1869 try:
1868 RepoModel().revoke_user_group_permission(
1870 RepoModel().revoke_user_group_permission(
1869 repo=repo, group_name=user_group)
1871 repo=repo, group_name=user_group)
1870
1872
1871 Session().commit()
1873 Session().commit()
1872 return dict(
1874 return dict(
1873 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1875 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1874 user_group.users_group_name, repo.repo_name
1876 user_group.users_group_name, repo.repo_name
1875 ),
1877 ),
1876 success=True
1878 success=True
1877 )
1879 )
1878 except Exception:
1880 except Exception:
1879 log.error(traceback.format_exc())
1881 log.error(traceback.format_exc())
1880 raise JSONRPCError(
1882 raise JSONRPCError(
1881 'failed to edit permission for user group: `%s` in '
1883 'failed to edit permission for user group: `%s` in '
1882 'repo: `%s`' % (
1884 'repo: `%s`' % (
1883 user_group.users_group_name, repo.repo_name
1885 user_group.users_group_name, repo.repo_name
1884 )
1886 )
1885 )
1887 )
1886
1888
1887 @HasPermissionAnyDecorator('hg.admin')
1889 @HasPermissionAnyDecorator('hg.admin')
1888 def get_repo_group(self, repogroupid):
1890 def get_repo_group(self, repogroupid):
1889 """
1891 """
1890 Returns given repo group together with permissions, and repositories
1892 Returns given repo group together with permissions, and repositories
1891 inside the group
1893 inside the group
1892
1894
1893 :param repogroupid: id/name of repository group
1895 :param repogroupid: id/name of repository group
1894 :type repogroupid: str or int
1896 :type repogroupid: str or int
1895 """
1897 """
1896 repo_group = get_repo_group_or_error(repogroupid)
1898 repo_group = get_repo_group_or_error(repogroupid)
1897
1899
1898 members = []
1900 members = []
1899 for user in repo_group.repo_group_to_perm:
1901 for user in repo_group.repo_group_to_perm:
1900 perm = user.permission.permission_name
1902 perm = user.permission.permission_name
1901 user = user.user
1903 user = user.user
1902 user_data = {
1904 user_data = {
1903 'name': user.username,
1905 'name': user.username,
1904 'type': "user",
1906 'type': "user",
1905 'permission': perm
1907 'permission': perm
1906 }
1908 }
1907 members.append(user_data)
1909 members.append(user_data)
1908
1910
1909 for user_group in repo_group.users_group_to_perm:
1911 for user_group in repo_group.users_group_to_perm:
1910 perm = user_group.permission.permission_name
1912 perm = user_group.permission.permission_name
1911 user_group = user_group.users_group
1913 user_group = user_group.users_group
1912 user_group_data = {
1914 user_group_data = {
1913 'name': user_group.users_group_name,
1915 'name': user_group.users_group_name,
1914 'type': "user_group",
1916 'type': "user_group",
1915 'permission': perm
1917 'permission': perm
1916 }
1918 }
1917 members.append(user_group_data)
1919 members.append(user_group_data)
1918
1920
1919 data = repo_group.get_api_data()
1921 data = repo_group.get_api_data()
1920 data["members"] = members
1922 data["members"] = members
1921 return data
1923 return data
1922
1924
1923 @HasPermissionAnyDecorator('hg.admin')
1925 @HasPermissionAnyDecorator('hg.admin')
1924 def get_repo_groups(self):
1926 def get_repo_groups(self):
1925 """
1927 """
1926 Returns all repository groups
1928 Returns all repository groups
1927
1929
1928 """
1930 """
1929 return [
1931 return [
1930 repo_group.get_api_data()
1932 repo_group.get_api_data()
1931 for repo_group in RepoGroup.query()
1933 for repo_group in RepoGroup.query()
1932 ]
1934 ]
1933
1935
1934 @HasPermissionAnyDecorator('hg.admin')
1936 @HasPermissionAnyDecorator('hg.admin')
1935 def create_repo_group(self, group_name, description=Optional(''),
1937 def create_repo_group(self, group_name, description=Optional(''),
1936 owner=Optional(OAttr('apiuser')),
1938 owner=Optional(OAttr('apiuser')),
1937 parent=Optional(None),
1939 parent=Optional(None),
1938 copy_permissions=Optional(False)):
1940 copy_permissions=Optional(False)):
1939 """
1941 """
1940 Creates a repository group. This command can be executed only using
1942 Creates a repository group. This command can be executed only using
1941 api_key belonging to user with admin rights.
1943 api_key belonging to user with admin rights.
1942
1944
1943 :param group_name:
1945 :param group_name:
1944 :type group_name:
1946 :type group_name:
1945 :param description:
1947 :param description:
1946 :type description:
1948 :type description:
1947 :param owner:
1949 :param owner:
1948 :type owner:
1950 :type owner:
1949 :param parent:
1951 :param parent:
1950 :type parent:
1952 :type parent:
1951 :param copy_permissions:
1953 :param copy_permissions:
1952 :type copy_permissions:
1954 :type copy_permissions:
1953
1955
1954 OUTPUT::
1956 OUTPUT::
1955
1957
1956 id : <id_given_in_input>
1958 id : <id_given_in_input>
1957 result : {
1959 result : {
1958 "msg": "created new repo group `<repo_group_name>`"
1960 "msg": "created new repo group `<repo_group_name>`"
1959 "repo_group": <repogroup_object>
1961 "repo_group": <repogroup_object>
1960 }
1962 }
1961 error : null
1963 error : null
1962
1964
1963 ERROR OUTPUT::
1965 ERROR OUTPUT::
1964
1966
1965 id : <id_given_in_input>
1967 id : <id_given_in_input>
1966 result : null
1968 result : null
1967 error : {
1969 error : {
1968 failed to create repo group `<repogroupid>`
1970 failed to create repo group `<repogroupid>`
1969 }
1971 }
1970
1972
1971 """
1973 """
1972 if RepoGroup.get_by_group_name(group_name):
1974 if RepoGroup.get_by_group_name(group_name):
1973 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1975 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1974
1976
1975 if isinstance(owner, Optional):
1977 if isinstance(owner, Optional):
1976 owner = request.authuser.user_id
1978 owner = request.authuser.user_id
1977 group_description = Optional.extract(description)
1979 group_description = Optional.extract(description)
1978 parent_group = Optional.extract(parent)
1980 parent_group = Optional.extract(parent)
1979 if not isinstance(parent, Optional):
1981 if not isinstance(parent, Optional):
1980 parent_group = get_repo_group_or_error(parent_group)
1982 parent_group = get_repo_group_or_error(parent_group)
1981
1983
1982 copy_permissions = Optional.extract(copy_permissions)
1984 copy_permissions = Optional.extract(copy_permissions)
1983 try:
1985 try:
1984 repo_group = RepoGroupModel().create(
1986 repo_group = RepoGroupModel().create(
1985 group_name=group_name,
1987 group_name=group_name,
1986 group_description=group_description,
1988 group_description=group_description,
1987 owner=owner,
1989 owner=owner,
1988 parent=parent_group,
1990 parent=parent_group,
1989 copy_permissions=copy_permissions
1991 copy_permissions=copy_permissions
1990 )
1992 )
1991 Session().commit()
1993 Session().commit()
1992 return dict(
1994 return dict(
1993 msg='created new repo group `%s`' % group_name,
1995 msg='created new repo group `%s`' % group_name,
1994 repo_group=repo_group.get_api_data()
1996 repo_group=repo_group.get_api_data()
1995 )
1997 )
1996 except Exception:
1998 except Exception:
1997
1999
1998 log.error(traceback.format_exc())
2000 log.error(traceback.format_exc())
1999 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
2001 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
2000
2002
2001 @HasPermissionAnyDecorator('hg.admin')
2003 @HasPermissionAnyDecorator('hg.admin')
2002 def update_repo_group(self, repogroupid, group_name=Optional(''),
2004 def update_repo_group(self, repogroupid, group_name=Optional(''),
2003 description=Optional(''),
2005 description=Optional(''),
2004 owner=Optional(OAttr('apiuser')),
2006 owner=Optional(OAttr('apiuser')),
2005 parent=Optional(None), enable_locking=Optional(False)):
2007 parent=Optional(None), enable_locking=Optional(False)):
2006 repo_group = get_repo_group_or_error(repogroupid)
2008 repo_group = get_repo_group_or_error(repogroupid)
2007
2009
2008 updates = {}
2010 updates = {}
2009 try:
2011 try:
2010 store_update(updates, group_name, 'group_name')
2012 store_update(updates, group_name, 'group_name')
2011 store_update(updates, description, 'group_description')
2013 store_update(updates, description, 'group_description')
2012 store_update(updates, owner, 'owner')
2014 store_update(updates, owner, 'owner')
2013 store_update(updates, parent, 'parent_group')
2015 store_update(updates, parent, 'parent_group')
2014 store_update(updates, enable_locking, 'enable_locking')
2016 store_update(updates, enable_locking, 'enable_locking')
2015 repo_group = RepoGroupModel().update(repo_group, updates)
2017 repo_group = RepoGroupModel().update(repo_group, updates)
2016 Session().commit()
2018 Session().commit()
2017 return dict(
2019 return dict(
2018 msg='updated repository group ID:%s %s' % (repo_group.group_id,
2020 msg='updated repository group ID:%s %s' % (repo_group.group_id,
2019 repo_group.group_name),
2021 repo_group.group_name),
2020 repo_group=repo_group.get_api_data()
2022 repo_group=repo_group.get_api_data()
2021 )
2023 )
2022 except Exception:
2024 except Exception:
2023 log.error(traceback.format_exc())
2025 log.error(traceback.format_exc())
2024 raise JSONRPCError('failed to update repository group `%s`'
2026 raise JSONRPCError('failed to update repository group `%s`'
2025 % (repogroupid,))
2027 % (repogroupid,))
2026
2028
2027 @HasPermissionAnyDecorator('hg.admin')
2029 @HasPermissionAnyDecorator('hg.admin')
2028 def delete_repo_group(self, repogroupid):
2030 def delete_repo_group(self, repogroupid):
2029 """
2031 """
2030
2032
2031 :param repogroupid: name or id of repository group
2033 :param repogroupid: name or id of repository group
2032 :type repogroupid: str or int
2034 :type repogroupid: str or int
2033
2035
2034 OUTPUT::
2036 OUTPUT::
2035
2037
2036 id : <id_given_in_input>
2038 id : <id_given_in_input>
2037 result : {
2039 result : {
2038 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2040 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2039 'repo_group': null
2041 'repo_group': null
2040 }
2042 }
2041 error : null
2043 error : null
2042
2044
2043 ERROR OUTPUT::
2045 ERROR OUTPUT::
2044
2046
2045 id : <id_given_in_input>
2047 id : <id_given_in_input>
2046 result : null
2048 result : null
2047 error : {
2049 error : {
2048 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2050 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2049 }
2051 }
2050
2052
2051 """
2053 """
2052 repo_group = get_repo_group_or_error(repogroupid)
2054 repo_group = get_repo_group_or_error(repogroupid)
2053
2055
2054 try:
2056 try:
2055 RepoGroupModel().delete(repo_group)
2057 RepoGroupModel().delete(repo_group)
2056 Session().commit()
2058 Session().commit()
2057 return dict(
2059 return dict(
2058 msg='deleted repo group ID:%s %s' %
2060 msg='deleted repo group ID:%s %s' %
2059 (repo_group.group_id, repo_group.group_name),
2061 (repo_group.group_id, repo_group.group_name),
2060 repo_group=None
2062 repo_group=None
2061 )
2063 )
2062 except Exception:
2064 except Exception:
2063 log.error(traceback.format_exc())
2065 log.error(traceback.format_exc())
2064 raise JSONRPCError('failed to delete repo group ID:%s %s' %
2066 raise JSONRPCError('failed to delete repo group ID:%s %s' %
2065 (repo_group.group_id, repo_group.group_name)
2067 (repo_group.group_id, repo_group.group_name)
2066 )
2068 )
2067
2069
2068 # permission check inside
2070 # permission check inside
2069 def grant_user_permission_to_repo_group(self, repogroupid, userid,
2071 def grant_user_permission_to_repo_group(self, repogroupid, userid,
2070 perm, apply_to_children=Optional('none')):
2072 perm, apply_to_children=Optional('none')):
2071 """
2073 """
2072 Grant permission for user on given repository group, or update existing
2074 Grant permission for user on given repository group, or update existing
2073 one if found. This command can be executed only using api_key belonging
2075 one if found. This command can be executed only using api_key belonging
2074 to user with admin rights, or user who has admin right to given repository
2076 to user with admin rights, or user who has admin right to given repository
2075 group.
2077 group.
2076
2078
2077 :param repogroupid: name or id of repository group
2079 :param repogroupid: name or id of repository group
2078 :type repogroupid: str or int
2080 :type repogroupid: str or int
2079 :param userid:
2081 :param userid:
2080 :param perm: (group.(none|read|write|admin))
2082 :param perm: (group.(none|read|write|admin))
2081 :type perm: str
2083 :type perm: str
2082 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2084 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2083 :type apply_to_children: str
2085 :type apply_to_children: str
2084
2086
2085 OUTPUT::
2087 OUTPUT::
2086
2088
2087 id : <id_given_in_input>
2089 id : <id_given_in_input>
2088 result: {
2090 result: {
2089 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2091 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2090 "success": true
2092 "success": true
2091 }
2093 }
2092 error: null
2094 error: null
2093
2095
2094 ERROR OUTPUT::
2096 ERROR OUTPUT::
2095
2097
2096 id : <id_given_in_input>
2098 id : <id_given_in_input>
2097 result : null
2099 result : null
2098 error : {
2100 error : {
2099 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2101 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2100 }
2102 }
2101
2103
2102 """
2104 """
2103
2105
2104 repo_group = get_repo_group_or_error(repogroupid)
2106 repo_group = get_repo_group_or_error(repogroupid)
2105
2107
2106 if not HasPermissionAny('hg.admin')():
2108 if not HasPermissionAny('hg.admin')():
2107 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2109 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2108 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2110 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2109
2111
2110 user = get_user_or_error(userid)
2112 user = get_user_or_error(userid)
2111 perm = get_perm_or_error(perm, prefix='group.')
2113 perm = get_perm_or_error(perm, prefix='group.')
2112 apply_to_children = Optional.extract(apply_to_children)
2114 apply_to_children = Optional.extract(apply_to_children)
2113
2115
2114 try:
2116 try:
2115 RepoGroupModel().add_permission(repo_group=repo_group,
2117 RepoGroupModel().add_permission(repo_group=repo_group,
2116 obj=user,
2118 obj=user,
2117 obj_type="user",
2119 obj_type="user",
2118 perm=perm,
2120 perm=perm,
2119 recursive=apply_to_children)
2121 recursive=apply_to_children)
2120 Session().commit()
2122 Session().commit()
2121 return dict(
2123 return dict(
2122 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
2124 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
2123 perm.permission_name, apply_to_children, user.username, repo_group.name
2125 perm.permission_name, apply_to_children, user.username, repo_group.name
2124 ),
2126 ),
2125 success=True
2127 success=True
2126 )
2128 )
2127 except Exception:
2129 except Exception:
2128 log.error(traceback.format_exc())
2130 log.error(traceback.format_exc())
2129 raise JSONRPCError(
2131 raise JSONRPCError(
2130 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2132 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2131 userid, repo_group.name))
2133 userid, repo_group.name))
2132
2134
2133 # permission check inside
2135 # permission check inside
2134 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
2136 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
2135 apply_to_children=Optional('none')):
2137 apply_to_children=Optional('none')):
2136 """
2138 """
2137 Revoke permission for user on given repository group. This command can
2139 Revoke permission for user on given repository group. This command can
2138 be executed only using api_key belonging to user with admin rights, or
2140 be executed only using api_key belonging to user with admin rights, or
2139 user who has admin right to given repository group.
2141 user who has admin right to given repository group.
2140
2142
2141 :param repogroupid: name or id of repository group
2143 :param repogroupid: name or id of repository group
2142 :type repogroupid: str or int
2144 :type repogroupid: str or int
2143 :param userid:
2145 :param userid:
2144 :type userid:
2146 :type userid:
2145 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2147 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2146 :type apply_to_children: str
2148 :type apply_to_children: str
2147
2149
2148 OUTPUT::
2150 OUTPUT::
2149
2151
2150 id : <id_given_in_input>
2152 id : <id_given_in_input>
2151 result: {
2153 result: {
2152 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2154 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2153 "success": true
2155 "success": true
2154 }
2156 }
2155 error: null
2157 error: null
2156
2158
2157 ERROR OUTPUT::
2159 ERROR OUTPUT::
2158
2160
2159 id : <id_given_in_input>
2161 id : <id_given_in_input>
2160 result : null
2162 result : null
2161 error : {
2163 error : {
2162 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2164 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2163 }
2165 }
2164
2166
2165 """
2167 """
2166
2168
2167 repo_group = get_repo_group_or_error(repogroupid)
2169 repo_group = get_repo_group_or_error(repogroupid)
2168
2170
2169 if not HasPermissionAny('hg.admin')():
2171 if not HasPermissionAny('hg.admin')():
2170 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2172 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2171 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2173 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2172
2174
2173 user = get_user_or_error(userid)
2175 user = get_user_or_error(userid)
2174 apply_to_children = Optional.extract(apply_to_children)
2176 apply_to_children = Optional.extract(apply_to_children)
2175
2177
2176 try:
2178 try:
2177 RepoGroupModel().delete_permission(repo_group=repo_group,
2179 RepoGroupModel().delete_permission(repo_group=repo_group,
2178 obj=user,
2180 obj=user,
2179 obj_type="user",
2181 obj_type="user",
2180 recursive=apply_to_children)
2182 recursive=apply_to_children)
2181
2183
2182 Session().commit()
2184 Session().commit()
2183 return dict(
2185 return dict(
2184 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2186 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2185 apply_to_children, user.username, repo_group.name
2187 apply_to_children, user.username, repo_group.name
2186 ),
2188 ),
2187 success=True
2189 success=True
2188 )
2190 )
2189 except Exception:
2191 except Exception:
2190 log.error(traceback.format_exc())
2192 log.error(traceback.format_exc())
2191 raise JSONRPCError(
2193 raise JSONRPCError(
2192 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2194 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2193 userid, repo_group.name))
2195 userid, repo_group.name))
2194
2196
2195 # permission check inside
2197 # permission check inside
2196 def grant_user_group_permission_to_repo_group(
2198 def grant_user_group_permission_to_repo_group(
2197 self, repogroupid, usergroupid, perm,
2199 self, repogroupid, usergroupid, perm,
2198 apply_to_children=Optional('none')):
2200 apply_to_children=Optional('none')):
2199 """
2201 """
2200 Grant permission for user group on given repository group, or update
2202 Grant permission for user group on given repository group, or update
2201 existing one if found. This command can be executed only using
2203 existing one if found. This command can be executed only using
2202 api_key belonging to user with admin rights, or user who has admin
2204 api_key belonging to user with admin rights, or user who has admin
2203 right to given repository group.
2205 right to given repository group.
2204
2206
2205 :param repogroupid: name or id of repository group
2207 :param repogroupid: name or id of repository group
2206 :type repogroupid: str or int
2208 :type repogroupid: str or int
2207 :param usergroupid: id of usergroup
2209 :param usergroupid: id of usergroup
2208 :type usergroupid: str or int
2210 :type usergroupid: str or int
2209 :param perm: (group.(none|read|write|admin))
2211 :param perm: (group.(none|read|write|admin))
2210 :type perm: str
2212 :type perm: str
2211 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2213 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2212 :type apply_to_children: str
2214 :type apply_to_children: str
2213
2215
2214 OUTPUT::
2216 OUTPUT::
2215
2217
2216 id : <id_given_in_input>
2218 id : <id_given_in_input>
2217 result : {
2219 result : {
2218 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2220 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2219 "success": true
2221 "success": true
2220
2222
2221 }
2223 }
2222 error : null
2224 error : null
2223
2225
2224 ERROR OUTPUT::
2226 ERROR OUTPUT::
2225
2227
2226 id : <id_given_in_input>
2228 id : <id_given_in_input>
2227 result : null
2229 result : null
2228 error : {
2230 error : {
2229 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2231 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2230 }
2232 }
2231
2233
2232 """
2234 """
2233 repo_group = get_repo_group_or_error(repogroupid)
2235 repo_group = get_repo_group_or_error(repogroupid)
2234 perm = get_perm_or_error(perm, prefix='group.')
2236 perm = get_perm_or_error(perm, prefix='group.')
2235 user_group = get_user_group_or_error(usergroupid)
2237 user_group = get_user_group_or_error(usergroupid)
2236 if not HasPermissionAny('hg.admin')():
2238 if not HasPermissionAny('hg.admin')():
2237 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2239 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2238 raise JSONRPCError(
2240 raise JSONRPCError(
2239 'repository group `%s` does not exist' % (repogroupid,))
2241 'repository group `%s` does not exist' % (repogroupid,))
2240
2242
2241 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2243 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2242 raise JSONRPCError(
2244 raise JSONRPCError(
2243 'user group `%s` does not exist' % (usergroupid,))
2245 'user group `%s` does not exist' % (usergroupid,))
2244
2246
2245 apply_to_children = Optional.extract(apply_to_children)
2247 apply_to_children = Optional.extract(apply_to_children)
2246
2248
2247 try:
2249 try:
2248 RepoGroupModel().add_permission(repo_group=repo_group,
2250 RepoGroupModel().add_permission(repo_group=repo_group,
2249 obj=user_group,
2251 obj=user_group,
2250 obj_type="user_group",
2252 obj_type="user_group",
2251 perm=perm,
2253 perm=perm,
2252 recursive=apply_to_children)
2254 recursive=apply_to_children)
2253 Session().commit()
2255 Session().commit()
2254 return dict(
2256 return dict(
2255 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2257 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2256 perm.permission_name, apply_to_children,
2258 perm.permission_name, apply_to_children,
2257 user_group.users_group_name, repo_group.name
2259 user_group.users_group_name, repo_group.name
2258 ),
2260 ),
2259 success=True
2261 success=True
2260 )
2262 )
2261 except Exception:
2263 except Exception:
2262 log.error(traceback.format_exc())
2264 log.error(traceback.format_exc())
2263 raise JSONRPCError(
2265 raise JSONRPCError(
2264 'failed to edit permission for user group: `%s` in '
2266 'failed to edit permission for user group: `%s` in '
2265 'repo group: `%s`' % (
2267 'repo group: `%s`' % (
2266 usergroupid, repo_group.name
2268 usergroupid, repo_group.name
2267 )
2269 )
2268 )
2270 )
2269
2271
2270 # permission check inside
2272 # permission check inside
2271 def revoke_user_group_permission_from_repo_group(
2273 def revoke_user_group_permission_from_repo_group(
2272 self, repogroupid, usergroupid,
2274 self, repogroupid, usergroupid,
2273 apply_to_children=Optional('none')):
2275 apply_to_children=Optional('none')):
2274 """
2276 """
2275 Revoke permission for user group on given repository. This command can be
2277 Revoke permission for user group on given repository. This command can be
2276 executed only using api_key belonging to user with admin rights, or
2278 executed only using api_key belonging to user with admin rights, or
2277 user who has admin right to given repository group.
2279 user who has admin right to given repository group.
2278
2280
2279 :param repogroupid: name or id of repository group
2281 :param repogroupid: name or id of repository group
2280 :type repogroupid: str or int
2282 :type repogroupid: str or int
2281 :param usergroupid:
2283 :param usergroupid:
2282 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2284 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2283 :type apply_to_children: str
2285 :type apply_to_children: str
2284
2286
2285 OUTPUT::
2287 OUTPUT::
2286
2288
2287 id : <id_given_in_input>
2289 id : <id_given_in_input>
2288 result: {
2290 result: {
2289 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2291 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2290 "success": true
2292 "success": true
2291 }
2293 }
2292 error: null
2294 error: null
2293
2295
2294 ERROR OUTPUT::
2296 ERROR OUTPUT::
2295
2297
2296 id : <id_given_in_input>
2298 id : <id_given_in_input>
2297 result : null
2299 result : null
2298 error : {
2300 error : {
2299 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2301 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2300 }
2302 }
2301
2303
2302
2304
2303 """
2305 """
2304 repo_group = get_repo_group_or_error(repogroupid)
2306 repo_group = get_repo_group_or_error(repogroupid)
2305 user_group = get_user_group_or_error(usergroupid)
2307 user_group = get_user_group_or_error(usergroupid)
2306 if not HasPermissionAny('hg.admin')():
2308 if not HasPermissionAny('hg.admin')():
2307 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2309 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2308 raise JSONRPCError(
2310 raise JSONRPCError(
2309 'repository group `%s` does not exist' % (repogroupid,))
2311 'repository group `%s` does not exist' % (repogroupid,))
2310
2312
2311 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2313 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2312 raise JSONRPCError(
2314 raise JSONRPCError(
2313 'user group `%s` does not exist' % (usergroupid,))
2315 'user group `%s` does not exist' % (usergroupid,))
2314
2316
2315 apply_to_children = Optional.extract(apply_to_children)
2317 apply_to_children = Optional.extract(apply_to_children)
2316
2318
2317 try:
2319 try:
2318 RepoGroupModel().delete_permission(repo_group=repo_group,
2320 RepoGroupModel().delete_permission(repo_group=repo_group,
2319 obj=user_group,
2321 obj=user_group,
2320 obj_type="user_group",
2322 obj_type="user_group",
2321 recursive=apply_to_children)
2323 recursive=apply_to_children)
2322 Session().commit()
2324 Session().commit()
2323 return dict(
2325 return dict(
2324 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2326 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2325 apply_to_children, user_group.users_group_name, repo_group.name
2327 apply_to_children, user_group.users_group_name, repo_group.name
2326 ),
2328 ),
2327 success=True
2329 success=True
2328 )
2330 )
2329 except Exception:
2331 except Exception:
2330 log.error(traceback.format_exc())
2332 log.error(traceback.format_exc())
2331 raise JSONRPCError(
2333 raise JSONRPCError(
2332 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2334 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2333 user_group.users_group_name, repo_group.name
2335 user_group.users_group_name, repo_group.name
2334 )
2336 )
2335 )
2337 )
2336
2338
2337 def get_gist(self, gistid):
2339 def get_gist(self, gistid):
2338 """
2340 """
2339 Get given gist by id
2341 Get given gist by id
2340
2342
2341 :param gistid: id of private or public gist
2343 :param gistid: id of private or public gist
2342 :type gistid: str
2344 :type gistid: str
2343 """
2345 """
2344 gist = get_gist_or_error(gistid)
2346 gist = get_gist_or_error(gistid)
2345 if not HasPermissionAny('hg.admin')():
2347 if not HasPermissionAny('hg.admin')():
2346 if gist.owner_id != request.authuser.user_id:
2348 if gist.owner_id != request.authuser.user_id:
2347 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2349 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2348 return gist.get_api_data()
2350 return gist.get_api_data()
2349
2351
2350 def get_gists(self, userid=Optional(OAttr('apiuser'))):
2352 def get_gists(self, userid=Optional(OAttr('apiuser'))):
2351 """
2353 """
2352 Get all gists for given user. If userid is empty returned gists
2354 Get all gists for given user. If userid is empty returned gists
2353 are for user who called the api
2355 are for user who called the api
2354
2356
2355 :param userid: user to get gists for
2357 :param userid: user to get gists for
2356 :type userid: Optional(str or int)
2358 :type userid: Optional(str or int)
2357 """
2359 """
2358 if not HasPermissionAny('hg.admin')():
2360 if not HasPermissionAny('hg.admin')():
2359 # make sure normal user does not pass someone else userid,
2361 # make sure normal user does not pass someone else userid,
2360 # he is not allowed to do that
2362 # he is not allowed to do that
2361 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
2363 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
2362 raise JSONRPCError(
2364 raise JSONRPCError(
2363 'userid is not the same as your user'
2365 'userid is not the same as your user'
2364 )
2366 )
2365
2367
2366 if isinstance(userid, Optional):
2368 if isinstance(userid, Optional):
2367 user_id = request.authuser.user_id
2369 user_id = request.authuser.user_id
2368 else:
2370 else:
2369 user_id = get_user_or_error(userid).user_id
2371 user_id = get_user_or_error(userid).user_id
2370
2372
2371 return [
2373 return [
2372 gist.get_api_data()
2374 gist.get_api_data()
2373 for gist in Gist().query()
2375 for gist in Gist().query()
2374 .filter_by(is_expired=False)
2376 .filter_by(is_expired=False)
2375 .filter(Gist.owner_id == user_id)
2377 .filter(Gist.owner_id == user_id)
2376 .order_by(Gist.created_on.desc())
2378 .order_by(Gist.created_on.desc())
2377 ]
2379 ]
2378
2380
2379 def create_gist(self, files, owner=Optional(OAttr('apiuser')),
2381 def create_gist(self, files, owner=Optional(OAttr('apiuser')),
2380 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
2382 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
2381 description=Optional('')):
2383 description=Optional('')):
2382
2384
2383 """
2385 """
2384 Creates new Gist
2386 Creates new Gist
2385
2387
2386 :param files: files to be added to gist
2388 :param files: files to be added to gist
2387 {'filename': {'content':'...', 'lexer': null},
2389 {'filename': {'content':'...', 'lexer': null},
2388 'filename2': {'content':'...', 'lexer': null}}
2390 'filename2': {'content':'...', 'lexer': null}}
2389 :type files: dict
2391 :type files: dict
2390 :param owner: gist owner, defaults to api method caller
2392 :param owner: gist owner, defaults to api method caller
2391 :type owner: Optional(str or int)
2393 :type owner: Optional(str or int)
2392 :param gist_type: type of gist 'public' or 'private'
2394 :param gist_type: type of gist 'public' or 'private'
2393 :type gist_type: Optional(str)
2395 :type gist_type: Optional(str)
2394 :param lifetime: time in minutes of gist lifetime
2396 :param lifetime: time in minutes of gist lifetime
2395 :type lifetime: Optional(int)
2397 :type lifetime: Optional(int)
2396 :param description: gist description
2398 :param description: gist description
2397 :type description: Optional(str)
2399 :type description: Optional(str)
2398
2400
2399 OUTPUT::
2401 OUTPUT::
2400
2402
2401 id : <id_given_in_input>
2403 id : <id_given_in_input>
2402 result : {
2404 result : {
2403 "msg": "created new gist",
2405 "msg": "created new gist",
2404 "gist": {}
2406 "gist": {}
2405 }
2407 }
2406 error : null
2408 error : null
2407
2409
2408 ERROR OUTPUT::
2410 ERROR OUTPUT::
2409
2411
2410 id : <id_given_in_input>
2412 id : <id_given_in_input>
2411 result : null
2413 result : null
2412 error : {
2414 error : {
2413 "failed to create gist"
2415 "failed to create gist"
2414 }
2416 }
2415
2417
2416 """
2418 """
2417 try:
2419 try:
2418 if isinstance(owner, Optional):
2420 if isinstance(owner, Optional):
2419 owner = request.authuser.user_id
2421 owner = request.authuser.user_id
2420
2422
2421 owner = get_user_or_error(owner)
2423 owner = get_user_or_error(owner)
2422 description = Optional.extract(description)
2424 description = Optional.extract(description)
2423 gist_type = Optional.extract(gist_type)
2425 gist_type = Optional.extract(gist_type)
2424 lifetime = Optional.extract(lifetime)
2426 lifetime = Optional.extract(lifetime)
2425
2427
2426 gist = GistModel().create(description=description,
2428 gist = GistModel().create(description=description,
2427 owner=owner,
2429 owner=owner,
2428 gist_mapping=files,
2430 gist_mapping=files,
2429 gist_type=gist_type,
2431 gist_type=gist_type,
2430 lifetime=lifetime)
2432 lifetime=lifetime)
2431 Session().commit()
2433 Session().commit()
2432 return dict(
2434 return dict(
2433 msg='created new gist',
2435 msg='created new gist',
2434 gist=gist.get_api_data()
2436 gist=gist.get_api_data()
2435 )
2437 )
2436 except Exception:
2438 except Exception:
2437 log.error(traceback.format_exc())
2439 log.error(traceback.format_exc())
2438 raise JSONRPCError('failed to create gist')
2440 raise JSONRPCError('failed to create gist')
2439
2441
2440 # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')),
2442 # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')),
2441 # gist_type=Optional(Gist.GIST_PUBLIC),
2443 # gist_type=Optional(Gist.GIST_PUBLIC),
2442 # gist_lifetime=Optional(-1), gist_description=Optional('')):
2444 # gist_lifetime=Optional(-1), gist_description=Optional('')):
2443 # gist = get_gist_or_error(gistid)
2445 # gist = get_gist_or_error(gistid)
2444 # updates = {}
2446 # updates = {}
2445
2447
2446 # permission check inside
2448 # permission check inside
2447 def delete_gist(self, gistid):
2449 def delete_gist(self, gistid):
2448 """
2450 """
2449 Deletes existing gist
2451 Deletes existing gist
2450
2452
2451 :param gistid: id of gist to delete
2453 :param gistid: id of gist to delete
2452 :type gistid: str
2454 :type gistid: str
2453
2455
2454 OUTPUT::
2456 OUTPUT::
2455
2457
2456 id : <id_given_in_input>
2458 id : <id_given_in_input>
2457 result : {
2459 result : {
2458 "deleted gist ID: <gist_id>",
2460 "deleted gist ID: <gist_id>",
2459 "gist": null
2461 "gist": null
2460 }
2462 }
2461 error : null
2463 error : null
2462
2464
2463 ERROR OUTPUT::
2465 ERROR OUTPUT::
2464
2466
2465 id : <id_given_in_input>
2467 id : <id_given_in_input>
2466 result : null
2468 result : null
2467 error : {
2469 error : {
2468 "failed to delete gist ID:<gist_id>"
2470 "failed to delete gist ID:<gist_id>"
2469 }
2471 }
2470
2472
2471 """
2473 """
2472 gist = get_gist_or_error(gistid)
2474 gist = get_gist_or_error(gistid)
2473 if not HasPermissionAny('hg.admin')():
2475 if not HasPermissionAny('hg.admin')():
2474 if gist.owner_id != request.authuser.user_id:
2476 if gist.owner_id != request.authuser.user_id:
2475 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2477 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2476
2478
2477 try:
2479 try:
2478 GistModel().delete(gist)
2480 GistModel().delete(gist)
2479 Session().commit()
2481 Session().commit()
2480 return dict(
2482 return dict(
2481 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2483 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2482 gist=None
2484 gist=None
2483 )
2485 )
2484 except Exception:
2486 except Exception:
2485 log.error(traceback.format_exc())
2487 log.error(traceback.format_exc())
2486 raise JSONRPCError('failed to delete gist ID:%s'
2488 raise JSONRPCError('failed to delete gist ID:%s'
2487 % (gist.gist_access_id,))
2489 % (gist.gist_access_id,))
2488
2490
2489 # permission check inside
2491 # permission check inside
2490 def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
2492 def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
2491 repo = get_repo_or_error(repoid)
2493 repo = get_repo_or_error(repoid)
2492 if not HasRepoPermissionLevel('read')(repo.repo_name):
2494 if not HasRepoPermissionLevel('read')(repo.repo_name):
2493 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2495 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2494 changeset = repo.get_changeset(raw_id)
2496 changeset = repo.get_changeset(raw_id)
2495 if isinstance(changeset, EmptyChangeset):
2497 if isinstance(changeset, EmptyChangeset):
2496 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2498 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2497
2499
2498 info = dict(changeset.as_dict())
2500 info = dict(changeset.as_dict())
2499
2501
2500 with_reviews = Optional.extract(with_reviews)
2502 with_reviews = Optional.extract(with_reviews)
2501 if with_reviews:
2503 if with_reviews:
2502 reviews = ChangesetStatusModel().get_statuses(
2504 reviews = ChangesetStatusModel().get_statuses(
2503 repo.repo_name, raw_id)
2505 repo.repo_name, raw_id)
2504 info["reviews"] = reviews
2506 info["reviews"] = reviews
2505
2507
2506 return info
2508 return info
@@ -1,2622 +1,2625 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.db
15 kallithea.model.db
16 ~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~
17
17
18 Database Models for Kallithea
18 Database Models for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 08, 2010
22 :created_on: Apr 08, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import os
28 import os
29 import time
29 import time
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import traceback
32 import traceback
33 import hashlib
33 import hashlib
34 import collections
34 import collections
35 import functools
35 import functools
36
36
37 import sqlalchemy
37 import sqlalchemy
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.hybrid import hybrid_property
39 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
40 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
41 from beaker.cache import cache_region, region_invalidate
41 from beaker.cache import cache_region, region_invalidate
42 from webob.exc import HTTPNotFound
42 from webob.exc import HTTPNotFound
43
43
44 from tg.i18n import lazy_ugettext as _
44 from tg.i18n import lazy_ugettext as _
45
45
46 from kallithea.lib.exceptions import DefaultUserException
46 from kallithea.lib.exceptions import DefaultUserException
47 from kallithea.lib.vcs import get_backend
47 from kallithea.lib.vcs import get_backend
48 from kallithea.lib.vcs.utils.helpers import get_scm
48 from kallithea.lib.vcs.utils.helpers import get_scm
49 from kallithea.lib.vcs.utils.lazy import LazyProperty
49 from kallithea.lib.vcs.utils.lazy import LazyProperty
50 from kallithea.lib.vcs.backends.base import EmptyChangeset
50 from kallithea.lib.vcs.backends.base import EmptyChangeset
51
51
52 from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
52 from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
53 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
53 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
54 get_clone_url, urlreadable
54 get_clone_url, urlreadable
55 from kallithea.lib.compat import json
55 from kallithea.lib.compat import json
56 from kallithea.lib.caching_query import FromCache
56 from kallithea.lib.caching_query import FromCache
57
57
58 from kallithea.model.meta import Base, Session
58 from kallithea.model.meta import Base, Session
59
59
60 URL_SEP = '/'
60 URL_SEP = '/'
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 #==============================================================================
63 #==============================================================================
64 # BASE CLASSES
64 # BASE CLASSES
65 #==============================================================================
65 #==============================================================================
66
66
67 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
67 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
68
68
69
69
70 class BaseDbModel(object):
70 class BaseDbModel(object):
71 """
71 """
72 Base Model for all classes
72 Base Model for all classes
73 """
73 """
74
74
75 @classmethod
75 @classmethod
76 def _get_keys(cls):
76 def _get_keys(cls):
77 """return column names for this model """
77 """return column names for this model """
78 return class_mapper(cls).c.keys()
78 return class_mapper(cls).c.keys()
79
79
80 def get_dict(self):
80 def get_dict(self):
81 """
81 """
82 return dict with keys and values corresponding
82 return dict with keys and values corresponding
83 to this model data """
83 to this model data """
84
84
85 d = {}
85 d = {}
86 for k in self._get_keys():
86 for k in self._get_keys():
87 d[k] = getattr(self, k)
87 d[k] = getattr(self, k)
88
88
89 # also use __json__() if present to get additional fields
89 # also use __json__() if present to get additional fields
90 _json_attr = getattr(self, '__json__', None)
90 _json_attr = getattr(self, '__json__', None)
91 if _json_attr:
91 if _json_attr:
92 # update with attributes from __json__
92 # update with attributes from __json__
93 if callable(_json_attr):
93 if callable(_json_attr):
94 _json_attr = _json_attr()
94 _json_attr = _json_attr()
95 for k, val in _json_attr.iteritems():
95 for k, val in _json_attr.iteritems():
96 d[k] = val
96 d[k] = val
97 return d
97 return d
98
98
99 def get_appstruct(self):
99 def get_appstruct(self):
100 """return list with keys and values tuples corresponding
100 """return list with keys and values tuples corresponding
101 to this model data """
101 to this model data """
102
102
103 return [
103 return [
104 (k, getattr(self, k))
104 (k, getattr(self, k))
105 for k in self._get_keys()
105 for k in self._get_keys()
106 ]
106 ]
107
107
108 def populate_obj(self, populate_dict):
108 def populate_obj(self, populate_dict):
109 """populate model with data from given populate_dict"""
109 """populate model with data from given populate_dict"""
110
110
111 for k in self._get_keys():
111 for k in self._get_keys():
112 if k in populate_dict:
112 if k in populate_dict:
113 setattr(self, k, populate_dict[k])
113 setattr(self, k, populate_dict[k])
114
114
115 @classmethod
115 @classmethod
116 def query(cls):
116 def query(cls):
117 return Session().query(cls)
117 return Session().query(cls)
118
118
119 @classmethod
119 @classmethod
120 def get(cls, id_):
120 def get(cls, id_):
121 if id_:
121 if id_:
122 return cls.query().get(id_)
122 return cls.query().get(id_)
123
123
124 @classmethod
124 @classmethod
125 def guess_instance(cls, value, callback=None):
125 def guess_instance(cls, value, callback=None):
126 """Haphazardly attempt to convert `value` to a `cls` instance.
126 """Haphazardly attempt to convert `value` to a `cls` instance.
127
127
128 If `value` is None or already a `cls` instance, return it. If `value`
128 If `value` is None or already a `cls` instance, return it. If `value`
129 is a number (or looks like one if you squint just right), assume it's
129 is a number (or looks like one if you squint just right), assume it's
130 a database primary key and let SQLAlchemy sort things out. Otherwise,
130 a database primary key and let SQLAlchemy sort things out. Otherwise,
131 fall back to resolving it using `callback` (if specified); this could
131 fall back to resolving it using `callback` (if specified); this could
132 e.g. be a function that looks up instances by name (though that won't
132 e.g. be a function that looks up instances by name (though that won't
133 work if the name begins with a digit). Otherwise, raise Exception.
133 work if the name begins with a digit). Otherwise, raise Exception.
134 """
134 """
135
135
136 if value is None:
136 if value is None:
137 return None
137 return None
138 if isinstance(value, cls):
138 if isinstance(value, cls):
139 return value
139 return value
140 if isinstance(value, (int, long)) or safe_str(value).isdigit():
140 if isinstance(value, (int, long)) or safe_str(value).isdigit():
141 return cls.get(value)
141 return cls.get(value)
142 if callback is not None:
142 if callback is not None:
143 return callback(value)
143 return callback(value)
144
144
145 raise Exception(
145 raise Exception(
146 'given object must be int, long or Instance of %s '
146 'given object must be int, long or Instance of %s '
147 'got %s, no callback provided' % (cls, type(value))
147 'got %s, no callback provided' % (cls, type(value))
148 )
148 )
149
149
150 @classmethod
150 @classmethod
151 def get_or_404(cls, id_):
151 def get_or_404(cls, id_):
152 try:
152 try:
153 id_ = int(id_)
153 id_ = int(id_)
154 except (TypeError, ValueError):
154 except (TypeError, ValueError):
155 raise HTTPNotFound
155 raise HTTPNotFound
156
156
157 res = cls.query().get(id_)
157 res = cls.query().get(id_)
158 if res is None:
158 if res is None:
159 raise HTTPNotFound
159 raise HTTPNotFound
160 return res
160 return res
161
161
162 @classmethod
162 @classmethod
163 def delete(cls, id_):
163 def delete(cls, id_):
164 obj = cls.query().get(id_)
164 obj = cls.query().get(id_)
165 Session().delete(obj)
165 Session().delete(obj)
166
166
167 def __repr__(self):
167 def __repr__(self):
168 if hasattr(self, '__unicode__'):
168 if hasattr(self, '__unicode__'):
169 # python repr needs to return str
169 # python repr needs to return str
170 try:
170 try:
171 return safe_str(self.__unicode__())
171 return safe_str(self.__unicode__())
172 except UnicodeDecodeError:
172 except UnicodeDecodeError:
173 pass
173 pass
174 return '<DB:%s>' % (self.__class__.__name__)
174 return '<DB:%s>' % (self.__class__.__name__)
175
175
176
176
177 _table_args_default_dict = {'extend_existing': True,
177 _table_args_default_dict = {'extend_existing': True,
178 'mysql_engine': 'InnoDB',
178 'mysql_engine': 'InnoDB',
179 'mysql_charset': 'utf8',
179 'mysql_charset': 'utf8',
180 'sqlite_autoincrement': True,
180 'sqlite_autoincrement': True,
181 }
181 }
182
182
183 class Setting(Base, BaseDbModel):
183 class Setting(Base, BaseDbModel):
184 __tablename__ = 'settings'
184 __tablename__ = 'settings'
185 __table_args__ = (
185 __table_args__ = (
186 _table_args_default_dict,
186 _table_args_default_dict,
187 )
187 )
188
188
189 SETTINGS_TYPES = {
189 SETTINGS_TYPES = {
190 'str': safe_str,
190 'str': safe_str,
191 'int': safe_int,
191 'int': safe_int,
192 'unicode': safe_unicode,
192 'unicode': safe_unicode,
193 'bool': str2bool,
193 'bool': str2bool,
194 'list': functools.partial(aslist, sep=',')
194 'list': functools.partial(aslist, sep=',')
195 }
195 }
196 DEFAULT_UPDATE_URL = ''
196 DEFAULT_UPDATE_URL = ''
197
197
198 app_settings_id = Column(Integer(), primary_key=True)
198 app_settings_id = Column(Integer(), primary_key=True)
199 app_settings_name = Column(String(255), nullable=False, unique=True)
199 app_settings_name = Column(String(255), nullable=False, unique=True)
200 _app_settings_value = Column("app_settings_value", Unicode(4096), nullable=False)
200 _app_settings_value = Column("app_settings_value", Unicode(4096), nullable=False)
201 _app_settings_type = Column("app_settings_type", String(255), nullable=True) # FIXME: not nullable?
201 _app_settings_type = Column("app_settings_type", String(255), nullable=True) # FIXME: not nullable?
202
202
203 def __init__(self, key='', val='', type='unicode'):
203 def __init__(self, key='', val='', type='unicode'):
204 self.app_settings_name = key
204 self.app_settings_name = key
205 self.app_settings_value = val
205 self.app_settings_value = val
206 self.app_settings_type = type
206 self.app_settings_type = type
207
207
208 @validates('_app_settings_value')
208 @validates('_app_settings_value')
209 def validate_settings_value(self, key, val):
209 def validate_settings_value(self, key, val):
210 assert type(val) == unicode
210 assert type(val) == unicode
211 return val
211 return val
212
212
213 @hybrid_property
213 @hybrid_property
214 def app_settings_value(self):
214 def app_settings_value(self):
215 v = self._app_settings_value
215 v = self._app_settings_value
216 _type = self.app_settings_type
216 _type = self.app_settings_type
217 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
217 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
218 return converter(v)
218 return converter(v)
219
219
220 @app_settings_value.setter
220 @app_settings_value.setter
221 def app_settings_value(self, val):
221 def app_settings_value(self, val):
222 """
222 """
223 Setter that will always make sure we use unicode in app_settings_value
223 Setter that will always make sure we use unicode in app_settings_value
224
224
225 :param val:
225 :param val:
226 """
226 """
227 self._app_settings_value = safe_unicode(val)
227 self._app_settings_value = safe_unicode(val)
228
228
229 @hybrid_property
229 @hybrid_property
230 def app_settings_type(self):
230 def app_settings_type(self):
231 return self._app_settings_type
231 return self._app_settings_type
232
232
233 @app_settings_type.setter
233 @app_settings_type.setter
234 def app_settings_type(self, val):
234 def app_settings_type(self, val):
235 if val not in self.SETTINGS_TYPES:
235 if val not in self.SETTINGS_TYPES:
236 raise Exception('type must be one of %s got %s'
236 raise Exception('type must be one of %s got %s'
237 % (self.SETTINGS_TYPES.keys(), val))
237 % (self.SETTINGS_TYPES.keys(), val))
238 self._app_settings_type = val
238 self._app_settings_type = val
239
239
240 def __unicode__(self):
240 def __unicode__(self):
241 return u"<%s('%s:%s[%s]')>" % (
241 return u"<%s('%s:%s[%s]')>" % (
242 self.__class__.__name__,
242 self.__class__.__name__,
243 self.app_settings_name, self.app_settings_value, self.app_settings_type
243 self.app_settings_name, self.app_settings_value, self.app_settings_type
244 )
244 )
245
245
246 @classmethod
246 @classmethod
247 def get_by_name(cls, key):
247 def get_by_name(cls, key):
248 return cls.query() \
248 return cls.query() \
249 .filter(cls.app_settings_name == key).scalar()
249 .filter(cls.app_settings_name == key).scalar()
250
250
251 @classmethod
251 @classmethod
252 def get_by_name_or_create(cls, key, val='', type='unicode'):
252 def get_by_name_or_create(cls, key, val='', type='unicode'):
253 res = cls.get_by_name(key)
253 res = cls.get_by_name(key)
254 if res is None:
254 if res is None:
255 res = cls(key, val, type)
255 res = cls(key, val, type)
256 return res
256 return res
257
257
258 @classmethod
258 @classmethod
259 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
259 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
260 """
260 """
261 Creates or updates Kallithea setting. If updates are triggered, it will only
261 Creates or updates Kallithea setting. If updates are triggered, it will only
262 update parameters that are explicitly set. Optional instance will be skipped.
262 update parameters that are explicitly set. Optional instance will be skipped.
263
263
264 :param key:
264 :param key:
265 :param val:
265 :param val:
266 :param type:
266 :param type:
267 :return:
267 :return:
268 """
268 """
269 res = cls.get_by_name(key)
269 res = cls.get_by_name(key)
270 if res is None:
270 if res is None:
271 val = Optional.extract(val)
271 val = Optional.extract(val)
272 type = Optional.extract(type)
272 type = Optional.extract(type)
273 res = cls(key, val, type)
273 res = cls(key, val, type)
274 Session().add(res)
274 Session().add(res)
275 else:
275 else:
276 res.app_settings_name = key
276 res.app_settings_name = key
277 if not isinstance(val, Optional):
277 if not isinstance(val, Optional):
278 # update if set
278 # update if set
279 res.app_settings_value = val
279 res.app_settings_value = val
280 if not isinstance(type, Optional):
280 if not isinstance(type, Optional):
281 # update if set
281 # update if set
282 res.app_settings_type = type
282 res.app_settings_type = type
283 return res
283 return res
284
284
285 @classmethod
285 @classmethod
286 def get_app_settings(cls, cache=False):
286 def get_app_settings(cls, cache=False):
287
287
288 ret = cls.query()
288 ret = cls.query()
289
289
290 if cache:
290 if cache:
291 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
291 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
292
292
293 if ret is None:
293 if ret is None:
294 raise Exception('Could not get application settings !')
294 raise Exception('Could not get application settings !')
295 settings = {}
295 settings = {}
296 for each in ret:
296 for each in ret:
297 settings[each.app_settings_name] = \
297 settings[each.app_settings_name] = \
298 each.app_settings_value
298 each.app_settings_value
299
299
300 return settings
300 return settings
301
301
302 @classmethod
302 @classmethod
303 def get_auth_settings(cls, cache=False):
303 def get_auth_settings(cls, cache=False):
304 ret = cls.query() \
304 ret = cls.query() \
305 .filter(cls.app_settings_name.startswith('auth_')).all()
305 .filter(cls.app_settings_name.startswith('auth_')).all()
306 fd = {}
306 fd = {}
307 for row in ret:
307 for row in ret:
308 fd[row.app_settings_name] = row.app_settings_value
308 fd[row.app_settings_name] = row.app_settings_value
309 return fd
309 return fd
310
310
311 @classmethod
311 @classmethod
312 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
312 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
313 ret = cls.query() \
313 ret = cls.query() \
314 .filter(cls.app_settings_name.startswith('default_')).all()
314 .filter(cls.app_settings_name.startswith('default_')).all()
315 fd = {}
315 fd = {}
316 for row in ret:
316 for row in ret:
317 key = row.app_settings_name
317 key = row.app_settings_name
318 if strip_prefix:
318 if strip_prefix:
319 key = remove_prefix(key, prefix='default_')
319 key = remove_prefix(key, prefix='default_')
320 fd.update({key: row.app_settings_value})
320 fd.update({key: row.app_settings_value})
321
321
322 return fd
322 return fd
323
323
324 @classmethod
324 @classmethod
325 def get_server_info(cls):
325 def get_server_info(cls):
326 import pkg_resources
326 import pkg_resources
327 import platform
327 import platform
328 import kallithea
328 import kallithea
329 from kallithea.lib.utils import check_git_version
329 from kallithea.lib.utils import check_git_version
330 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
330 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
331 info = {
331 info = {
332 'modules': sorted(mods, key=lambda k: k[0].lower()),
332 'modules': sorted(mods, key=lambda k: k[0].lower()),
333 'py_version': platform.python_version(),
333 'py_version': platform.python_version(),
334 'platform': safe_unicode(platform.platform()),
334 'platform': safe_unicode(platform.platform()),
335 'kallithea_version': kallithea.__version__,
335 'kallithea_version': kallithea.__version__,
336 'git_version': safe_unicode(check_git_version()),
336 'git_version': safe_unicode(check_git_version()),
337 'git_path': kallithea.CONFIG.get('git_path')
337 'git_path': kallithea.CONFIG.get('git_path')
338 }
338 }
339 return info
339 return info
340
340
341
341
342 class Ui(Base, BaseDbModel):
342 class Ui(Base, BaseDbModel):
343 __tablename__ = 'ui'
343 __tablename__ = 'ui'
344 __table_args__ = (
344 __table_args__ = (
345 # FIXME: ui_key as key is wrong and should be removed when the corresponding
345 # FIXME: ui_key as key is wrong and should be removed when the corresponding
346 # Ui.get_by_key has been replaced by the composite key
346 # Ui.get_by_key has been replaced by the composite key
347 UniqueConstraint('ui_key'),
347 UniqueConstraint('ui_key'),
348 UniqueConstraint('ui_section', 'ui_key'),
348 UniqueConstraint('ui_section', 'ui_key'),
349 _table_args_default_dict,
349 _table_args_default_dict,
350 )
350 )
351
351
352 HOOK_UPDATE = 'changegroup.update'
352 HOOK_UPDATE = 'changegroup.update'
353 HOOK_REPO_SIZE = 'changegroup.repo_size'
353 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_PUSH = 'changegroup.push_logger'
354 HOOK_PUSH = 'changegroup.push_logger'
355 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
355 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
356 HOOK_PULL = 'outgoing.pull_logger'
356 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
358
358
359 ui_id = Column(Integer(), primary_key=True)
359 ui_id = Column(Integer(), primary_key=True)
360 ui_section = Column(String(255), nullable=False)
360 ui_section = Column(String(255), nullable=False)
361 ui_key = Column(String(255), nullable=False)
361 ui_key = Column(String(255), nullable=False)
362 ui_value = Column(String(255), nullable=True) # FIXME: not nullable?
362 ui_value = Column(String(255), nullable=True) # FIXME: not nullable?
363 ui_active = Column(Boolean(), nullable=False, default=True)
363 ui_active = Column(Boolean(), nullable=False, default=True)
364
364
365 @classmethod
365 @classmethod
366 def get_by_key(cls, section, key):
366 def get_by_key(cls, section, key):
367 """ Return specified Ui object, or None if not found. """
367 """ Return specified Ui object, or None if not found. """
368 return cls.query().filter_by(ui_section=section, ui_key=key).scalar()
368 return cls.query().filter_by(ui_section=section, ui_key=key).scalar()
369
369
370 @classmethod
370 @classmethod
371 def get_or_create(cls, section, key):
371 def get_or_create(cls, section, key):
372 """ Return specified Ui object, creating it if necessary. """
372 """ Return specified Ui object, creating it if necessary. """
373 setting = cls.get_by_key(section, key)
373 setting = cls.get_by_key(section, key)
374 if setting is None:
374 if setting is None:
375 setting = cls(ui_section=section, ui_key=key)
375 setting = cls(ui_section=section, ui_key=key)
376 Session().add(setting)
376 Session().add(setting)
377 return setting
377 return setting
378
378
379 @classmethod
379 @classmethod
380 def get_builtin_hooks(cls):
380 def get_builtin_hooks(cls):
381 q = cls.query()
381 q = cls.query()
382 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
382 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
383 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
383 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
384 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
384 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
385 q = q.filter(cls.ui_section == 'hooks')
385 q = q.filter(cls.ui_section == 'hooks')
386 return q.all()
386 return q.all()
387
387
388 @classmethod
388 @classmethod
389 def get_custom_hooks(cls):
389 def get_custom_hooks(cls):
390 q = cls.query()
390 q = cls.query()
391 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
391 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
392 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
392 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
393 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
393 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
394 q = q.filter(cls.ui_section == 'hooks')
394 q = q.filter(cls.ui_section == 'hooks')
395 return q.all()
395 return q.all()
396
396
397 @classmethod
397 @classmethod
398 def get_repos_location(cls):
398 def get_repos_location(cls):
399 return cls.get_by_key('paths', '/').ui_value
399 return cls.get_by_key('paths', '/').ui_value
400
400
401 @classmethod
401 @classmethod
402 def create_or_update_hook(cls, key, val):
402 def create_or_update_hook(cls, key, val):
403 new_ui = cls.get_or_create('hooks', key)
403 new_ui = cls.get_or_create('hooks', key)
404 new_ui.ui_active = True
404 new_ui.ui_active = True
405 new_ui.ui_value = val
405 new_ui.ui_value = val
406
406
407 def __repr__(self):
407 def __repr__(self):
408 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
408 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
409 self.ui_key, self.ui_value)
409 self.ui_key, self.ui_value)
410
410
411
411
412 class User(Base, BaseDbModel):
412 class User(Base, BaseDbModel):
413 __tablename__ = 'users'
413 __tablename__ = 'users'
414 __table_args__ = (
414 __table_args__ = (
415 Index('u_username_idx', 'username'),
415 Index('u_username_idx', 'username'),
416 Index('u_email_idx', 'email'),
416 Index('u_email_idx', 'email'),
417 _table_args_default_dict,
417 _table_args_default_dict,
418 )
418 )
419
419
420 DEFAULT_USER = 'default'
420 DEFAULT_USER = 'default'
421 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
421 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
422 # The name of the default auth type in extern_type, 'internal' lives in auth_internal.py
422 # The name of the default auth type in extern_type, 'internal' lives in auth_internal.py
423 DEFAULT_AUTH_TYPE = 'internal'
423 DEFAULT_AUTH_TYPE = 'internal'
424
424
425 user_id = Column(Integer(), primary_key=True)
425 user_id = Column(Integer(), primary_key=True)
426 username = Column(String(255), nullable=False, unique=True)
426 username = Column(String(255), nullable=False, unique=True)
427 password = Column(String(255), nullable=False)
427 password = Column(String(255), nullable=False)
428 active = Column(Boolean(), nullable=False, default=True)
428 active = Column(Boolean(), nullable=False, default=True)
429 admin = Column(Boolean(), nullable=False, default=False)
429 admin = Column(Boolean(), nullable=False, default=False)
430 name = Column("firstname", Unicode(255), nullable=False)
430 name = Column("firstname", Unicode(255), nullable=False)
431 lastname = Column(Unicode(255), nullable=False)
431 lastname = Column(Unicode(255), nullable=False)
432 _email = Column("email", String(255), nullable=True, unique=True) # FIXME: not nullable?
432 _email = Column("email", String(255), nullable=True, unique=True) # FIXME: not nullable?
433 last_login = Column(DateTime(timezone=False), nullable=True)
433 last_login = Column(DateTime(timezone=False), nullable=True)
434 extern_type = Column(String(255), nullable=True) # FIXME: not nullable?
434 extern_type = Column(String(255), nullable=True) # FIXME: not nullable?
435 extern_name = Column(String(255), nullable=True) # FIXME: not nullable?
435 extern_name = Column(String(255), nullable=True) # FIXME: not nullable?
436 api_key = Column(String(255), nullable=False)
436 api_key = Column(String(255), nullable=False)
437 inherit_default_permissions = Column(Boolean(), nullable=False, default=True)
437 inherit_default_permissions = Column(Boolean(), nullable=False, default=True)
438 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
438 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
439 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
439 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
440
440
441 user_log = relationship('UserLog')
441 user_log = relationship('UserLog')
442 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
442 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
443
443
444 repositories = relationship('Repository')
444 repositories = relationship('Repository')
445 repo_groups = relationship('RepoGroup')
445 repo_groups = relationship('RepoGroup')
446 user_groups = relationship('UserGroup')
446 user_groups = relationship('UserGroup')
447 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
447 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
448 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
448 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
449
449
450 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
450 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
451 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
451 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
452
452
453 group_member = relationship('UserGroupMember', cascade='all')
453 group_member = relationship('UserGroupMember', cascade='all')
454
454
455 notifications = relationship('UserNotification', cascade='all')
455 notifications = relationship('UserNotification', cascade='all')
456 # notifications assigned to this user
456 # notifications assigned to this user
457 user_created_notifications = relationship('Notification', cascade='all')
457 user_created_notifications = relationship('Notification', cascade='all')
458 # comments created by this user
458 # comments created by this user
459 user_comments = relationship('ChangesetComment', cascade='all')
459 user_comments = relationship('ChangesetComment', cascade='all')
460 #extra emails for this user
460 #extra emails for this user
461 user_emails = relationship('UserEmailMap', cascade='all')
461 user_emails = relationship('UserEmailMap', cascade='all')
462 #extra API keys
462 #extra API keys
463 user_api_keys = relationship('UserApiKeys', cascade='all')
463 user_api_keys = relationship('UserApiKeys', cascade='all')
464
464
465
465
466 @hybrid_property
466 @hybrid_property
467 def email(self):
467 def email(self):
468 return self._email
468 return self._email
469
469
470 @email.setter
470 @email.setter
471 def email(self, val):
471 def email(self, val):
472 self._email = val.lower() if val else None
472 self._email = val.lower() if val else None
473
473
474 @property
474 @property
475 def firstname(self):
475 def firstname(self):
476 # alias for future
476 # alias for future
477 return self.name
477 return self.name
478
478
479 @property
479 @property
480 def emails(self):
480 def emails(self):
481 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
481 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
482 return [self.email] + [x.email for x in other]
482 return [self.email] + [x.email for x in other]
483
483
484 @property
484 @property
485 def api_keys(self):
485 def api_keys(self):
486 other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
486 other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
487 return [self.api_key] + [x.api_key for x in other]
487 return [self.api_key] + [x.api_key for x in other]
488
488
489 @property
489 @property
490 def ip_addresses(self):
490 def ip_addresses(self):
491 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
491 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
492 return [x.ip_addr for x in ret]
492 return [x.ip_addr for x in ret]
493
493
494 @property
494 @property
495 def full_name(self):
495 def full_name(self):
496 return '%s %s' % (self.firstname, self.lastname)
496 return '%s %s' % (self.firstname, self.lastname)
497
497
498 @property
498 @property
499 def full_name_or_username(self):
499 def full_name_or_username(self):
500 """
500 """
501 Show full name.
501 Show full name.
502 If full name is not set, fall back to username.
502 If full name is not set, fall back to username.
503 """
503 """
504 return ('%s %s' % (self.firstname, self.lastname)
504 return ('%s %s' % (self.firstname, self.lastname)
505 if (self.firstname and self.lastname) else self.username)
505 if (self.firstname and self.lastname) else self.username)
506
506
507 @property
507 @property
508 def full_name_and_username(self):
508 def full_name_and_username(self):
509 """
509 """
510 Show full name and username as 'Firstname Lastname (username)'.
510 Show full name and username as 'Firstname Lastname (username)'.
511 If full name is not set, fall back to username.
511 If full name is not set, fall back to username.
512 """
512 """
513 return ('%s %s (%s)' % (self.firstname, self.lastname, self.username)
513 return ('%s %s (%s)' % (self.firstname, self.lastname, self.username)
514 if (self.firstname and self.lastname) else self.username)
514 if (self.firstname and self.lastname) else self.username)
515
515
516 @property
516 @property
517 def full_contact(self):
517 def full_contact(self):
518 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
518 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
519
519
520 @property
520 @property
521 def short_contact(self):
521 def short_contact(self):
522 return '%s %s' % (self.firstname, self.lastname)
522 return '%s %s' % (self.firstname, self.lastname)
523
523
524 @property
524 @property
525 def is_admin(self):
525 def is_admin(self):
526 return self.admin
526 return self.admin
527
527
528 @hybrid_property
528 @hybrid_property
529 def is_default_user(self):
529 def is_default_user(self):
530 return self.username == User.DEFAULT_USER
530 return self.username == User.DEFAULT_USER
531
531
532 @property
532 @property
533 def AuthUser(self):
533 def AuthUser(self):
534 """
534 """
535 Returns instance of AuthUser for this user
535 Returns instance of AuthUser for this user
536 """
536 """
537 from kallithea.lib.auth import AuthUser
537 from kallithea.lib.auth import AuthUser
538 return AuthUser(dbuser=self)
538 return AuthUser(dbuser=self)
539
539
540 @hybrid_property
540 @hybrid_property
541 def user_data(self):
541 def user_data(self):
542 if not self._user_data:
542 if not self._user_data:
543 return {}
543 return {}
544
544
545 try:
545 try:
546 return json.loads(self._user_data)
546 return json.loads(self._user_data)
547 except TypeError:
547 except TypeError:
548 return {}
548 return {}
549
549
550 @user_data.setter
550 @user_data.setter
551 def user_data(self, val):
551 def user_data(self, val):
552 try:
552 try:
553 self._user_data = json.dumps(val)
553 self._user_data = json.dumps(val)
554 except Exception:
554 except Exception:
555 log.error(traceback.format_exc())
555 log.error(traceback.format_exc())
556
556
557 def __unicode__(self):
557 def __unicode__(self):
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
559 self.user_id, self.username)
559 self.user_id, self.username)
560
560
561 @classmethod
561 @classmethod
562 def guess_instance(cls, value):
562 def guess_instance(cls, value):
563 return super(User, cls).guess_instance(value, User.get_by_username)
563 return super(User, cls).guess_instance(value, User.get_by_username)
564
564
565 @classmethod
565 @classmethod
566 def get_or_404(cls, id_, allow_default=True):
566 def get_or_404(cls, id_, allow_default=True):
567 '''
567 '''
568 Overridden version of BaseDbModel.get_or_404, with an extra check on
568 Overridden version of BaseDbModel.get_or_404, with an extra check on
569 the default user.
569 the default user.
570 '''
570 '''
571 user = super(User, cls).get_or_404(id_)
571 user = super(User, cls).get_or_404(id_)
572 if not allow_default and user.is_default_user:
572 if not allow_default and user.is_default_user:
573 raise DefaultUserException()
573 raise DefaultUserException()
574 return user
574 return user
575
575
576 @classmethod
576 @classmethod
577 def get_by_username_or_email(cls, username_or_email, case_insensitive=False, cache=False):
577 def get_by_username_or_email(cls, username_or_email, case_insensitive=False, cache=False):
578 """
578 """
579 For anything that looks like an email address, look up by the email address (matching
579 For anything that looks like an email address, look up by the email address (matching
580 case insensitively).
580 case insensitively).
581 For anything else, try to look up by the user name.
581 For anything else, try to look up by the user name.
582
582
583 This assumes no normal username can have '@' symbol.
583 This assumes no normal username can have '@' symbol.
584 """
584 """
585 if '@' in username_or_email:
585 if '@' in username_or_email:
586 return User.get_by_email(username_or_email, cache=cache)
586 return User.get_by_email(username_or_email, cache=cache)
587 else:
587 else:
588 return User.get_by_username(username_or_email, case_insensitive=case_insensitive, cache=cache)
588 return User.get_by_username(username_or_email, case_insensitive=case_insensitive, cache=cache)
589
589
590 @classmethod
590 @classmethod
591 def get_by_username(cls, username, case_insensitive=False, cache=False):
591 def get_by_username(cls, username, case_insensitive=False, cache=False):
592 if case_insensitive:
592 if case_insensitive:
593 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
593 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
594 else:
594 else:
595 q = cls.query().filter(cls.username == username)
595 q = cls.query().filter(cls.username == username)
596
596
597 if cache:
597 if cache:
598 q = q.options(FromCache(
598 q = q.options(FromCache(
599 "sql_cache_short",
599 "sql_cache_short",
600 "get_user_%s" % _hash_key(username)
600 "get_user_%s" % _hash_key(username)
601 )
601 )
602 )
602 )
603 return q.scalar()
603 return q.scalar()
604
604
605 @classmethod
605 @classmethod
606 def get_by_api_key(cls, api_key, cache=False, fallback=True):
606 def get_by_api_key(cls, api_key, cache=False, fallback=True):
607 if len(api_key) != 40 or not api_key.isalnum():
607 if len(api_key) != 40 or not api_key.isalnum():
608 return None
608 return None
609
609
610 q = cls.query().filter(cls.api_key == api_key)
610 q = cls.query().filter(cls.api_key == api_key)
611
611
612 if cache:
612 if cache:
613 q = q.options(FromCache("sql_cache_short",
613 q = q.options(FromCache("sql_cache_short",
614 "get_api_key_%s" % api_key))
614 "get_api_key_%s" % api_key))
615 res = q.scalar()
615 res = q.scalar()
616
616
617 if fallback and not res:
617 if fallback and not res:
618 #fallback to additional keys
618 #fallback to additional keys
619 _res = UserApiKeys.query().filter_by(api_key=api_key, is_expired=False).first()
619 _res = UserApiKeys.query().filter_by(api_key=api_key, is_expired=False).first()
620 if _res:
620 if _res:
621 res = _res.user
621 res = _res.user
622 return res
622 return res
623
623
624 @classmethod
624 @classmethod
625 def get_by_email(cls, email, cache=False):
625 def get_by_email(cls, email, cache=False):
626 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
626 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
627
627
628 if cache:
628 if cache:
629 q = q.options(FromCache("sql_cache_short",
629 q = q.options(FromCache("sql_cache_short",
630 "get_email_key_%s" % email))
630 "get_email_key_%s" % email))
631
631
632 ret = q.scalar()
632 ret = q.scalar()
633 if ret is None:
633 if ret is None:
634 q = UserEmailMap.query()
634 q = UserEmailMap.query()
635 # try fetching in alternate email map
635 # try fetching in alternate email map
636 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
636 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
637 q = q.options(joinedload(UserEmailMap.user))
637 q = q.options(joinedload(UserEmailMap.user))
638 if cache:
638 if cache:
639 q = q.options(FromCache("sql_cache_short",
639 q = q.options(FromCache("sql_cache_short",
640 "get_email_map_key_%s" % email))
640 "get_email_map_key_%s" % email))
641 ret = getattr(q.scalar(), 'user', None)
641 ret = getattr(q.scalar(), 'user', None)
642
642
643 return ret
643 return ret
644
644
645 @classmethod
645 @classmethod
646 def get_from_cs_author(cls, author):
646 def get_from_cs_author(cls, author):
647 """
647 """
648 Tries to get User objects out of commit author string
648 Tries to get User objects out of commit author string
649
649
650 :param author:
650 :param author:
651 """
651 """
652 from kallithea.lib.helpers import email, author_name
652 from kallithea.lib.helpers import email, author_name
653 # Valid email in the attribute passed, see if they're in the system
653 # Valid email in the attribute passed, see if they're in the system
654 _email = email(author)
654 _email = email(author)
655 if _email:
655 if _email:
656 user = cls.get_by_email(_email)
656 user = cls.get_by_email(_email)
657 if user is not None:
657 if user is not None:
658 return user
658 return user
659 # Maybe we can match by username?
659 # Maybe we can match by username?
660 _author = author_name(author)
660 _author = author_name(author)
661 user = cls.get_by_username(_author, case_insensitive=True)
661 user = cls.get_by_username(_author, case_insensitive=True)
662 if user is not None:
662 if user is not None:
663 return user
663 return user
664
664
665 def update_lastlogin(self):
665 def update_lastlogin(self):
666 """Update user lastlogin"""
666 """Update user lastlogin"""
667 self.last_login = datetime.datetime.now()
667 self.last_login = datetime.datetime.now()
668 log.debug('updated user %s lastlogin', self.username)
668 log.debug('updated user %s lastlogin', self.username)
669
669
670 @classmethod
670 @classmethod
671 def get_first_admin(cls):
671 def get_first_admin(cls):
672 user = User.query().filter(User.admin == True).first()
672 user = User.query().filter(User.admin == True).first()
673 if user is None:
673 if user is None:
674 raise Exception('Missing administrative account!')
674 raise Exception('Missing administrative account!')
675 return user
675 return user
676
676
677 @classmethod
677 @classmethod
678 def get_default_user(cls, cache=False):
678 def get_default_user(cls, cache=False):
679 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
679 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
680 if user is None:
680 if user is None:
681 raise Exception('Missing default account!')
681 raise Exception('Missing default account!')
682 return user
682 return user
683
683
684 def get_api_data(self, details=False):
684 def get_api_data(self, details=False):
685 """
685 """
686 Common function for generating user related data for API
686 Common function for generating user related data for API
687 """
687 """
688 user = self
688 user = self
689 data = dict(
689 data = dict(
690 user_id=user.user_id,
690 user_id=user.user_id,
691 username=user.username,
691 username=user.username,
692 firstname=user.name,
692 firstname=user.name,
693 lastname=user.lastname,
693 lastname=user.lastname,
694 email=user.email,
694 email=user.email,
695 emails=user.emails,
695 emails=user.emails,
696 active=user.active,
696 active=user.active,
697 admin=user.admin,
697 admin=user.admin,
698 )
698 )
699 if details:
699 if details:
700 data.update(dict(
700 data.update(dict(
701 extern_type=user.extern_type,
701 extern_type=user.extern_type,
702 extern_name=user.extern_name,
702 extern_name=user.extern_name,
703 api_key=user.api_key,
703 api_key=user.api_key,
704 api_keys=user.api_keys,
704 api_keys=user.api_keys,
705 last_login=user.last_login,
705 last_login=user.last_login,
706 ip_addresses=user.ip_addresses
706 ip_addresses=user.ip_addresses
707 ))
707 ))
708 return data
708 return data
709
709
710 def __json__(self):
710 def __json__(self):
711 data = dict(
711 data = dict(
712 full_name=self.full_name,
712 full_name=self.full_name,
713 full_name_or_username=self.full_name_or_username,
713 full_name_or_username=self.full_name_or_username,
714 short_contact=self.short_contact,
714 short_contact=self.short_contact,
715 full_contact=self.full_contact
715 full_contact=self.full_contact
716 )
716 )
717 data.update(self.get_api_data())
717 data.update(self.get_api_data())
718 return data
718 return data
719
719
720
720
721 class UserApiKeys(Base, BaseDbModel):
721 class UserApiKeys(Base, BaseDbModel):
722 __tablename__ = 'user_api_keys'
722 __tablename__ = 'user_api_keys'
723 __table_args__ = (
723 __table_args__ = (
724 Index('uak_api_key_idx', 'api_key'),
724 Index('uak_api_key_idx', 'api_key'),
725 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
725 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
726 _table_args_default_dict,
726 _table_args_default_dict,
727 )
727 )
728 __mapper_args__ = {}
728 __mapper_args__ = {}
729
729
730 user_api_key_id = Column(Integer(), primary_key=True)
730 user_api_key_id = Column(Integer(), primary_key=True)
731 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
731 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
732 api_key = Column(String(255), nullable=False, unique=True)
732 api_key = Column(String(255), nullable=False, unique=True)
733 description = Column(UnicodeText(), nullable=False)
733 description = Column(UnicodeText(), nullable=False)
734 expires = Column(Float(53), nullable=False)
734 expires = Column(Float(53), nullable=False)
735 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
735 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
736
736
737 user = relationship('User')
737 user = relationship('User')
738
738
739 @hybrid_property
739 @hybrid_property
740 def is_expired(self):
740 def is_expired(self):
741 return (self.expires != -1) & (time.time() > self.expires)
741 return (self.expires != -1) & (time.time() > self.expires)
742
742
743
743
744 class UserEmailMap(Base, BaseDbModel):
744 class UserEmailMap(Base, BaseDbModel):
745 __tablename__ = 'user_email_map'
745 __tablename__ = 'user_email_map'
746 __table_args__ = (
746 __table_args__ = (
747 Index('uem_email_idx', 'email'),
747 Index('uem_email_idx', 'email'),
748 _table_args_default_dict,
748 _table_args_default_dict,
749 )
749 )
750 __mapper_args__ = {}
750 __mapper_args__ = {}
751
751
752 email_id = Column(Integer(), primary_key=True)
752 email_id = Column(Integer(), primary_key=True)
753 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
753 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
754 _email = Column("email", String(255), nullable=False, unique=True)
754 _email = Column("email", String(255), nullable=False, unique=True)
755 user = relationship('User')
755 user = relationship('User')
756
756
757 @validates('_email')
757 @validates('_email')
758 def validate_email(self, key, email):
758 def validate_email(self, key, email):
759 # check if this email is not main one
759 # check if this email is not main one
760 main_email = Session().query(User).filter(User.email == email).scalar()
760 main_email = Session().query(User).filter(User.email == email).scalar()
761 if main_email is not None:
761 if main_email is not None:
762 raise AttributeError('email %s is present is user table' % email)
762 raise AttributeError('email %s is present is user table' % email)
763 return email
763 return email
764
764
765 @hybrid_property
765 @hybrid_property
766 def email(self):
766 def email(self):
767 return self._email
767 return self._email
768
768
769 @email.setter
769 @email.setter
770 def email(self, val):
770 def email(self, val):
771 self._email = val.lower() if val else None
771 self._email = val.lower() if val else None
772
772
773
773
774 class UserIpMap(Base, BaseDbModel):
774 class UserIpMap(Base, BaseDbModel):
775 __tablename__ = 'user_ip_map'
775 __tablename__ = 'user_ip_map'
776 __table_args__ = (
776 __table_args__ = (
777 UniqueConstraint('user_id', 'ip_addr'),
777 UniqueConstraint('user_id', 'ip_addr'),
778 _table_args_default_dict,
778 _table_args_default_dict,
779 )
779 )
780 __mapper_args__ = {}
780 __mapper_args__ = {}
781
781
782 ip_id = Column(Integer(), primary_key=True)
782 ip_id = Column(Integer(), primary_key=True)
783 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
783 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
784 ip_addr = Column(String(255), nullable=False)
784 ip_addr = Column(String(255), nullable=False)
785 active = Column(Boolean(), nullable=False, default=True)
785 active = Column(Boolean(), nullable=False, default=True)
786 user = relationship('User')
786 user = relationship('User')
787
787
788 @classmethod
788 @classmethod
789 def _get_ip_range(cls, ip_addr):
789 def _get_ip_range(cls, ip_addr):
790 from kallithea.lib import ipaddr
790 from kallithea.lib import ipaddr
791 net = ipaddr.IPNetwork(address=ip_addr)
791 net = ipaddr.IPNetwork(address=ip_addr)
792 return [str(net.network), str(net.broadcast)]
792 return [str(net.network), str(net.broadcast)]
793
793
794 def __json__(self):
794 def __json__(self):
795 return dict(
795 return dict(
796 ip_addr=self.ip_addr,
796 ip_addr=self.ip_addr,
797 ip_range=self._get_ip_range(self.ip_addr)
797 ip_range=self._get_ip_range(self.ip_addr)
798 )
798 )
799
799
800 def __unicode__(self):
800 def __unicode__(self):
801 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
801 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
802 self.user_id, self.ip_addr)
802 self.user_id, self.ip_addr)
803
803
804 class UserLog(Base, BaseDbModel):
804 class UserLog(Base, BaseDbModel):
805 __tablename__ = 'user_logs'
805 __tablename__ = 'user_logs'
806 __table_args__ = (
806 __table_args__ = (
807 _table_args_default_dict,
807 _table_args_default_dict,
808 )
808 )
809
809
810 user_log_id = Column(Integer(), primary_key=True)
810 user_log_id = Column(Integer(), primary_key=True)
811 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
811 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
812 username = Column(String(255), nullable=False)
812 username = Column(String(255), nullable=False)
813 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
813 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
814 repository_name = Column(Unicode(255), nullable=False)
814 repository_name = Column(Unicode(255), nullable=False)
815 user_ip = Column(String(255), nullable=True)
815 user_ip = Column(String(255), nullable=True)
816 action = Column(UnicodeText(), nullable=False)
816 action = Column(UnicodeText(), nullable=False)
817 action_date = Column(DateTime(timezone=False), nullable=False)
817 action_date = Column(DateTime(timezone=False), nullable=False)
818
818
819 def __unicode__(self):
819 def __unicode__(self):
820 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
820 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
821 self.repository_name,
821 self.repository_name,
822 self.action)
822 self.action)
823
823
824 @property
824 @property
825 def action_as_day(self):
825 def action_as_day(self):
826 return datetime.date(*self.action_date.timetuple()[:3])
826 return datetime.date(*self.action_date.timetuple()[:3])
827
827
828 user = relationship('User')
828 user = relationship('User')
829 repository = relationship('Repository', cascade='')
829 repository = relationship('Repository', cascade='')
830
830
831
831
832 class UserGroup(Base, BaseDbModel):
832 class UserGroup(Base, BaseDbModel):
833 __tablename__ = 'users_groups'
833 __tablename__ = 'users_groups'
834 __table_args__ = (
834 __table_args__ = (
835 _table_args_default_dict,
835 _table_args_default_dict,
836 )
836 )
837
837
838 users_group_id = Column(Integer(), primary_key=True)
838 users_group_id = Column(Integer(), primary_key=True)
839 users_group_name = Column(Unicode(255), nullable=False, unique=True)
839 users_group_name = Column(Unicode(255), nullable=False, unique=True)
840 user_group_description = Column(Unicode(10000), nullable=True) # FIXME: not nullable?
840 user_group_description = Column(Unicode(10000), nullable=True) # FIXME: not nullable?
841 users_group_active = Column(Boolean(), nullable=False)
841 users_group_active = Column(Boolean(), nullable=False)
842 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, default=True)
842 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, default=True)
843 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
843 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
844 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
844 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
845 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
845 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
846
846
847 members = relationship('UserGroupMember', cascade="all, delete-orphan")
847 members = relationship('UserGroupMember', cascade="all, delete-orphan")
848 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
848 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
849 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
849 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
850 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
850 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
851 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
851 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
852 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
852 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
853
853
854 owner = relationship('User')
854 owner = relationship('User')
855
855
856 @hybrid_property
856 @hybrid_property
857 def group_data(self):
857 def group_data(self):
858 if not self._group_data:
858 if not self._group_data:
859 return {}
859 return {}
860
860
861 try:
861 try:
862 return json.loads(self._group_data)
862 return json.loads(self._group_data)
863 except TypeError:
863 except TypeError:
864 return {}
864 return {}
865
865
866 @group_data.setter
866 @group_data.setter
867 def group_data(self, val):
867 def group_data(self, val):
868 try:
868 try:
869 self._group_data = json.dumps(val)
869 self._group_data = json.dumps(val)
870 except Exception:
870 except Exception:
871 log.error(traceback.format_exc())
871 log.error(traceback.format_exc())
872
872
873 def __unicode__(self):
873 def __unicode__(self):
874 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
874 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
875 self.users_group_id,
875 self.users_group_id,
876 self.users_group_name)
876 self.users_group_name)
877
877
878 @classmethod
878 @classmethod
879 def guess_instance(cls, value):
879 def guess_instance(cls, value):
880 return super(UserGroup, cls).guess_instance(value, UserGroup.get_by_group_name)
880 return super(UserGroup, cls).guess_instance(value, UserGroup.get_by_group_name)
881
881
882 @classmethod
882 @classmethod
883 def get_by_group_name(cls, group_name, cache=False,
883 def get_by_group_name(cls, group_name, cache=False,
884 case_insensitive=False):
884 case_insensitive=False):
885 if case_insensitive:
885 if case_insensitive:
886 q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
886 q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
887 else:
887 else:
888 q = cls.query().filter(cls.users_group_name == group_name)
888 q = cls.query().filter(cls.users_group_name == group_name)
889 if cache:
889 if cache:
890 q = q.options(FromCache(
890 q = q.options(FromCache(
891 "sql_cache_short",
891 "sql_cache_short",
892 "get_group_%s" % _hash_key(group_name)
892 "get_group_%s" % _hash_key(group_name)
893 )
893 )
894 )
894 )
895 return q.scalar()
895 return q.scalar()
896
896
897 @classmethod
897 @classmethod
898 def get(cls, user_group_id, cache=False):
898 def get(cls, user_group_id, cache=False):
899 user_group = cls.query()
899 user_group = cls.query()
900 if cache:
900 if cache:
901 user_group = user_group.options(FromCache("sql_cache_short",
901 user_group = user_group.options(FromCache("sql_cache_short",
902 "get_users_group_%s" % user_group_id))
902 "get_users_group_%s" % user_group_id))
903 return user_group.get(user_group_id)
903 return user_group.get(user_group_id)
904
904
905 def get_api_data(self, with_members=True):
905 def get_api_data(self, with_members=True):
906 user_group = self
906 user_group = self
907
907
908 data = dict(
908 data = dict(
909 users_group_id=user_group.users_group_id,
909 users_group_id=user_group.users_group_id,
910 group_name=user_group.users_group_name,
910 group_name=user_group.users_group_name,
911 group_description=user_group.user_group_description,
911 group_description=user_group.user_group_description,
912 active=user_group.users_group_active,
912 active=user_group.users_group_active,
913 owner=user_group.owner.username,
913 owner=user_group.owner.username,
914 )
914 )
915 if with_members:
915 if with_members:
916 data['members'] = [
916 data['members'] = [
917 ugm.user.get_api_data()
917 ugm.user.get_api_data()
918 for ugm in user_group.members
918 for ugm in user_group.members
919 ]
919 ]
920
920
921 return data
921 return data
922
922
923
923
924 class UserGroupMember(Base, BaseDbModel):
924 class UserGroupMember(Base, BaseDbModel):
925 __tablename__ = 'users_groups_members'
925 __tablename__ = 'users_groups_members'
926 __table_args__ = (
926 __table_args__ = (
927 _table_args_default_dict,
927 _table_args_default_dict,
928 )
928 )
929
929
930 users_group_member_id = Column(Integer(), primary_key=True)
930 users_group_member_id = Column(Integer(), primary_key=True)
931 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
931 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
932 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
932 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
933
933
934 user = relationship('User')
934 user = relationship('User')
935 users_group = relationship('UserGroup')
935 users_group = relationship('UserGroup')
936
936
937 def __init__(self, gr_id='', u_id=''):
937 def __init__(self, gr_id='', u_id=''):
938 self.users_group_id = gr_id
938 self.users_group_id = gr_id
939 self.user_id = u_id
939 self.user_id = u_id
940
940
941
941
942 class RepositoryField(Base, BaseDbModel):
942 class RepositoryField(Base, BaseDbModel):
943 __tablename__ = 'repositories_fields'
943 __tablename__ = 'repositories_fields'
944 __table_args__ = (
944 __table_args__ = (
945 UniqueConstraint('repository_id', 'field_key'), # no-multi field
945 UniqueConstraint('repository_id', 'field_key'), # no-multi field
946 _table_args_default_dict,
946 _table_args_default_dict,
947 )
947 )
948
948
949 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
949 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
950
950
951 repo_field_id = Column(Integer(), primary_key=True)
951 repo_field_id = Column(Integer(), primary_key=True)
952 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
952 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
953 field_key = Column(String(250), nullable=False)
953 field_key = Column(String(250), nullable=False)
954 field_label = Column(String(1024), nullable=False)
954 field_label = Column(String(1024), nullable=False)
955 field_value = Column(String(10000), nullable=False)
955 field_value = Column(String(10000), nullable=False)
956 field_desc = Column(String(1024), nullable=False)
956 field_desc = Column(String(1024), nullable=False)
957 field_type = Column(String(255), nullable=False)
957 field_type = Column(String(255), nullable=False)
958 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
958 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
959
959
960 repository = relationship('Repository')
960 repository = relationship('Repository')
961
961
962 @property
962 @property
963 def field_key_prefixed(self):
963 def field_key_prefixed(self):
964 return 'ex_%s' % self.field_key
964 return 'ex_%s' % self.field_key
965
965
966 @classmethod
966 @classmethod
967 def un_prefix_key(cls, key):
967 def un_prefix_key(cls, key):
968 if key.startswith(cls.PREFIX):
968 if key.startswith(cls.PREFIX):
969 return key[len(cls.PREFIX):]
969 return key[len(cls.PREFIX):]
970 return key
970 return key
971
971
972 @classmethod
972 @classmethod
973 def get_by_key_name(cls, key, repo):
973 def get_by_key_name(cls, key, repo):
974 row = cls.query() \
974 row = cls.query() \
975 .filter(cls.repository == repo) \
975 .filter(cls.repository == repo) \
976 .filter(cls.field_key == key).scalar()
976 .filter(cls.field_key == key).scalar()
977 return row
977 return row
978
978
979
979
980 class Repository(Base, BaseDbModel):
980 class Repository(Base, BaseDbModel):
981 __tablename__ = 'repositories'
981 __tablename__ = 'repositories'
982 __table_args__ = (
982 __table_args__ = (
983 Index('r_repo_name_idx', 'repo_name'),
983 Index('r_repo_name_idx', 'repo_name'),
984 _table_args_default_dict,
984 _table_args_default_dict,
985 )
985 )
986
986
987 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
987 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
988 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
988 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
989
989
990 STATE_CREATED = u'repo_state_created'
990 STATE_CREATED = u'repo_state_created'
991 STATE_PENDING = u'repo_state_pending'
991 STATE_PENDING = u'repo_state_pending'
992 STATE_ERROR = u'repo_state_error'
992 STATE_ERROR = u'repo_state_error'
993
993
994 repo_id = Column(Integer(), primary_key=True)
994 repo_id = Column(Integer(), primary_key=True)
995 repo_name = Column(Unicode(255), nullable=False, unique=True)
995 repo_name = Column(Unicode(255), nullable=False, unique=True)
996 repo_state = Column(String(255), nullable=False)
996 repo_state = Column(String(255), nullable=False)
997
997
998 clone_uri = Column(String(255), nullable=True) # FIXME: not nullable?
998 clone_uri = Column(String(255), nullable=True) # FIXME: not nullable?
999 repo_type = Column(String(255), nullable=False)
999 repo_type = Column(String(255), nullable=False)
1000 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1000 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1001 private = Column(Boolean(), nullable=False)
1001 private = Column(Boolean(), nullable=False)
1002 enable_statistics = Column("statistics", Boolean(), nullable=False, default=True)
1002 enable_statistics = Column("statistics", Boolean(), nullable=False, default=True)
1003 enable_downloads = Column("downloads", Boolean(), nullable=False, default=True)
1003 enable_downloads = Column("downloads", Boolean(), nullable=False, default=True)
1004 description = Column(Unicode(10000), nullable=False)
1004 description = Column(Unicode(10000), nullable=False)
1005 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1005 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1006 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1006 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1007 _landing_revision = Column("landing_revision", String(255), nullable=False)
1007 _landing_revision = Column("landing_revision", String(255), nullable=False)
1008 enable_locking = Column(Boolean(), nullable=False, default=False)
1008 enable_locking = Column(Boolean(), nullable=False, default=False)
1009 _locked = Column("locked", String(255), nullable=True) # FIXME: not nullable?
1009 _locked = Column("locked", String(255), nullable=True) # FIXME: not nullable?
1010 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
1010 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
1011
1011
1012 fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1012 fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1013 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
1013 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
1014
1014
1015 owner = relationship('User')
1015 owner = relationship('User')
1016 fork = relationship('Repository', remote_side=repo_id)
1016 fork = relationship('Repository', remote_side=repo_id)
1017 group = relationship('RepoGroup')
1017 group = relationship('RepoGroup')
1018 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1018 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1019 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1019 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1020 stats = relationship('Statistics', cascade='all', uselist=False)
1020 stats = relationship('Statistics', cascade='all', uselist=False)
1021
1021
1022 followers = relationship('UserFollowing',
1022 followers = relationship('UserFollowing',
1023 primaryjoin='UserFollowing.follows_repository_id==Repository.repo_id',
1023 primaryjoin='UserFollowing.follows_repository_id==Repository.repo_id',
1024 cascade='all')
1024 cascade='all')
1025 extra_fields = relationship('RepositoryField',
1025 extra_fields = relationship('RepositoryField',
1026 cascade="all, delete-orphan")
1026 cascade="all, delete-orphan")
1027
1027
1028 logs = relationship('UserLog')
1028 logs = relationship('UserLog')
1029 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
1029 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
1030
1030
1031 pull_requests_org = relationship('PullRequest',
1031 pull_requests_org = relationship('PullRequest',
1032 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
1032 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
1033 cascade="all, delete-orphan")
1033 cascade="all, delete-orphan")
1034
1034
1035 pull_requests_other = relationship('PullRequest',
1035 pull_requests_other = relationship('PullRequest',
1036 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
1036 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
1037 cascade="all, delete-orphan")
1037 cascade="all, delete-orphan")
1038
1038
1039 def __unicode__(self):
1039 def __unicode__(self):
1040 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1040 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1041 safe_unicode(self.repo_name))
1041 safe_unicode(self.repo_name))
1042
1042
1043 @hybrid_property
1043 @hybrid_property
1044 def landing_rev(self):
1044 def landing_rev(self):
1045 # always should return [rev_type, rev]
1045 # always should return [rev_type, rev]
1046 if self._landing_revision:
1046 if self._landing_revision:
1047 _rev_info = self._landing_revision.split(':')
1047 _rev_info = self._landing_revision.split(':')
1048 if len(_rev_info) < 2:
1048 if len(_rev_info) < 2:
1049 _rev_info.insert(0, 'rev')
1049 _rev_info.insert(0, 'rev')
1050 return [_rev_info[0], _rev_info[1]]
1050 return [_rev_info[0], _rev_info[1]]
1051 return [None, None]
1051 return [None, None]
1052
1052
1053 @landing_rev.setter
1053 @landing_rev.setter
1054 def landing_rev(self, val):
1054 def landing_rev(self, val):
1055 if ':' not in val:
1055 if ':' not in val:
1056 raise ValueError('value must be delimited with `:` and consist '
1056 raise ValueError('value must be delimited with `:` and consist '
1057 'of <rev_type>:<rev>, got %s instead' % val)
1057 'of <rev_type>:<rev>, got %s instead' % val)
1058 self._landing_revision = val
1058 self._landing_revision = val
1059
1059
1060 @hybrid_property
1060 @hybrid_property
1061 def locked(self):
1061 def locked(self):
1062 # always should return [user_id, timelocked]
1062 # always should return [user_id, timelocked]
1063 if self._locked:
1063 if self._locked:
1064 _lock_info = self._locked.split(':')
1064 _lock_info = self._locked.split(':')
1065 return int(_lock_info[0]), _lock_info[1]
1065 return int(_lock_info[0]), _lock_info[1]
1066 return [None, None]
1066 return [None, None]
1067
1067
1068 @locked.setter
1068 @locked.setter
1069 def locked(self, val):
1069 def locked(self, val):
1070 if val and isinstance(val, (list, tuple)):
1070 if val and isinstance(val, (list, tuple)):
1071 self._locked = ':'.join(map(str, val))
1071 self._locked = ':'.join(map(str, val))
1072 else:
1072 else:
1073 self._locked = None
1073 self._locked = None
1074
1074
1075 @hybrid_property
1075 @hybrid_property
1076 def changeset_cache(self):
1076 def changeset_cache(self):
1077 try:
1077 try:
1078 cs_cache = json.loads(self._changeset_cache) # might raise on bad data
1078 cs_cache = json.loads(self._changeset_cache) # might raise on bad data
1079 cs_cache['raw_id'] # verify data, raise exception on error
1079 cs_cache['raw_id'] # verify data, raise exception on error
1080 return cs_cache
1080 return cs_cache
1081 except (TypeError, KeyError, ValueError):
1081 except (TypeError, KeyError, ValueError):
1082 return EmptyChangeset().__json__()
1082 return EmptyChangeset().__json__()
1083
1083
1084 @changeset_cache.setter
1084 @changeset_cache.setter
1085 def changeset_cache(self, val):
1085 def changeset_cache(self, val):
1086 try:
1086 try:
1087 self._changeset_cache = json.dumps(val)
1087 self._changeset_cache = json.dumps(val)
1088 except Exception:
1088 except Exception:
1089 log.error(traceback.format_exc())
1089 log.error(traceback.format_exc())
1090
1090
1091 @classmethod
1091 @classmethod
1092 def query(cls, sorted=False):
1092 def query(cls, sorted=False):
1093 """Add Repository-specific helpers for common query constructs.
1093 """Add Repository-specific helpers for common query constructs.
1094
1094
1095 sorted: if True, apply the default ordering (name, case insensitive).
1095 sorted: if True, apply the default ordering (name, case insensitive).
1096 """
1096 """
1097 q = super(Repository, cls).query()
1097 q = super(Repository, cls).query()
1098
1098
1099 if sorted:
1099 if sorted:
1100 q = q.order_by(func.lower(Repository.repo_name))
1100 q = q.order_by(func.lower(Repository.repo_name))
1101
1101
1102 return q
1102 return q
1103
1103
1104 @classmethod
1104 @classmethod
1105 def url_sep(cls):
1105 def url_sep(cls):
1106 return URL_SEP
1106 return URL_SEP
1107
1107
1108 @classmethod
1108 @classmethod
1109 def normalize_repo_name(cls, repo_name):
1109 def normalize_repo_name(cls, repo_name):
1110 """
1110 """
1111 Normalizes os specific repo_name to the format internally stored inside
1111 Normalizes os specific repo_name to the format internally stored inside
1112 database using URL_SEP
1112 database using URL_SEP
1113
1113
1114 :param cls:
1114 :param cls:
1115 :param repo_name:
1115 :param repo_name:
1116 """
1116 """
1117 return cls.url_sep().join(repo_name.split(os.sep))
1117 return cls.url_sep().join(repo_name.split(os.sep))
1118
1118
1119 @classmethod
1119 @classmethod
1120 def guess_instance(cls, value):
1120 def guess_instance(cls, value):
1121 return super(Repository, cls).guess_instance(value, Repository.get_by_repo_name)
1121 return super(Repository, cls).guess_instance(value, Repository.get_by_repo_name)
1122
1122
1123 @classmethod
1123 @classmethod
1124 def get_by_repo_name(cls, repo_name, case_insensitive=False):
1124 def get_by_repo_name(cls, repo_name, case_insensitive=False):
1125 """Get the repo, defaulting to database case sensitivity.
1125 """Get the repo, defaulting to database case sensitivity.
1126 case_insensitive will be slower and should only be specified if necessary."""
1126 case_insensitive will be slower and should only be specified if necessary."""
1127 if case_insensitive:
1127 if case_insensitive:
1128 q = Session().query(cls).filter(func.lower(cls.repo_name) == func.lower(repo_name))
1128 q = Session().query(cls).filter(func.lower(cls.repo_name) == func.lower(repo_name))
1129 else:
1129 else:
1130 q = Session().query(cls).filter(cls.repo_name == repo_name)
1130 q = Session().query(cls).filter(cls.repo_name == repo_name)
1131 q = q.options(joinedload(Repository.fork)) \
1131 q = q.options(joinedload(Repository.fork)) \
1132 .options(joinedload(Repository.owner)) \
1132 .options(joinedload(Repository.owner)) \
1133 .options(joinedload(Repository.group))
1133 .options(joinedload(Repository.group))
1134 return q.scalar()
1134 return q.scalar()
1135
1135
1136 @classmethod
1136 @classmethod
1137 def get_by_full_path(cls, repo_full_path):
1137 def get_by_full_path(cls, repo_full_path):
1138 base_full_path = os.path.realpath(cls.base_path())
1138 base_full_path = os.path.realpath(cls.base_path())
1139 repo_full_path = os.path.realpath(repo_full_path)
1139 repo_full_path = os.path.realpath(repo_full_path)
1140 assert repo_full_path.startswith(base_full_path + os.path.sep)
1140 assert repo_full_path.startswith(base_full_path + os.path.sep)
1141 repo_name = repo_full_path[len(base_full_path) + 1:]
1141 repo_name = repo_full_path[len(base_full_path) + 1:]
1142 repo_name = cls.normalize_repo_name(repo_name)
1142 repo_name = cls.normalize_repo_name(repo_name)
1143 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1143 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1144
1144
1145 @classmethod
1145 @classmethod
1146 def get_repo_forks(cls, repo_id):
1146 def get_repo_forks(cls, repo_id):
1147 return cls.query().filter(Repository.fork_id == repo_id)
1147 return cls.query().filter(Repository.fork_id == repo_id)
1148
1148
1149 @classmethod
1149 @classmethod
1150 def base_path(cls):
1150 def base_path(cls):
1151 """
1151 """
1152 Returns base path where all repos are stored
1152 Returns base path where all repos are stored
1153
1153
1154 :param cls:
1154 :param cls:
1155 """
1155 """
1156 q = Session().query(Ui) \
1156 q = Session().query(Ui) \
1157 .filter(Ui.ui_key == cls.url_sep())
1157 .filter(Ui.ui_key == cls.url_sep())
1158 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1158 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1159 return q.one().ui_value
1159 return q.one().ui_value
1160
1160
1161 @property
1161 @property
1162 def forks(self):
1162 def forks(self):
1163 """
1163 """
1164 Return forks of this repo
1164 Return forks of this repo
1165 """
1165 """
1166 return Repository.get_repo_forks(self.repo_id)
1166 return Repository.get_repo_forks(self.repo_id)
1167
1167
1168 @property
1168 @property
1169 def parent(self):
1169 def parent(self):
1170 """
1170 """
1171 Returns fork parent
1171 Returns fork parent
1172 """
1172 """
1173 return self.fork
1173 return self.fork
1174
1174
1175 @property
1175 @property
1176 def just_name(self):
1176 def just_name(self):
1177 return self.repo_name.split(Repository.url_sep())[-1]
1177 return self.repo_name.split(Repository.url_sep())[-1]
1178
1178
1179 @property
1179 @property
1180 def groups_with_parents(self):
1180 def groups_with_parents(self):
1181 groups = []
1181 groups = []
1182 group = self.group
1182 group = self.group
1183 while group is not None:
1183 while group is not None:
1184 groups.append(group)
1184 groups.append(group)
1185 group = group.parent_group
1185 group = group.parent_group
1186 assert group not in groups, group # avoid recursion on bad db content
1186 assert group not in groups, group # avoid recursion on bad db content
1187 groups.reverse()
1187 groups.reverse()
1188 return groups
1188 return groups
1189
1189
1190 @LazyProperty
1190 @LazyProperty
1191 def repo_path(self):
1191 def repo_path(self):
1192 """
1192 """
1193 Returns base full path for that repository means where it actually
1193 Returns base full path for that repository means where it actually
1194 exists on a filesystem
1194 exists on a filesystem
1195 """
1195 """
1196 q = Session().query(Ui).filter(Ui.ui_key ==
1196 q = Session().query(Ui).filter(Ui.ui_key ==
1197 Repository.url_sep())
1197 Repository.url_sep())
1198 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1198 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1199 return q.one().ui_value
1199 return q.one().ui_value
1200
1200
1201 @property
1201 @property
1202 def repo_full_path(self):
1202 def repo_full_path(self):
1203 p = [self.repo_path]
1203 p = [self.repo_path]
1204 # we need to split the name by / since this is how we store the
1204 # we need to split the name by / since this is how we store the
1205 # names in the database, but that eventually needs to be converted
1205 # names in the database, but that eventually needs to be converted
1206 # into a valid system path
1206 # into a valid system path
1207 p += self.repo_name.split(Repository.url_sep())
1207 p += self.repo_name.split(Repository.url_sep())
1208 return os.path.join(*map(safe_unicode, p))
1208 return os.path.join(*map(safe_unicode, p))
1209
1209
1210 @property
1210 @property
1211 def cache_keys(self):
1211 def cache_keys(self):
1212 """
1212 """
1213 Returns associated cache keys for that repo
1213 Returns associated cache keys for that repo
1214 """
1214 """
1215 return CacheInvalidation.query() \
1215 return CacheInvalidation.query() \
1216 .filter(CacheInvalidation.cache_args == self.repo_name) \
1216 .filter(CacheInvalidation.cache_args == self.repo_name) \
1217 .order_by(CacheInvalidation.cache_key) \
1217 .order_by(CacheInvalidation.cache_key) \
1218 .all()
1218 .all()
1219
1219
1220 def get_new_name(self, repo_name):
1220 def get_new_name(self, repo_name):
1221 """
1221 """
1222 returns new full repository name based on assigned group and new new
1222 returns new full repository name based on assigned group and new new
1223
1223
1224 :param group_name:
1224 :param group_name:
1225 """
1225 """
1226 path_prefix = self.group.full_path_splitted if self.group else []
1226 path_prefix = self.group.full_path_splitted if self.group else []
1227 return Repository.url_sep().join(path_prefix + [repo_name])
1227 return Repository.url_sep().join(path_prefix + [repo_name])
1228
1228
1229 @property
1229 @property
1230 def _ui(self):
1230 def _ui(self):
1231 """
1231 """
1232 Creates an db based ui object for this repository
1232 Creates an db based ui object for this repository
1233 """
1233 """
1234 from kallithea.lib.utils import make_ui
1234 from kallithea.lib.utils import make_ui
1235 return make_ui('db', clear_session=False)
1235 return make_ui('db', clear_session=False)
1236
1236
1237 @classmethod
1237 @classmethod
1238 def is_valid(cls, repo_name):
1238 def is_valid(cls, repo_name):
1239 """
1239 """
1240 returns True if given repo name is a valid filesystem repository
1240 returns True if given repo name is a valid filesystem repository
1241
1241
1242 :param cls:
1242 :param cls:
1243 :param repo_name:
1243 :param repo_name:
1244 """
1244 """
1245 from kallithea.lib.utils import is_valid_repo
1245 from kallithea.lib.utils import is_valid_repo
1246
1246
1247 return is_valid_repo(repo_name, cls.base_path())
1247 return is_valid_repo(repo_name, cls.base_path())
1248
1248
1249 def get_api_data(self, with_revision_names=False):
1249 def get_api_data(self, with_revision_names=False,
1250 with_pullrequests=False):
1250 """
1251 """
1251 Common function for generating repo api data.
1252 Common function for generating repo api data.
1252 Optionally, also return tags, branches and bookmarks.
1253 Optionally, also return tags, branches, bookmarks and PRs.
1253 """
1254 """
1254 repo = self
1255 repo = self
1255 data = dict(
1256 data = dict(
1256 repo_id=repo.repo_id,
1257 repo_id=repo.repo_id,
1257 repo_name=repo.repo_name,
1258 repo_name=repo.repo_name,
1258 repo_type=repo.repo_type,
1259 repo_type=repo.repo_type,
1259 clone_uri=repo.clone_uri,
1260 clone_uri=repo.clone_uri,
1260 private=repo.private,
1261 private=repo.private,
1261 created_on=repo.created_on,
1262 created_on=repo.created_on,
1262 description=repo.description,
1263 description=repo.description,
1263 landing_rev=repo.landing_rev,
1264 landing_rev=repo.landing_rev,
1264 owner=repo.owner.username,
1265 owner=repo.owner.username,
1265 fork_of=repo.fork.repo_name if repo.fork else None,
1266 fork_of=repo.fork.repo_name if repo.fork else None,
1266 enable_statistics=repo.enable_statistics,
1267 enable_statistics=repo.enable_statistics,
1267 enable_locking=repo.enable_locking,
1268 enable_locking=repo.enable_locking,
1268 enable_downloads=repo.enable_downloads,
1269 enable_downloads=repo.enable_downloads,
1269 last_changeset=repo.changeset_cache,
1270 last_changeset=repo.changeset_cache,
1270 locked_by=User.get(self.locked[0]).get_api_data() \
1271 locked_by=User.get(self.locked[0]).get_api_data() \
1271 if self.locked[0] else None,
1272 if self.locked[0] else None,
1272 locked_date=time_to_datetime(self.locked[1]) \
1273 locked_date=time_to_datetime(self.locked[1]) \
1273 if self.locked[1] else None
1274 if self.locked[1] else None
1274 )
1275 )
1275 if with_revision_names:
1276 if with_revision_names:
1276 scm_repo = repo.scm_instance_no_cache()
1277 scm_repo = repo.scm_instance_no_cache()
1277 data.update(dict(
1278 data.update(dict(
1278 tags=scm_repo.tags,
1279 tags=scm_repo.tags,
1279 branches=scm_repo.branches,
1280 branches=scm_repo.branches,
1280 bookmarks=scm_repo.bookmarks,
1281 bookmarks=scm_repo.bookmarks,
1281 ))
1282 ))
1283 if with_pullrequests:
1284 data['pull_requests'] = repo.pull_requests_other
1282 rc_config = Setting.get_app_settings()
1285 rc_config = Setting.get_app_settings()
1283 repository_fields = str2bool(rc_config.get('repository_fields'))
1286 repository_fields = str2bool(rc_config.get('repository_fields'))
1284 if repository_fields:
1287 if repository_fields:
1285 for f in self.extra_fields:
1288 for f in self.extra_fields:
1286 data[f.field_key_prefixed] = f.field_value
1289 data[f.field_key_prefixed] = f.field_value
1287
1290
1288 return data
1291 return data
1289
1292
1290 @classmethod
1293 @classmethod
1291 def lock(cls, repo, user_id, lock_time=None):
1294 def lock(cls, repo, user_id, lock_time=None):
1292 if lock_time is not None:
1295 if lock_time is not None:
1293 lock_time = time.time()
1296 lock_time = time.time()
1294 repo.locked = [user_id, lock_time]
1297 repo.locked = [user_id, lock_time]
1295 Session().commit()
1298 Session().commit()
1296
1299
1297 @classmethod
1300 @classmethod
1298 def unlock(cls, repo):
1301 def unlock(cls, repo):
1299 repo.locked = None
1302 repo.locked = None
1300 Session().commit()
1303 Session().commit()
1301
1304
1302 @classmethod
1305 @classmethod
1303 def getlock(cls, repo):
1306 def getlock(cls, repo):
1304 return repo.locked
1307 return repo.locked
1305
1308
1306 @property
1309 @property
1307 def last_db_change(self):
1310 def last_db_change(self):
1308 return self.updated_on
1311 return self.updated_on
1309
1312
1310 @property
1313 @property
1311 def clone_uri_hidden(self):
1314 def clone_uri_hidden(self):
1312 clone_uri = self.clone_uri
1315 clone_uri = self.clone_uri
1313 if clone_uri:
1316 if clone_uri:
1314 import urlobject
1317 import urlobject
1315 url_obj = urlobject.URLObject(self.clone_uri)
1318 url_obj = urlobject.URLObject(self.clone_uri)
1316 if url_obj.password:
1319 if url_obj.password:
1317 clone_uri = url_obj.with_password('*****')
1320 clone_uri = url_obj.with_password('*****')
1318 return clone_uri
1321 return clone_uri
1319
1322
1320 def clone_url(self, **override):
1323 def clone_url(self, **override):
1321 import kallithea.lib.helpers as h
1324 import kallithea.lib.helpers as h
1322 qualified_home_url = h.canonical_url('home')
1325 qualified_home_url = h.canonical_url('home')
1323
1326
1324 uri_tmpl = None
1327 uri_tmpl = None
1325 if 'with_id' in override:
1328 if 'with_id' in override:
1326 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1329 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1327 del override['with_id']
1330 del override['with_id']
1328
1331
1329 if 'uri_tmpl' in override:
1332 if 'uri_tmpl' in override:
1330 uri_tmpl = override['uri_tmpl']
1333 uri_tmpl = override['uri_tmpl']
1331 del override['uri_tmpl']
1334 del override['uri_tmpl']
1332
1335
1333 # we didn't override our tmpl from **overrides
1336 # we didn't override our tmpl from **overrides
1334 if not uri_tmpl:
1337 if not uri_tmpl:
1335 uri_tmpl = self.DEFAULT_CLONE_URI
1338 uri_tmpl = self.DEFAULT_CLONE_URI
1336 try:
1339 try:
1337 from tg import tmpl_context as c
1340 from tg import tmpl_context as c
1338 uri_tmpl = c.clone_uri_tmpl
1341 uri_tmpl = c.clone_uri_tmpl
1339 except AttributeError:
1342 except AttributeError:
1340 # in any case if we call this outside of request context,
1343 # in any case if we call this outside of request context,
1341 # ie, not having tmpl_context set up
1344 # ie, not having tmpl_context set up
1342 pass
1345 pass
1343
1346
1344 return get_clone_url(uri_tmpl=uri_tmpl,
1347 return get_clone_url(uri_tmpl=uri_tmpl,
1345 qualified_home_url=qualified_home_url,
1348 qualified_home_url=qualified_home_url,
1346 repo_name=self.repo_name,
1349 repo_name=self.repo_name,
1347 repo_id=self.repo_id, **override)
1350 repo_id=self.repo_id, **override)
1348
1351
1349 def set_state(self, state):
1352 def set_state(self, state):
1350 self.repo_state = state
1353 self.repo_state = state
1351
1354
1352 #==========================================================================
1355 #==========================================================================
1353 # SCM PROPERTIES
1356 # SCM PROPERTIES
1354 #==========================================================================
1357 #==========================================================================
1355
1358
1356 def get_changeset(self, rev=None):
1359 def get_changeset(self, rev=None):
1357 return get_changeset_safe(self.scm_instance, rev)
1360 return get_changeset_safe(self.scm_instance, rev)
1358
1361
1359 def get_landing_changeset(self):
1362 def get_landing_changeset(self):
1360 """
1363 """
1361 Returns landing changeset, or if that doesn't exist returns the tip
1364 Returns landing changeset, or if that doesn't exist returns the tip
1362 """
1365 """
1363 _rev_type, _rev = self.landing_rev
1366 _rev_type, _rev = self.landing_rev
1364 cs = self.get_changeset(_rev)
1367 cs = self.get_changeset(_rev)
1365 if isinstance(cs, EmptyChangeset):
1368 if isinstance(cs, EmptyChangeset):
1366 return self.get_changeset()
1369 return self.get_changeset()
1367 return cs
1370 return cs
1368
1371
1369 def update_changeset_cache(self, cs_cache=None):
1372 def update_changeset_cache(self, cs_cache=None):
1370 """
1373 """
1371 Update cache of last changeset for repository, keys should be::
1374 Update cache of last changeset for repository, keys should be::
1372
1375
1373 short_id
1376 short_id
1374 raw_id
1377 raw_id
1375 revision
1378 revision
1376 message
1379 message
1377 date
1380 date
1378 author
1381 author
1379
1382
1380 :param cs_cache:
1383 :param cs_cache:
1381 """
1384 """
1382 from kallithea.lib.vcs.backends.base import BaseChangeset
1385 from kallithea.lib.vcs.backends.base import BaseChangeset
1383 if cs_cache is None:
1386 if cs_cache is None:
1384 cs_cache = EmptyChangeset()
1387 cs_cache = EmptyChangeset()
1385 # use no-cache version here
1388 # use no-cache version here
1386 scm_repo = self.scm_instance_no_cache()
1389 scm_repo = self.scm_instance_no_cache()
1387 if scm_repo:
1390 if scm_repo:
1388 cs_cache = scm_repo.get_changeset()
1391 cs_cache = scm_repo.get_changeset()
1389
1392
1390 if isinstance(cs_cache, BaseChangeset):
1393 if isinstance(cs_cache, BaseChangeset):
1391 cs_cache = cs_cache.__json__()
1394 cs_cache = cs_cache.__json__()
1392
1395
1393 if (not self.changeset_cache or cs_cache['raw_id'] != self.changeset_cache['raw_id']):
1396 if (not self.changeset_cache or cs_cache['raw_id'] != self.changeset_cache['raw_id']):
1394 _default = datetime.datetime.fromtimestamp(0)
1397 _default = datetime.datetime.fromtimestamp(0)
1395 last_change = cs_cache.get('date') or _default
1398 last_change = cs_cache.get('date') or _default
1396 log.debug('updated repo %s with new cs cache %s',
1399 log.debug('updated repo %s with new cs cache %s',
1397 self.repo_name, cs_cache)
1400 self.repo_name, cs_cache)
1398 self.updated_on = last_change
1401 self.updated_on = last_change
1399 self.changeset_cache = cs_cache
1402 self.changeset_cache = cs_cache
1400 Session().commit()
1403 Session().commit()
1401 else:
1404 else:
1402 log.debug('changeset_cache for %s already up to date with %s',
1405 log.debug('changeset_cache for %s already up to date with %s',
1403 self.repo_name, cs_cache['raw_id'])
1406 self.repo_name, cs_cache['raw_id'])
1404
1407
1405 @property
1408 @property
1406 def tip(self):
1409 def tip(self):
1407 return self.get_changeset('tip')
1410 return self.get_changeset('tip')
1408
1411
1409 @property
1412 @property
1410 def author(self):
1413 def author(self):
1411 return self.tip.author
1414 return self.tip.author
1412
1415
1413 @property
1416 @property
1414 def last_change(self):
1417 def last_change(self):
1415 return self.scm_instance.last_change
1418 return self.scm_instance.last_change
1416
1419
1417 def get_comments(self, revisions=None):
1420 def get_comments(self, revisions=None):
1418 """
1421 """
1419 Returns comments for this repository grouped by revisions
1422 Returns comments for this repository grouped by revisions
1420
1423
1421 :param revisions: filter query by revisions only
1424 :param revisions: filter query by revisions only
1422 """
1425 """
1423 cmts = ChangesetComment.query() \
1426 cmts = ChangesetComment.query() \
1424 .filter(ChangesetComment.repo == self)
1427 .filter(ChangesetComment.repo == self)
1425 if revisions is not None:
1428 if revisions is not None:
1426 if not revisions:
1429 if not revisions:
1427 return {} # don't use sql 'in' on empty set
1430 return {} # don't use sql 'in' on empty set
1428 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1431 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1429 grouped = collections.defaultdict(list)
1432 grouped = collections.defaultdict(list)
1430 for cmt in cmts.all():
1433 for cmt in cmts.all():
1431 grouped[cmt.revision].append(cmt)
1434 grouped[cmt.revision].append(cmt)
1432 return grouped
1435 return grouped
1433
1436
1434 def statuses(self, revisions):
1437 def statuses(self, revisions):
1435 """
1438 """
1436 Returns statuses for this repository.
1439 Returns statuses for this repository.
1437 PRs without any votes do _not_ show up as unreviewed.
1440 PRs without any votes do _not_ show up as unreviewed.
1438
1441
1439 :param revisions: list of revisions to get statuses for
1442 :param revisions: list of revisions to get statuses for
1440 """
1443 """
1441 if not revisions:
1444 if not revisions:
1442 return {}
1445 return {}
1443
1446
1444 statuses = ChangesetStatus.query() \
1447 statuses = ChangesetStatus.query() \
1445 .filter(ChangesetStatus.repo == self) \
1448 .filter(ChangesetStatus.repo == self) \
1446 .filter(ChangesetStatus.version == 0) \
1449 .filter(ChangesetStatus.version == 0) \
1447 .filter(ChangesetStatus.revision.in_(revisions))
1450 .filter(ChangesetStatus.revision.in_(revisions))
1448
1451
1449 grouped = {}
1452 grouped = {}
1450 for stat in statuses.all():
1453 for stat in statuses.all():
1451 pr_id = pr_nice_id = pr_repo = None
1454 pr_id = pr_nice_id = pr_repo = None
1452 if stat.pull_request:
1455 if stat.pull_request:
1453 pr_id = stat.pull_request.pull_request_id
1456 pr_id = stat.pull_request.pull_request_id
1454 pr_nice_id = PullRequest.make_nice_id(pr_id)
1457 pr_nice_id = PullRequest.make_nice_id(pr_id)
1455 pr_repo = stat.pull_request.other_repo.repo_name
1458 pr_repo = stat.pull_request.other_repo.repo_name
1456 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1459 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1457 pr_id, pr_repo, pr_nice_id,
1460 pr_id, pr_repo, pr_nice_id,
1458 stat.author]
1461 stat.author]
1459 return grouped
1462 return grouped
1460
1463
1461 def _repo_size(self):
1464 def _repo_size(self):
1462 from kallithea.lib import helpers as h
1465 from kallithea.lib import helpers as h
1463 log.debug('calculating repository size...')
1466 log.debug('calculating repository size...')
1464 return h.format_byte_size(self.scm_instance.size)
1467 return h.format_byte_size(self.scm_instance.size)
1465
1468
1466 #==========================================================================
1469 #==========================================================================
1467 # SCM CACHE INSTANCE
1470 # SCM CACHE INSTANCE
1468 #==========================================================================
1471 #==========================================================================
1469
1472
1470 def set_invalidate(self):
1473 def set_invalidate(self):
1471 """
1474 """
1472 Mark caches of this repo as invalid.
1475 Mark caches of this repo as invalid.
1473 """
1476 """
1474 CacheInvalidation.set_invalidate(self.repo_name)
1477 CacheInvalidation.set_invalidate(self.repo_name)
1475
1478
1476 _scm_instance = None
1479 _scm_instance = None
1477
1480
1478 @property
1481 @property
1479 def scm_instance(self):
1482 def scm_instance(self):
1480 if self._scm_instance is None:
1483 if self._scm_instance is None:
1481 self._scm_instance = self.scm_instance_cached()
1484 self._scm_instance = self.scm_instance_cached()
1482 return self._scm_instance
1485 return self._scm_instance
1483
1486
1484 def scm_instance_cached(self, valid_cache_keys=None):
1487 def scm_instance_cached(self, valid_cache_keys=None):
1485 @cache_region('long_term', 'scm_instance_cached')
1488 @cache_region('long_term', 'scm_instance_cached')
1486 def _c(repo_name): # repo_name is just for the cache key
1489 def _c(repo_name): # repo_name is just for the cache key
1487 log.debug('Creating new %s scm_instance and populating cache', repo_name)
1490 log.debug('Creating new %s scm_instance and populating cache', repo_name)
1488 return self.scm_instance_no_cache()
1491 return self.scm_instance_no_cache()
1489 rn = self.repo_name
1492 rn = self.repo_name
1490
1493
1491 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1494 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1492 if not valid:
1495 if not valid:
1493 log.debug('Cache for %s invalidated, getting new object', rn)
1496 log.debug('Cache for %s invalidated, getting new object', rn)
1494 region_invalidate(_c, None, 'scm_instance_cached', rn)
1497 region_invalidate(_c, None, 'scm_instance_cached', rn)
1495 else:
1498 else:
1496 log.debug('Trying to get scm_instance of %s from cache', rn)
1499 log.debug('Trying to get scm_instance of %s from cache', rn)
1497 return _c(rn)
1500 return _c(rn)
1498
1501
1499 def scm_instance_no_cache(self):
1502 def scm_instance_no_cache(self):
1500 repo_full_path = safe_str(self.repo_full_path)
1503 repo_full_path = safe_str(self.repo_full_path)
1501 alias = get_scm(repo_full_path)[0]
1504 alias = get_scm(repo_full_path)[0]
1502 log.debug('Creating instance of %s repository from %s',
1505 log.debug('Creating instance of %s repository from %s',
1503 alias, self.repo_full_path)
1506 alias, self.repo_full_path)
1504 backend = get_backend(alias)
1507 backend = get_backend(alias)
1505
1508
1506 if alias == 'hg':
1509 if alias == 'hg':
1507 repo = backend(repo_full_path, create=False,
1510 repo = backend(repo_full_path, create=False,
1508 baseui=self._ui)
1511 baseui=self._ui)
1509 else:
1512 else:
1510 repo = backend(repo_full_path, create=False)
1513 repo = backend(repo_full_path, create=False)
1511
1514
1512 return repo
1515 return repo
1513
1516
1514 def __json__(self):
1517 def __json__(self):
1515 return dict(landing_rev = self.landing_rev)
1518 return dict(landing_rev = self.landing_rev)
1516
1519
1517 class RepoGroup(Base, BaseDbModel):
1520 class RepoGroup(Base, BaseDbModel):
1518 __tablename__ = 'groups'
1521 __tablename__ = 'groups'
1519 __table_args__ = (
1522 __table_args__ = (
1520 CheckConstraint('group_id != group_parent_id', name='ck_groups_no_self_parent'),
1523 CheckConstraint('group_id != group_parent_id', name='ck_groups_no_self_parent'),
1521 _table_args_default_dict,
1524 _table_args_default_dict,
1522 )
1525 )
1523 __mapper_args__ = {'order_by': 'group_name'} # TODO: Deprecated as of SQLAlchemy 1.1.
1526 __mapper_args__ = {'order_by': 'group_name'} # TODO: Deprecated as of SQLAlchemy 1.1.
1524
1527
1525 SEP = ' &raquo; '
1528 SEP = ' &raquo; '
1526
1529
1527 group_id = Column(Integer(), primary_key=True)
1530 group_id = Column(Integer(), primary_key=True)
1528 group_name = Column(Unicode(255), nullable=False, unique=True) # full path
1531 group_name = Column(Unicode(255), nullable=False, unique=True) # full path
1529 parent_group_id = Column('group_parent_id', Integer(), ForeignKey('groups.group_id'), nullable=True)
1532 parent_group_id = Column('group_parent_id', Integer(), ForeignKey('groups.group_id'), nullable=True)
1530 group_description = Column(Unicode(10000), nullable=False)
1533 group_description = Column(Unicode(10000), nullable=False)
1531 enable_locking = Column(Boolean(), nullable=False, default=False)
1534 enable_locking = Column(Boolean(), nullable=False, default=False)
1532 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1535 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1533 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1536 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1534
1537
1535 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1538 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1536 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1539 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1537 parent_group = relationship('RepoGroup', remote_side=group_id)
1540 parent_group = relationship('RepoGroup', remote_side=group_id)
1538 owner = relationship('User')
1541 owner = relationship('User')
1539
1542
1540 @classmethod
1543 @classmethod
1541 def query(cls, sorted=False):
1544 def query(cls, sorted=False):
1542 """Add RepoGroup-specific helpers for common query constructs.
1545 """Add RepoGroup-specific helpers for common query constructs.
1543
1546
1544 sorted: if True, apply the default ordering (name, case insensitive).
1547 sorted: if True, apply the default ordering (name, case insensitive).
1545 """
1548 """
1546 q = super(RepoGroup, cls).query()
1549 q = super(RepoGroup, cls).query()
1547
1550
1548 if sorted:
1551 if sorted:
1549 q = q.order_by(func.lower(RepoGroup.group_name))
1552 q = q.order_by(func.lower(RepoGroup.group_name))
1550
1553
1551 return q
1554 return q
1552
1555
1553 def __init__(self, group_name='', parent_group=None):
1556 def __init__(self, group_name='', parent_group=None):
1554 self.group_name = group_name
1557 self.group_name = group_name
1555 self.parent_group = parent_group
1558 self.parent_group = parent_group
1556
1559
1557 def __unicode__(self):
1560 def __unicode__(self):
1558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1561 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1559 self.group_name)
1562 self.group_name)
1560
1563
1561 @classmethod
1564 @classmethod
1562 def _generate_choice(cls, repo_group):
1565 def _generate_choice(cls, repo_group):
1563 """Return tuple with group_id and name as html literal"""
1566 """Return tuple with group_id and name as html literal"""
1564 from webhelpers.html import literal
1567 from webhelpers.html import literal
1565 if repo_group is None:
1568 if repo_group is None:
1566 return (-1, u'-- %s --' % _('top level'))
1569 return (-1, u'-- %s --' % _('top level'))
1567 return repo_group.group_id, literal(cls.SEP.join(repo_group.full_path_splitted))
1570 return repo_group.group_id, literal(cls.SEP.join(repo_group.full_path_splitted))
1568
1571
1569 @classmethod
1572 @classmethod
1570 def groups_choices(cls, groups):
1573 def groups_choices(cls, groups):
1571 """Return tuples with group_id and name as html literal."""
1574 """Return tuples with group_id and name as html literal."""
1572 return sorted((cls._generate_choice(g) for g in groups),
1575 return sorted((cls._generate_choice(g) for g in groups),
1573 key=lambda c: c[1].split(cls.SEP))
1576 key=lambda c: c[1].split(cls.SEP))
1574
1577
1575 @classmethod
1578 @classmethod
1576 def url_sep(cls):
1579 def url_sep(cls):
1577 return URL_SEP
1580 return URL_SEP
1578
1581
1579 @classmethod
1582 @classmethod
1580 def guess_instance(cls, value):
1583 def guess_instance(cls, value):
1581 return super(RepoGroup, cls).guess_instance(value, RepoGroup.get_by_group_name)
1584 return super(RepoGroup, cls).guess_instance(value, RepoGroup.get_by_group_name)
1582
1585
1583 @classmethod
1586 @classmethod
1584 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1587 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1585 group_name = group_name.rstrip('/')
1588 group_name = group_name.rstrip('/')
1586 if case_insensitive:
1589 if case_insensitive:
1587 gr = cls.query() \
1590 gr = cls.query() \
1588 .filter(func.lower(cls.group_name) == func.lower(group_name))
1591 .filter(func.lower(cls.group_name) == func.lower(group_name))
1589 else:
1592 else:
1590 gr = cls.query() \
1593 gr = cls.query() \
1591 .filter(cls.group_name == group_name)
1594 .filter(cls.group_name == group_name)
1592 if cache:
1595 if cache:
1593 gr = gr.options(FromCache(
1596 gr = gr.options(FromCache(
1594 "sql_cache_short",
1597 "sql_cache_short",
1595 "get_group_%s" % _hash_key(group_name)
1598 "get_group_%s" % _hash_key(group_name)
1596 )
1599 )
1597 )
1600 )
1598 return gr.scalar()
1601 return gr.scalar()
1599
1602
1600 @property
1603 @property
1601 def parents(self):
1604 def parents(self):
1602 groups = []
1605 groups = []
1603 group = self.parent_group
1606 group = self.parent_group
1604 while group is not None:
1607 while group is not None:
1605 groups.append(group)
1608 groups.append(group)
1606 group = group.parent_group
1609 group = group.parent_group
1607 assert group not in groups, group # avoid recursion on bad db content
1610 assert group not in groups, group # avoid recursion on bad db content
1608 groups.reverse()
1611 groups.reverse()
1609 return groups
1612 return groups
1610
1613
1611 @property
1614 @property
1612 def children(self):
1615 def children(self):
1613 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1616 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1614
1617
1615 @property
1618 @property
1616 def name(self):
1619 def name(self):
1617 return self.group_name.split(RepoGroup.url_sep())[-1]
1620 return self.group_name.split(RepoGroup.url_sep())[-1]
1618
1621
1619 @property
1622 @property
1620 def full_path(self):
1623 def full_path(self):
1621 return self.group_name
1624 return self.group_name
1622
1625
1623 @property
1626 @property
1624 def full_path_splitted(self):
1627 def full_path_splitted(self):
1625 return self.group_name.split(RepoGroup.url_sep())
1628 return self.group_name.split(RepoGroup.url_sep())
1626
1629
1627 @property
1630 @property
1628 def repositories(self):
1631 def repositories(self):
1629 return Repository.query(sorted=True).filter_by(group=self)
1632 return Repository.query(sorted=True).filter_by(group=self)
1630
1633
1631 @property
1634 @property
1632 def repositories_recursive_count(self):
1635 def repositories_recursive_count(self):
1633 cnt = self.repositories.count()
1636 cnt = self.repositories.count()
1634
1637
1635 def children_count(group):
1638 def children_count(group):
1636 cnt = 0
1639 cnt = 0
1637 for child in group.children:
1640 for child in group.children:
1638 cnt += child.repositories.count()
1641 cnt += child.repositories.count()
1639 cnt += children_count(child)
1642 cnt += children_count(child)
1640 return cnt
1643 return cnt
1641
1644
1642 return cnt + children_count(self)
1645 return cnt + children_count(self)
1643
1646
1644 def _recursive_objects(self, include_repos=True):
1647 def _recursive_objects(self, include_repos=True):
1645 all_ = []
1648 all_ = []
1646
1649
1647 def _get_members(root_gr):
1650 def _get_members(root_gr):
1648 if include_repos:
1651 if include_repos:
1649 for r in root_gr.repositories:
1652 for r in root_gr.repositories:
1650 all_.append(r)
1653 all_.append(r)
1651 childs = root_gr.children.all()
1654 childs = root_gr.children.all()
1652 if childs:
1655 if childs:
1653 for gr in childs:
1656 for gr in childs:
1654 all_.append(gr)
1657 all_.append(gr)
1655 _get_members(gr)
1658 _get_members(gr)
1656
1659
1657 _get_members(self)
1660 _get_members(self)
1658 return [self] + all_
1661 return [self] + all_
1659
1662
1660 def recursive_groups_and_repos(self):
1663 def recursive_groups_and_repos(self):
1661 """
1664 """
1662 Recursive return all groups, with repositories in those groups
1665 Recursive return all groups, with repositories in those groups
1663 """
1666 """
1664 return self._recursive_objects()
1667 return self._recursive_objects()
1665
1668
1666 def recursive_groups(self):
1669 def recursive_groups(self):
1667 """
1670 """
1668 Returns all children groups for this group including children of children
1671 Returns all children groups for this group including children of children
1669 """
1672 """
1670 return self._recursive_objects(include_repos=False)
1673 return self._recursive_objects(include_repos=False)
1671
1674
1672 def get_new_name(self, group_name):
1675 def get_new_name(self, group_name):
1673 """
1676 """
1674 returns new full group name based on parent and new name
1677 returns new full group name based on parent and new name
1675
1678
1676 :param group_name:
1679 :param group_name:
1677 """
1680 """
1678 path_prefix = (self.parent_group.full_path_splitted if
1681 path_prefix = (self.parent_group.full_path_splitted if
1679 self.parent_group else [])
1682 self.parent_group else [])
1680 return RepoGroup.url_sep().join(path_prefix + [group_name])
1683 return RepoGroup.url_sep().join(path_prefix + [group_name])
1681
1684
1682 def get_api_data(self):
1685 def get_api_data(self):
1683 """
1686 """
1684 Common function for generating api data
1687 Common function for generating api data
1685
1688
1686 """
1689 """
1687 group = self
1690 group = self
1688 data = dict(
1691 data = dict(
1689 group_id=group.group_id,
1692 group_id=group.group_id,
1690 group_name=group.group_name,
1693 group_name=group.group_name,
1691 group_description=group.group_description,
1694 group_description=group.group_description,
1692 parent_group=group.parent_group.group_name if group.parent_group else None,
1695 parent_group=group.parent_group.group_name if group.parent_group else None,
1693 repositories=[x.repo_name for x in group.repositories],
1696 repositories=[x.repo_name for x in group.repositories],
1694 owner=group.owner.username
1697 owner=group.owner.username
1695 )
1698 )
1696 return data
1699 return data
1697
1700
1698
1701
1699 class Permission(Base, BaseDbModel):
1702 class Permission(Base, BaseDbModel):
1700 __tablename__ = 'permissions'
1703 __tablename__ = 'permissions'
1701 __table_args__ = (
1704 __table_args__ = (
1702 Index('p_perm_name_idx', 'permission_name'),
1705 Index('p_perm_name_idx', 'permission_name'),
1703 _table_args_default_dict,
1706 _table_args_default_dict,
1704 )
1707 )
1705
1708
1706 PERMS = [
1709 PERMS = [
1707 ('hg.admin', _('Kallithea Administrator')),
1710 ('hg.admin', _('Kallithea Administrator')),
1708
1711
1709 ('repository.none', _('Default user has no access to new repositories')),
1712 ('repository.none', _('Default user has no access to new repositories')),
1710 ('repository.read', _('Default user has read access to new repositories')),
1713 ('repository.read', _('Default user has read access to new repositories')),
1711 ('repository.write', _('Default user has write access to new repositories')),
1714 ('repository.write', _('Default user has write access to new repositories')),
1712 ('repository.admin', _('Default user has admin access to new repositories')),
1715 ('repository.admin', _('Default user has admin access to new repositories')),
1713
1716
1714 ('group.none', _('Default user has no access to new repository groups')),
1717 ('group.none', _('Default user has no access to new repository groups')),
1715 ('group.read', _('Default user has read access to new repository groups')),
1718 ('group.read', _('Default user has read access to new repository groups')),
1716 ('group.write', _('Default user has write access to new repository groups')),
1719 ('group.write', _('Default user has write access to new repository groups')),
1717 ('group.admin', _('Default user has admin access to new repository groups')),
1720 ('group.admin', _('Default user has admin access to new repository groups')),
1718
1721
1719 ('usergroup.none', _('Default user has no access to new user groups')),
1722 ('usergroup.none', _('Default user has no access to new user groups')),
1720 ('usergroup.read', _('Default user has read access to new user groups')),
1723 ('usergroup.read', _('Default user has read access to new user groups')),
1721 ('usergroup.write', _('Default user has write access to new user groups')),
1724 ('usergroup.write', _('Default user has write access to new user groups')),
1722 ('usergroup.admin', _('Default user has admin access to new user groups')),
1725 ('usergroup.admin', _('Default user has admin access to new user groups')),
1723
1726
1724 ('hg.repogroup.create.false', _('Only admins can create repository groups')),
1727 ('hg.repogroup.create.false', _('Only admins can create repository groups')),
1725 ('hg.repogroup.create.true', _('Non-admins can create repository groups')),
1728 ('hg.repogroup.create.true', _('Non-admins can create repository groups')),
1726
1729
1727 ('hg.usergroup.create.false', _('Only admins can create user groups')),
1730 ('hg.usergroup.create.false', _('Only admins can create user groups')),
1728 ('hg.usergroup.create.true', _('Non-admins can create user groups')),
1731 ('hg.usergroup.create.true', _('Non-admins can create user groups')),
1729
1732
1730 ('hg.create.none', _('Only admins can create top level repositories')),
1733 ('hg.create.none', _('Only admins can create top level repositories')),
1731 ('hg.create.repository', _('Non-admins can create top level repositories')),
1734 ('hg.create.repository', _('Non-admins can create top level repositories')),
1732
1735
1733 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
1736 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
1734 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
1737 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
1735
1738
1736 ('hg.fork.none', _('Only admins can fork repositories')),
1739 ('hg.fork.none', _('Only admins can fork repositories')),
1737 ('hg.fork.repository', _('Non-admins can fork repositories')),
1740 ('hg.fork.repository', _('Non-admins can fork repositories')),
1738
1741
1739 ('hg.register.none', _('Registration disabled')),
1742 ('hg.register.none', _('Registration disabled')),
1740 ('hg.register.manual_activate', _('User registration with manual account activation')),
1743 ('hg.register.manual_activate', _('User registration with manual account activation')),
1741 ('hg.register.auto_activate', _('User registration with automatic account activation')),
1744 ('hg.register.auto_activate', _('User registration with automatic account activation')),
1742
1745
1743 ('hg.extern_activate.manual', _('Manual activation of external account')),
1746 ('hg.extern_activate.manual', _('Manual activation of external account')),
1744 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1747 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1745 ]
1748 ]
1746
1749
1747 #definition of system default permissions for DEFAULT user
1750 #definition of system default permissions for DEFAULT user
1748 DEFAULT_USER_PERMISSIONS = [
1751 DEFAULT_USER_PERMISSIONS = [
1749 'repository.read',
1752 'repository.read',
1750 'group.read',
1753 'group.read',
1751 'usergroup.read',
1754 'usergroup.read',
1752 'hg.create.repository',
1755 'hg.create.repository',
1753 'hg.create.write_on_repogroup.true',
1756 'hg.create.write_on_repogroup.true',
1754 'hg.fork.repository',
1757 'hg.fork.repository',
1755 'hg.register.manual_activate',
1758 'hg.register.manual_activate',
1756 'hg.extern_activate.auto',
1759 'hg.extern_activate.auto',
1757 ]
1760 ]
1758
1761
1759 # defines which permissions are more important higher the more important
1762 # defines which permissions are more important higher the more important
1760 # Weight defines which permissions are more important.
1763 # Weight defines which permissions are more important.
1761 # The higher number the more important.
1764 # The higher number the more important.
1762 PERM_WEIGHTS = {
1765 PERM_WEIGHTS = {
1763 'repository.none': 0,
1766 'repository.none': 0,
1764 'repository.read': 1,
1767 'repository.read': 1,
1765 'repository.write': 3,
1768 'repository.write': 3,
1766 'repository.admin': 4,
1769 'repository.admin': 4,
1767
1770
1768 'group.none': 0,
1771 'group.none': 0,
1769 'group.read': 1,
1772 'group.read': 1,
1770 'group.write': 3,
1773 'group.write': 3,
1771 'group.admin': 4,
1774 'group.admin': 4,
1772
1775
1773 'usergroup.none': 0,
1776 'usergroup.none': 0,
1774 'usergroup.read': 1,
1777 'usergroup.read': 1,
1775 'usergroup.write': 3,
1778 'usergroup.write': 3,
1776 'usergroup.admin': 4,
1779 'usergroup.admin': 4,
1777
1780
1778 'hg.repogroup.create.false': 0,
1781 'hg.repogroup.create.false': 0,
1779 'hg.repogroup.create.true': 1,
1782 'hg.repogroup.create.true': 1,
1780
1783
1781 'hg.usergroup.create.false': 0,
1784 'hg.usergroup.create.false': 0,
1782 'hg.usergroup.create.true': 1,
1785 'hg.usergroup.create.true': 1,
1783
1786
1784 'hg.fork.none': 0,
1787 'hg.fork.none': 0,
1785 'hg.fork.repository': 1,
1788 'hg.fork.repository': 1,
1786
1789
1787 'hg.create.none': 0,
1790 'hg.create.none': 0,
1788 'hg.create.repository': 1
1791 'hg.create.repository': 1
1789 }
1792 }
1790
1793
1791 permission_id = Column(Integer(), primary_key=True)
1794 permission_id = Column(Integer(), primary_key=True)
1792 permission_name = Column(String(255), nullable=False)
1795 permission_name = Column(String(255), nullable=False)
1793
1796
1794 def __unicode__(self):
1797 def __unicode__(self):
1795 return u"<%s('%s:%s')>" % (
1798 return u"<%s('%s:%s')>" % (
1796 self.__class__.__name__, self.permission_id, self.permission_name
1799 self.__class__.__name__, self.permission_id, self.permission_name
1797 )
1800 )
1798
1801
1799 @classmethod
1802 @classmethod
1800 def guess_instance(cls, value):
1803 def guess_instance(cls, value):
1801 return super(Permission, cls).guess_instance(value, Permission.get_by_key)
1804 return super(Permission, cls).guess_instance(value, Permission.get_by_key)
1802
1805
1803 @classmethod
1806 @classmethod
1804 def get_by_key(cls, key):
1807 def get_by_key(cls, key):
1805 return cls.query().filter(cls.permission_name == key).scalar()
1808 return cls.query().filter(cls.permission_name == key).scalar()
1806
1809
1807 @classmethod
1810 @classmethod
1808 def get_default_perms(cls, default_user_id):
1811 def get_default_perms(cls, default_user_id):
1809 q = Session().query(UserRepoToPerm, Repository, cls) \
1812 q = Session().query(UserRepoToPerm, Repository, cls) \
1810 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id)) \
1813 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id)) \
1811 .join((cls, UserRepoToPerm.permission_id == cls.permission_id)) \
1814 .join((cls, UserRepoToPerm.permission_id == cls.permission_id)) \
1812 .filter(UserRepoToPerm.user_id == default_user_id)
1815 .filter(UserRepoToPerm.user_id == default_user_id)
1813
1816
1814 return q.all()
1817 return q.all()
1815
1818
1816 @classmethod
1819 @classmethod
1817 def get_default_group_perms(cls, default_user_id):
1820 def get_default_group_perms(cls, default_user_id):
1818 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls) \
1821 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls) \
1819 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id)) \
1822 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id)) \
1820 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id)) \
1823 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id)) \
1821 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1824 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1822
1825
1823 return q.all()
1826 return q.all()
1824
1827
1825 @classmethod
1828 @classmethod
1826 def get_default_user_group_perms(cls, default_user_id):
1829 def get_default_user_group_perms(cls, default_user_id):
1827 q = Session().query(UserUserGroupToPerm, UserGroup, cls) \
1830 q = Session().query(UserUserGroupToPerm, UserGroup, cls) \
1828 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id)) \
1831 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id)) \
1829 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id)) \
1832 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id)) \
1830 .filter(UserUserGroupToPerm.user_id == default_user_id)
1833 .filter(UserUserGroupToPerm.user_id == default_user_id)
1831
1834
1832 return q.all()
1835 return q.all()
1833
1836
1834
1837
1835 class UserRepoToPerm(Base, BaseDbModel):
1838 class UserRepoToPerm(Base, BaseDbModel):
1836 __tablename__ = 'repo_to_perm'
1839 __tablename__ = 'repo_to_perm'
1837 __table_args__ = (
1840 __table_args__ = (
1838 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1841 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1839 _table_args_default_dict,
1842 _table_args_default_dict,
1840 )
1843 )
1841
1844
1842 repo_to_perm_id = Column(Integer(), primary_key=True)
1845 repo_to_perm_id = Column(Integer(), primary_key=True)
1843 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1846 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1844 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1847 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1845 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1848 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1846
1849
1847 user = relationship('User')
1850 user = relationship('User')
1848 repository = relationship('Repository')
1851 repository = relationship('Repository')
1849 permission = relationship('Permission')
1852 permission = relationship('Permission')
1850
1853
1851 @classmethod
1854 @classmethod
1852 def create(cls, user, repository, permission):
1855 def create(cls, user, repository, permission):
1853 n = cls()
1856 n = cls()
1854 n.user = user
1857 n.user = user
1855 n.repository = repository
1858 n.repository = repository
1856 n.permission = permission
1859 n.permission = permission
1857 Session().add(n)
1860 Session().add(n)
1858 return n
1861 return n
1859
1862
1860 def __unicode__(self):
1863 def __unicode__(self):
1861 return u'<%s => %s >' % (self.user, self.repository)
1864 return u'<%s => %s >' % (self.user, self.repository)
1862
1865
1863
1866
1864 class UserUserGroupToPerm(Base, BaseDbModel):
1867 class UserUserGroupToPerm(Base, BaseDbModel):
1865 __tablename__ = 'user_user_group_to_perm'
1868 __tablename__ = 'user_user_group_to_perm'
1866 __table_args__ = (
1869 __table_args__ = (
1867 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1870 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1868 _table_args_default_dict,
1871 _table_args_default_dict,
1869 )
1872 )
1870
1873
1871 user_user_group_to_perm_id = Column(Integer(), primary_key=True)
1874 user_user_group_to_perm_id = Column(Integer(), primary_key=True)
1872 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1875 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1873 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1876 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1874 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1877 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1875
1878
1876 user = relationship('User')
1879 user = relationship('User')
1877 user_group = relationship('UserGroup')
1880 user_group = relationship('UserGroup')
1878 permission = relationship('Permission')
1881 permission = relationship('Permission')
1879
1882
1880 @classmethod
1883 @classmethod
1881 def create(cls, user, user_group, permission):
1884 def create(cls, user, user_group, permission):
1882 n = cls()
1885 n = cls()
1883 n.user = user
1886 n.user = user
1884 n.user_group = user_group
1887 n.user_group = user_group
1885 n.permission = permission
1888 n.permission = permission
1886 Session().add(n)
1889 Session().add(n)
1887 return n
1890 return n
1888
1891
1889 def __unicode__(self):
1892 def __unicode__(self):
1890 return u'<%s => %s >' % (self.user, self.user_group)
1893 return u'<%s => %s >' % (self.user, self.user_group)
1891
1894
1892
1895
1893 class UserToPerm(Base, BaseDbModel):
1896 class UserToPerm(Base, BaseDbModel):
1894 __tablename__ = 'user_to_perm'
1897 __tablename__ = 'user_to_perm'
1895 __table_args__ = (
1898 __table_args__ = (
1896 UniqueConstraint('user_id', 'permission_id'),
1899 UniqueConstraint('user_id', 'permission_id'),
1897 _table_args_default_dict,
1900 _table_args_default_dict,
1898 )
1901 )
1899
1902
1900 user_to_perm_id = Column(Integer(), primary_key=True)
1903 user_to_perm_id = Column(Integer(), primary_key=True)
1901 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1904 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1902 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1905 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1903
1906
1904 user = relationship('User')
1907 user = relationship('User')
1905 permission = relationship('Permission')
1908 permission = relationship('Permission')
1906
1909
1907 def __unicode__(self):
1910 def __unicode__(self):
1908 return u'<%s => %s >' % (self.user, self.permission)
1911 return u'<%s => %s >' % (self.user, self.permission)
1909
1912
1910
1913
1911 class UserGroupRepoToPerm(Base, BaseDbModel):
1914 class UserGroupRepoToPerm(Base, BaseDbModel):
1912 __tablename__ = 'users_group_repo_to_perm'
1915 __tablename__ = 'users_group_repo_to_perm'
1913 __table_args__ = (
1916 __table_args__ = (
1914 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1917 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1915 _table_args_default_dict,
1918 _table_args_default_dict,
1916 )
1919 )
1917
1920
1918 users_group_to_perm_id = Column(Integer(), primary_key=True)
1921 users_group_to_perm_id = Column(Integer(), primary_key=True)
1919 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1922 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1920 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1923 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1921 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1924 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1922
1925
1923 users_group = relationship('UserGroup')
1926 users_group = relationship('UserGroup')
1924 permission = relationship('Permission')
1927 permission = relationship('Permission')
1925 repository = relationship('Repository')
1928 repository = relationship('Repository')
1926
1929
1927 @classmethod
1930 @classmethod
1928 def create(cls, users_group, repository, permission):
1931 def create(cls, users_group, repository, permission):
1929 n = cls()
1932 n = cls()
1930 n.users_group = users_group
1933 n.users_group = users_group
1931 n.repository = repository
1934 n.repository = repository
1932 n.permission = permission
1935 n.permission = permission
1933 Session().add(n)
1936 Session().add(n)
1934 return n
1937 return n
1935
1938
1936 def __unicode__(self):
1939 def __unicode__(self):
1937 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1940 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1938
1941
1939
1942
1940 class UserGroupUserGroupToPerm(Base, BaseDbModel):
1943 class UserGroupUserGroupToPerm(Base, BaseDbModel):
1941 __tablename__ = 'user_group_user_group_to_perm'
1944 __tablename__ = 'user_group_user_group_to_perm'
1942 __table_args__ = (
1945 __table_args__ = (
1943 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1946 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1944 CheckConstraint('target_user_group_id != user_group_id', name='ck_user_group_user_group_to_perm_no_self_target'),
1947 CheckConstraint('target_user_group_id != user_group_id', name='ck_user_group_user_group_to_perm_no_self_target'),
1945 _table_args_default_dict,
1948 _table_args_default_dict,
1946 )
1949 )
1947
1950
1948 user_group_user_group_to_perm_id = Column(Integer(), primary_key=True)
1951 user_group_user_group_to_perm_id = Column(Integer(), primary_key=True)
1949 target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1952 target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1950 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1953 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1951 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1954 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1952
1955
1953 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1956 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1954 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1957 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1955 permission = relationship('Permission')
1958 permission = relationship('Permission')
1956
1959
1957 @classmethod
1960 @classmethod
1958 def create(cls, target_user_group, user_group, permission):
1961 def create(cls, target_user_group, user_group, permission):
1959 n = cls()
1962 n = cls()
1960 n.target_user_group = target_user_group
1963 n.target_user_group = target_user_group
1961 n.user_group = user_group
1964 n.user_group = user_group
1962 n.permission = permission
1965 n.permission = permission
1963 Session().add(n)
1966 Session().add(n)
1964 return n
1967 return n
1965
1968
1966 def __unicode__(self):
1969 def __unicode__(self):
1967 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1970 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1968
1971
1969
1972
1970 class UserGroupToPerm(Base, BaseDbModel):
1973 class UserGroupToPerm(Base, BaseDbModel):
1971 __tablename__ = 'users_group_to_perm'
1974 __tablename__ = 'users_group_to_perm'
1972 __table_args__ = (
1975 __table_args__ = (
1973 UniqueConstraint('users_group_id', 'permission_id',),
1976 UniqueConstraint('users_group_id', 'permission_id',),
1974 _table_args_default_dict,
1977 _table_args_default_dict,
1975 )
1978 )
1976
1979
1977 users_group_to_perm_id = Column(Integer(), primary_key=True)
1980 users_group_to_perm_id = Column(Integer(), primary_key=True)
1978 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1981 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1979 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1982 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1980
1983
1981 users_group = relationship('UserGroup')
1984 users_group = relationship('UserGroup')
1982 permission = relationship('Permission')
1985 permission = relationship('Permission')
1983
1986
1984
1987
1985 class UserRepoGroupToPerm(Base, BaseDbModel):
1988 class UserRepoGroupToPerm(Base, BaseDbModel):
1986 __tablename__ = 'user_repo_group_to_perm'
1989 __tablename__ = 'user_repo_group_to_perm'
1987 __table_args__ = (
1990 __table_args__ = (
1988 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1991 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1989 _table_args_default_dict,
1992 _table_args_default_dict,
1990 )
1993 )
1991
1994
1992 group_to_perm_id = Column(Integer(), primary_key=True)
1995 group_to_perm_id = Column(Integer(), primary_key=True)
1993 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1996 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1994 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
1997 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
1995 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1998 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1996
1999
1997 user = relationship('User')
2000 user = relationship('User')
1998 group = relationship('RepoGroup')
2001 group = relationship('RepoGroup')
1999 permission = relationship('Permission')
2002 permission = relationship('Permission')
2000
2003
2001 @classmethod
2004 @classmethod
2002 def create(cls, user, repository_group, permission):
2005 def create(cls, user, repository_group, permission):
2003 n = cls()
2006 n = cls()
2004 n.user = user
2007 n.user = user
2005 n.group = repository_group
2008 n.group = repository_group
2006 n.permission = permission
2009 n.permission = permission
2007 Session().add(n)
2010 Session().add(n)
2008 return n
2011 return n
2009
2012
2010
2013
2011 class UserGroupRepoGroupToPerm(Base, BaseDbModel):
2014 class UserGroupRepoGroupToPerm(Base, BaseDbModel):
2012 __tablename__ = 'users_group_repo_group_to_perm'
2015 __tablename__ = 'users_group_repo_group_to_perm'
2013 __table_args__ = (
2016 __table_args__ = (
2014 UniqueConstraint('users_group_id', 'group_id'),
2017 UniqueConstraint('users_group_id', 'group_id'),
2015 _table_args_default_dict,
2018 _table_args_default_dict,
2016 )
2019 )
2017
2020
2018 users_group_repo_group_to_perm_id = Column(Integer(), primary_key=True)
2021 users_group_repo_group_to_perm_id = Column(Integer(), primary_key=True)
2019 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
2022 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
2020 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
2023 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
2021 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
2024 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
2022
2025
2023 users_group = relationship('UserGroup')
2026 users_group = relationship('UserGroup')
2024 permission = relationship('Permission')
2027 permission = relationship('Permission')
2025 group = relationship('RepoGroup')
2028 group = relationship('RepoGroup')
2026
2029
2027 @classmethod
2030 @classmethod
2028 def create(cls, user_group, repository_group, permission):
2031 def create(cls, user_group, repository_group, permission):
2029 n = cls()
2032 n = cls()
2030 n.users_group = user_group
2033 n.users_group = user_group
2031 n.group = repository_group
2034 n.group = repository_group
2032 n.permission = permission
2035 n.permission = permission
2033 Session().add(n)
2036 Session().add(n)
2034 return n
2037 return n
2035
2038
2036
2039
2037 class Statistics(Base, BaseDbModel):
2040 class Statistics(Base, BaseDbModel):
2038 __tablename__ = 'statistics'
2041 __tablename__ = 'statistics'
2039 __table_args__ = (
2042 __table_args__ = (
2040 _table_args_default_dict,
2043 _table_args_default_dict,
2041 )
2044 )
2042
2045
2043 stat_id = Column(Integer(), primary_key=True)
2046 stat_id = Column(Integer(), primary_key=True)
2044 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True)
2047 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True)
2045 stat_on_revision = Column(Integer(), nullable=False)
2048 stat_on_revision = Column(Integer(), nullable=False)
2046 commit_activity = Column(LargeBinary(1000000), nullable=False)#JSON data
2049 commit_activity = Column(LargeBinary(1000000), nullable=False)#JSON data
2047 commit_activity_combined = Column(LargeBinary(), nullable=False)#JSON data
2050 commit_activity_combined = Column(LargeBinary(), nullable=False)#JSON data
2048 languages = Column(LargeBinary(1000000), nullable=False)#JSON data
2051 languages = Column(LargeBinary(1000000), nullable=False)#JSON data
2049
2052
2050 repository = relationship('Repository', single_parent=True)
2053 repository = relationship('Repository', single_parent=True)
2051
2054
2052
2055
2053 class UserFollowing(Base, BaseDbModel):
2056 class UserFollowing(Base, BaseDbModel):
2054 __tablename__ = 'user_followings'
2057 __tablename__ = 'user_followings'
2055 __table_args__ = (
2058 __table_args__ = (
2056 UniqueConstraint('user_id', 'follows_repository_id', name='uq_user_followings_user_repo'),
2059 UniqueConstraint('user_id', 'follows_repository_id', name='uq_user_followings_user_repo'),
2057 UniqueConstraint('user_id', 'follows_user_id', name='uq_user_followings_user_user'),
2060 UniqueConstraint('user_id', 'follows_user_id', name='uq_user_followings_user_user'),
2058 _table_args_default_dict,
2061 _table_args_default_dict,
2059 )
2062 )
2060
2063
2061 user_following_id = Column(Integer(), primary_key=True)
2064 user_following_id = Column(Integer(), primary_key=True)
2062 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2065 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2063 follows_repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
2066 follows_repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
2064 follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
2067 follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
2065 follows_from = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2068 follows_from = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2066
2069
2067 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2070 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2068
2071
2069 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2072 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2070 follows_repository = relationship('Repository', order_by=lambda: func.lower(Repository.repo_name))
2073 follows_repository = relationship('Repository', order_by=lambda: func.lower(Repository.repo_name))
2071
2074
2072 @classmethod
2075 @classmethod
2073 def get_repo_followers(cls, repo_id):
2076 def get_repo_followers(cls, repo_id):
2074 return cls.query().filter(cls.follows_repository_id == repo_id)
2077 return cls.query().filter(cls.follows_repository_id == repo_id)
2075
2078
2076
2079
2077 class CacheInvalidation(Base, BaseDbModel):
2080 class CacheInvalidation(Base, BaseDbModel):
2078 __tablename__ = 'cache_invalidation'
2081 __tablename__ = 'cache_invalidation'
2079 __table_args__ = (
2082 __table_args__ = (
2080 Index('key_idx', 'cache_key'),
2083 Index('key_idx', 'cache_key'),
2081 _table_args_default_dict,
2084 _table_args_default_dict,
2082 )
2085 )
2083
2086
2084 # cache_id, not used
2087 # cache_id, not used
2085 cache_id = Column(Integer(), primary_key=True)
2088 cache_id = Column(Integer(), primary_key=True)
2086 # cache_key as created by _get_cache_key
2089 # cache_key as created by _get_cache_key
2087 cache_key = Column(Unicode(255), nullable=False, unique=True)
2090 cache_key = Column(Unicode(255), nullable=False, unique=True)
2088 # cache_args is a repo_name
2091 # cache_args is a repo_name
2089 cache_args = Column(Unicode(255), nullable=False)
2092 cache_args = Column(Unicode(255), nullable=False)
2090 # instance sets cache_active True when it is caching, other instances set
2093 # instance sets cache_active True when it is caching, other instances set
2091 # cache_active to False to indicate that this cache is invalid
2094 # cache_active to False to indicate that this cache is invalid
2092 cache_active = Column(Boolean(), nullable=False, default=False)
2095 cache_active = Column(Boolean(), nullable=False, default=False)
2093
2096
2094 def __init__(self, cache_key, repo_name=''):
2097 def __init__(self, cache_key, repo_name=''):
2095 self.cache_key = cache_key
2098 self.cache_key = cache_key
2096 self.cache_args = repo_name
2099 self.cache_args = repo_name
2097 self.cache_active = False
2100 self.cache_active = False
2098
2101
2099 def __unicode__(self):
2102 def __unicode__(self):
2100 return u"<%s('%s:%s[%s]')>" % (
2103 return u"<%s('%s:%s[%s]')>" % (
2101 self.__class__.__name__,
2104 self.__class__.__name__,
2102 self.cache_id, self.cache_key, self.cache_active)
2105 self.cache_id, self.cache_key, self.cache_active)
2103
2106
2104 def _cache_key_partition(self):
2107 def _cache_key_partition(self):
2105 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2108 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2106 return prefix, repo_name, suffix
2109 return prefix, repo_name, suffix
2107
2110
2108 def get_prefix(self):
2111 def get_prefix(self):
2109 """
2112 """
2110 get prefix that might have been used in _get_cache_key to
2113 get prefix that might have been used in _get_cache_key to
2111 generate self.cache_key. Only used for informational purposes
2114 generate self.cache_key. Only used for informational purposes
2112 in repo_edit.html.
2115 in repo_edit.html.
2113 """
2116 """
2114 # prefix, repo_name, suffix
2117 # prefix, repo_name, suffix
2115 return self._cache_key_partition()[0]
2118 return self._cache_key_partition()[0]
2116
2119
2117 def get_suffix(self):
2120 def get_suffix(self):
2118 """
2121 """
2119 get suffix that might have been used in _get_cache_key to
2122 get suffix that might have been used in _get_cache_key to
2120 generate self.cache_key. Only used for informational purposes
2123 generate self.cache_key. Only used for informational purposes
2121 in repo_edit.html.
2124 in repo_edit.html.
2122 """
2125 """
2123 # prefix, repo_name, suffix
2126 # prefix, repo_name, suffix
2124 return self._cache_key_partition()[2]
2127 return self._cache_key_partition()[2]
2125
2128
2126 @classmethod
2129 @classmethod
2127 def clear_cache(cls):
2130 def clear_cache(cls):
2128 """
2131 """
2129 Delete all cache keys from database.
2132 Delete all cache keys from database.
2130 Should only be run when all instances are down and all entries thus stale.
2133 Should only be run when all instances are down and all entries thus stale.
2131 """
2134 """
2132 cls.query().delete()
2135 cls.query().delete()
2133 Session().commit()
2136 Session().commit()
2134
2137
2135 @classmethod
2138 @classmethod
2136 def _get_cache_key(cls, key):
2139 def _get_cache_key(cls, key):
2137 """
2140 """
2138 Wrapper for generating a unique cache key for this instance and "key".
2141 Wrapper for generating a unique cache key for this instance and "key".
2139 key must / will start with a repo_name which will be stored in .cache_args .
2142 key must / will start with a repo_name which will be stored in .cache_args .
2140 """
2143 """
2141 import kallithea
2144 import kallithea
2142 prefix = kallithea.CONFIG.get('instance_id', '')
2145 prefix = kallithea.CONFIG.get('instance_id', '')
2143 return "%s%s" % (prefix, key)
2146 return "%s%s" % (prefix, key)
2144
2147
2145 @classmethod
2148 @classmethod
2146 def set_invalidate(cls, repo_name):
2149 def set_invalidate(cls, repo_name):
2147 """
2150 """
2148 Mark all caches of a repo as invalid in the database.
2151 Mark all caches of a repo as invalid in the database.
2149 """
2152 """
2150 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
2153 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
2151 log.debug('for repo %s got %s invalidation objects',
2154 log.debug('for repo %s got %s invalidation objects',
2152 safe_str(repo_name), inv_objs)
2155 safe_str(repo_name), inv_objs)
2153
2156
2154 for inv_obj in inv_objs:
2157 for inv_obj in inv_objs:
2155 log.debug('marking %s key for invalidation based on repo_name=%s',
2158 log.debug('marking %s key for invalidation based on repo_name=%s',
2156 inv_obj, safe_str(repo_name))
2159 inv_obj, safe_str(repo_name))
2157 Session().delete(inv_obj)
2160 Session().delete(inv_obj)
2158 Session().commit()
2161 Session().commit()
2159
2162
2160 @classmethod
2163 @classmethod
2161 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
2164 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
2162 """
2165 """
2163 Mark this cache key as active and currently cached.
2166 Mark this cache key as active and currently cached.
2164 Return True if the existing cache registration still was valid.
2167 Return True if the existing cache registration still was valid.
2165 Return False to indicate that it had been invalidated and caches should be refreshed.
2168 Return False to indicate that it had been invalidated and caches should be refreshed.
2166 """
2169 """
2167
2170
2168 key = (repo_name + '_' + kind) if kind else repo_name
2171 key = (repo_name + '_' + kind) if kind else repo_name
2169 cache_key = cls._get_cache_key(key)
2172 cache_key = cls._get_cache_key(key)
2170
2173
2171 if valid_cache_keys and cache_key in valid_cache_keys:
2174 if valid_cache_keys and cache_key in valid_cache_keys:
2172 return True
2175 return True
2173
2176
2174 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2177 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2175 if inv_obj is None:
2178 if inv_obj is None:
2176 inv_obj = cls(cache_key, repo_name)
2179 inv_obj = cls(cache_key, repo_name)
2177 Session().add(inv_obj)
2180 Session().add(inv_obj)
2178 elif inv_obj.cache_active:
2181 elif inv_obj.cache_active:
2179 return True
2182 return True
2180 inv_obj.cache_active = True
2183 inv_obj.cache_active = True
2181 try:
2184 try:
2182 Session().commit()
2185 Session().commit()
2183 except sqlalchemy.exc.IntegrityError:
2186 except sqlalchemy.exc.IntegrityError:
2184 log.error('commit of CacheInvalidation failed - retrying')
2187 log.error('commit of CacheInvalidation failed - retrying')
2185 Session().rollback()
2188 Session().rollback()
2186 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2189 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2187 if inv_obj is None:
2190 if inv_obj is None:
2188 log.error('failed to create CacheInvalidation entry')
2191 log.error('failed to create CacheInvalidation entry')
2189 # TODO: fail badly?
2192 # TODO: fail badly?
2190 # else: TOCTOU - another thread added the key at the same time; no further action required
2193 # else: TOCTOU - another thread added the key at the same time; no further action required
2191 return False
2194 return False
2192
2195
2193 @classmethod
2196 @classmethod
2194 def get_valid_cache_keys(cls):
2197 def get_valid_cache_keys(cls):
2195 """
2198 """
2196 Return opaque object with information of which caches still are valid
2199 Return opaque object with information of which caches still are valid
2197 and can be used without checking for invalidation.
2200 and can be used without checking for invalidation.
2198 """
2201 """
2199 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
2202 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
2200
2203
2201
2204
2202 class ChangesetComment(Base, BaseDbModel):
2205 class ChangesetComment(Base, BaseDbModel):
2203 __tablename__ = 'changeset_comments'
2206 __tablename__ = 'changeset_comments'
2204 __table_args__ = (
2207 __table_args__ = (
2205 Index('cc_revision_idx', 'revision'),
2208 Index('cc_revision_idx', 'revision'),
2206 Index('cc_pull_request_id_idx', 'pull_request_id'),
2209 Index('cc_pull_request_id_idx', 'pull_request_id'),
2207 _table_args_default_dict,
2210 _table_args_default_dict,
2208 )
2211 )
2209
2212
2210 comment_id = Column(Integer(), primary_key=True)
2213 comment_id = Column(Integer(), primary_key=True)
2211 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2214 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2212 revision = Column(String(40), nullable=True)
2215 revision = Column(String(40), nullable=True)
2213 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2216 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2214 line_no = Column(Unicode(10), nullable=True)
2217 line_no = Column(Unicode(10), nullable=True)
2215 f_path = Column(Unicode(1000), nullable=True)
2218 f_path = Column(Unicode(1000), nullable=True)
2216 author_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2219 author_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2217 text = Column(UnicodeText(), nullable=False)
2220 text = Column(UnicodeText(), nullable=False)
2218 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2221 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2219 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2222 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2220
2223
2221 author = relationship('User')
2224 author = relationship('User')
2222 repo = relationship('Repository')
2225 repo = relationship('Repository')
2223 # status_change is frequently used directly in templates - make it a lazy
2226 # status_change is frequently used directly in templates - make it a lazy
2224 # join to avoid fetching each related ChangesetStatus on demand.
2227 # join to avoid fetching each related ChangesetStatus on demand.
2225 # There will only be one ChangesetStatus referencing each comment so the join will not explode.
2228 # There will only be one ChangesetStatus referencing each comment so the join will not explode.
2226 status_change = relationship('ChangesetStatus',
2229 status_change = relationship('ChangesetStatus',
2227 cascade="all, delete-orphan", lazy='joined')
2230 cascade="all, delete-orphan", lazy='joined')
2228 pull_request = relationship('PullRequest')
2231 pull_request = relationship('PullRequest')
2229
2232
2230 def url(self):
2233 def url(self):
2231 anchor = "comment-%s" % self.comment_id
2234 anchor = "comment-%s" % self.comment_id
2232 import kallithea.lib.helpers as h
2235 import kallithea.lib.helpers as h
2233 if self.revision:
2236 if self.revision:
2234 return h.url('changeset_home', repo_name=self.repo.repo_name, revision=self.revision, anchor=anchor)
2237 return h.url('changeset_home', repo_name=self.repo.repo_name, revision=self.revision, anchor=anchor)
2235 elif self.pull_request_id is not None:
2238 elif self.pull_request_id is not None:
2236 return self.pull_request.url(anchor=anchor)
2239 return self.pull_request.url(anchor=anchor)
2237
2240
2238 def deletable(self):
2241 def deletable(self):
2239 return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
2242 return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
2240
2243
2241
2244
2242 class ChangesetStatus(Base, BaseDbModel):
2245 class ChangesetStatus(Base, BaseDbModel):
2243 __tablename__ = 'changeset_statuses'
2246 __tablename__ = 'changeset_statuses'
2244 __table_args__ = (
2247 __table_args__ = (
2245 Index('cs_revision_idx', 'revision'),
2248 Index('cs_revision_idx', 'revision'),
2246 Index('cs_version_idx', 'version'),
2249 Index('cs_version_idx', 'version'),
2247 Index('cs_pull_request_id_idx', 'pull_request_id'),
2250 Index('cs_pull_request_id_idx', 'pull_request_id'),
2248 Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
2251 Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
2249 Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
2252 Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
2250 Index('cs_repo_id_pull_request_id_idx', 'repo_id', 'pull_request_id'),
2253 Index('cs_repo_id_pull_request_id_idx', 'repo_id', 'pull_request_id'),
2251 UniqueConstraint('repo_id', 'revision', 'version'),
2254 UniqueConstraint('repo_id', 'revision', 'version'),
2252 _table_args_default_dict,
2255 _table_args_default_dict,
2253 )
2256 )
2254
2257
2255 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2258 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2256 STATUS_APPROVED = 'approved'
2259 STATUS_APPROVED = 'approved'
2257 STATUS_REJECTED = 'rejected' # is shown as "Not approved" - TODO: change database content / scheme
2260 STATUS_REJECTED = 'rejected' # is shown as "Not approved" - TODO: change database content / scheme
2258 STATUS_UNDER_REVIEW = 'under_review'
2261 STATUS_UNDER_REVIEW = 'under_review'
2259
2262
2260 STATUSES = [
2263 STATUSES = [
2261 (STATUS_NOT_REVIEWED, _("Not reviewed")), # (no icon) and default
2264 (STATUS_NOT_REVIEWED, _("Not reviewed")), # (no icon) and default
2262 (STATUS_UNDER_REVIEW, _("Under review")),
2265 (STATUS_UNDER_REVIEW, _("Under review")),
2263 (STATUS_REJECTED, _("Not approved")),
2266 (STATUS_REJECTED, _("Not approved")),
2264 (STATUS_APPROVED, _("Approved")),
2267 (STATUS_APPROVED, _("Approved")),
2265 ]
2268 ]
2266 STATUSES_DICT = dict(STATUSES)
2269 STATUSES_DICT = dict(STATUSES)
2267
2270
2268 changeset_status_id = Column(Integer(), primary_key=True)
2271 changeset_status_id = Column(Integer(), primary_key=True)
2269 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2272 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2270 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2273 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2271 revision = Column(String(40), nullable=True)
2274 revision = Column(String(40), nullable=True)
2272 status = Column(String(128), nullable=False, default=DEFAULT)
2275 status = Column(String(128), nullable=False, default=DEFAULT)
2273 comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
2276 comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
2274 modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
2277 modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
2275 version = Column(Integer(), nullable=False, default=0)
2278 version = Column(Integer(), nullable=False, default=0)
2276 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2279 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2277
2280
2278 author = relationship('User')
2281 author = relationship('User')
2279 repo = relationship('Repository')
2282 repo = relationship('Repository')
2280 comment = relationship('ChangesetComment')
2283 comment = relationship('ChangesetComment')
2281 pull_request = relationship('PullRequest')
2284 pull_request = relationship('PullRequest')
2282
2285
2283 def __unicode__(self):
2286 def __unicode__(self):
2284 return u"<%s('%s:%s')>" % (
2287 return u"<%s('%s:%s')>" % (
2285 self.__class__.__name__,
2288 self.__class__.__name__,
2286 self.status, self.author
2289 self.status, self.author
2287 )
2290 )
2288
2291
2289 @classmethod
2292 @classmethod
2290 def get_status_lbl(cls, value):
2293 def get_status_lbl(cls, value):
2291 return cls.STATUSES_DICT.get(value)
2294 return cls.STATUSES_DICT.get(value)
2292
2295
2293 @property
2296 @property
2294 def status_lbl(self):
2297 def status_lbl(self):
2295 return ChangesetStatus.get_status_lbl(self.status)
2298 return ChangesetStatus.get_status_lbl(self.status)
2296
2299
2297 def __json__(self):
2300 def __json__(self):
2298 return dict(
2301 return dict(
2299 status=self.status,
2302 status=self.status,
2300 modified_at=self.modified_at,
2303 modified_at=self.modified_at,
2301 reviewer=self.author.username,
2304 reviewer=self.author.username,
2302 )
2305 )
2303
2306
2304
2307
2305 class PullRequest(Base, BaseDbModel):
2308 class PullRequest(Base, BaseDbModel):
2306 __tablename__ = 'pull_requests'
2309 __tablename__ = 'pull_requests'
2307 __table_args__ = (
2310 __table_args__ = (
2308 Index('pr_org_repo_id_idx', 'org_repo_id'),
2311 Index('pr_org_repo_id_idx', 'org_repo_id'),
2309 Index('pr_other_repo_id_idx', 'other_repo_id'),
2312 Index('pr_other_repo_id_idx', 'other_repo_id'),
2310 _table_args_default_dict,
2313 _table_args_default_dict,
2311 )
2314 )
2312
2315
2313 # values for .status
2316 # values for .status
2314 STATUS_NEW = u'new'
2317 STATUS_NEW = u'new'
2315 STATUS_CLOSED = u'closed'
2318 STATUS_CLOSED = u'closed'
2316
2319
2317 pull_request_id = Column(Integer(), primary_key=True)
2320 pull_request_id = Column(Integer(), primary_key=True)
2318 title = Column(Unicode(255), nullable=False)
2321 title = Column(Unicode(255), nullable=False)
2319 description = Column(UnicodeText(), nullable=False)
2322 description = Column(UnicodeText(), nullable=False)
2320 status = Column(Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2323 status = Column(Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2321 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2324 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2322 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2325 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2323 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2326 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2324 _revisions = Column('revisions', UnicodeText(), nullable=False)
2327 _revisions = Column('revisions', UnicodeText(), nullable=False)
2325 org_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2328 org_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2326 org_ref = Column(Unicode(255), nullable=False)
2329 org_ref = Column(Unicode(255), nullable=False)
2327 other_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2330 other_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2328 other_ref = Column(Unicode(255), nullable=False)
2331 other_ref = Column(Unicode(255), nullable=False)
2329
2332
2330 @hybrid_property
2333 @hybrid_property
2331 def revisions(self):
2334 def revisions(self):
2332 return self._revisions.split(':')
2335 return self._revisions.split(':')
2333
2336
2334 @revisions.setter
2337 @revisions.setter
2335 def revisions(self, val):
2338 def revisions(self, val):
2336 self._revisions = safe_unicode(':'.join(val))
2339 self._revisions = safe_unicode(':'.join(val))
2337
2340
2338 @property
2341 @property
2339 def org_ref_parts(self):
2342 def org_ref_parts(self):
2340 return self.org_ref.split(':')
2343 return self.org_ref.split(':')
2341
2344
2342 @property
2345 @property
2343 def other_ref_parts(self):
2346 def other_ref_parts(self):
2344 return self.other_ref.split(':')
2347 return self.other_ref.split(':')
2345
2348
2346 owner = relationship('User')
2349 owner = relationship('User')
2347 reviewers = relationship('PullRequestReviewer',
2350 reviewers = relationship('PullRequestReviewer',
2348 cascade="all, delete-orphan")
2351 cascade="all, delete-orphan")
2349 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2352 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2350 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2353 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2351 statuses = relationship('ChangesetStatus', order_by='ChangesetStatus.changeset_status_id')
2354 statuses = relationship('ChangesetStatus', order_by='ChangesetStatus.changeset_status_id')
2352 comments = relationship('ChangesetComment', order_by='ChangesetComment.comment_id',
2355 comments = relationship('ChangesetComment', order_by='ChangesetComment.comment_id',
2353 cascade="all, delete-orphan")
2356 cascade="all, delete-orphan")
2354
2357
2355 @classmethod
2358 @classmethod
2356 def query(cls, reviewer_id=None, include_closed=True, sorted=False):
2359 def query(cls, reviewer_id=None, include_closed=True, sorted=False):
2357 """Add PullRequest-specific helpers for common query constructs.
2360 """Add PullRequest-specific helpers for common query constructs.
2358
2361
2359 reviewer_id: only PRs with the specified user added as reviewer.
2362 reviewer_id: only PRs with the specified user added as reviewer.
2360
2363
2361 include_closed: if False, do not include closed PRs.
2364 include_closed: if False, do not include closed PRs.
2362
2365
2363 sorted: if True, apply the default ordering (newest first).
2366 sorted: if True, apply the default ordering (newest first).
2364 """
2367 """
2365 q = super(PullRequest, cls).query()
2368 q = super(PullRequest, cls).query()
2366
2369
2367 if reviewer_id is not None:
2370 if reviewer_id is not None:
2368 q = q.join(PullRequestReviewer).filter(PullRequestReviewer.user_id == reviewer_id)
2371 q = q.join(PullRequestReviewer).filter(PullRequestReviewer.user_id == reviewer_id)
2369
2372
2370 if not include_closed:
2373 if not include_closed:
2371 q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
2374 q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
2372
2375
2373 if sorted:
2376 if sorted:
2374 q = q.order_by(PullRequest.created_on.desc())
2377 q = q.order_by(PullRequest.created_on.desc())
2375
2378
2376 return q
2379 return q
2377
2380
2378 def get_reviewer_users(self):
2381 def get_reviewer_users(self):
2379 """Like .reviewers, but actually returning the users"""
2382 """Like .reviewers, but actually returning the users"""
2380 return User.query() \
2383 return User.query() \
2381 .join(PullRequestReviewer) \
2384 .join(PullRequestReviewer) \
2382 .filter(PullRequestReviewer.pull_request == self) \
2385 .filter(PullRequestReviewer.pull_request == self) \
2383 .order_by(PullRequestReviewer.pull_request_reviewers_id) \
2386 .order_by(PullRequestReviewer.pull_request_reviewers_id) \
2384 .all()
2387 .all()
2385
2388
2386 def is_closed(self):
2389 def is_closed(self):
2387 return self.status == self.STATUS_CLOSED
2390 return self.status == self.STATUS_CLOSED
2388
2391
2389 def user_review_status(self, user_id):
2392 def user_review_status(self, user_id):
2390 """Return the user's latest status votes on PR"""
2393 """Return the user's latest status votes on PR"""
2391 # note: no filtering on repo - that would be redundant
2394 # note: no filtering on repo - that would be redundant
2392 status = ChangesetStatus.query() \
2395 status = ChangesetStatus.query() \
2393 .filter(ChangesetStatus.pull_request == self) \
2396 .filter(ChangesetStatus.pull_request == self) \
2394 .filter(ChangesetStatus.user_id == user_id) \
2397 .filter(ChangesetStatus.user_id == user_id) \
2395 .order_by(ChangesetStatus.version) \
2398 .order_by(ChangesetStatus.version) \
2396 .first()
2399 .first()
2397 return str(status.status) if status else ''
2400 return str(status.status) if status else ''
2398
2401
2399 @classmethod
2402 @classmethod
2400 def make_nice_id(cls, pull_request_id):
2403 def make_nice_id(cls, pull_request_id):
2401 '''Return pull request id nicely formatted for displaying'''
2404 '''Return pull request id nicely formatted for displaying'''
2402 return '#%s' % pull_request_id
2405 return '#%s' % pull_request_id
2403
2406
2404 def nice_id(self):
2407 def nice_id(self):
2405 '''Return the id of this pull request, nicely formatted for displaying'''
2408 '''Return the id of this pull request, nicely formatted for displaying'''
2406 return self.make_nice_id(self.pull_request_id)
2409 return self.make_nice_id(self.pull_request_id)
2407
2410
2408 def __json__(self):
2411 def __json__(self):
2409 return dict(
2412 return dict(
2410 revisions=self.revisions
2413 revisions=self.revisions
2411 )
2414 )
2412
2415
2413 def url(self, **kwargs):
2416 def url(self, **kwargs):
2414 canonical = kwargs.pop('canonical', None)
2417 canonical = kwargs.pop('canonical', None)
2415 import kallithea.lib.helpers as h
2418 import kallithea.lib.helpers as h
2416 b = self.org_ref_parts[1]
2419 b = self.org_ref_parts[1]
2417 if b != self.other_ref_parts[1]:
2420 if b != self.other_ref_parts[1]:
2418 s = '/_/' + b
2421 s = '/_/' + b
2419 else:
2422 else:
2420 s = '/_/' + self.title
2423 s = '/_/' + self.title
2421 kwargs['extra'] = urlreadable(s)
2424 kwargs['extra'] = urlreadable(s)
2422 if canonical:
2425 if canonical:
2423 return h.canonical_url('pullrequest_show', repo_name=self.other_repo.repo_name,
2426 return h.canonical_url('pullrequest_show', repo_name=self.other_repo.repo_name,
2424 pull_request_id=self.pull_request_id, **kwargs)
2427 pull_request_id=self.pull_request_id, **kwargs)
2425 return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
2428 return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
2426 pull_request_id=self.pull_request_id, **kwargs)
2429 pull_request_id=self.pull_request_id, **kwargs)
2427
2430
2428 class PullRequestReviewer(Base, BaseDbModel):
2431 class PullRequestReviewer(Base, BaseDbModel):
2429 __tablename__ = 'pull_request_reviewers'
2432 __tablename__ = 'pull_request_reviewers'
2430 __table_args__ = (
2433 __table_args__ = (
2431 Index('pull_request_reviewers_user_id_idx', 'user_id'),
2434 Index('pull_request_reviewers_user_id_idx', 'user_id'),
2432 _table_args_default_dict,
2435 _table_args_default_dict,
2433 )
2436 )
2434
2437
2435 def __init__(self, user=None, pull_request=None):
2438 def __init__(self, user=None, pull_request=None):
2436 self.user = user
2439 self.user = user
2437 self.pull_request = pull_request
2440 self.pull_request = pull_request
2438
2441
2439 pull_request_reviewers_id = Column('pull_requests_reviewers_id', Integer(), primary_key=True)
2442 pull_request_reviewers_id = Column('pull_requests_reviewers_id', Integer(), primary_key=True)
2440 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2443 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2441 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2444 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2442
2445
2443 user = relationship('User')
2446 user = relationship('User')
2444 pull_request = relationship('PullRequest')
2447 pull_request = relationship('PullRequest')
2445
2448
2446
2449
2447 class Notification(Base, BaseDbModel):
2450 class Notification(Base, BaseDbModel):
2448 __tablename__ = 'notifications'
2451 __tablename__ = 'notifications'
2449 __table_args__ = (
2452 __table_args__ = (
2450 Index('notification_type_idx', 'type'),
2453 Index('notification_type_idx', 'type'),
2451 _table_args_default_dict,
2454 _table_args_default_dict,
2452 )
2455 )
2453
2456
2454 TYPE_CHANGESET_COMMENT = u'cs_comment'
2457 TYPE_CHANGESET_COMMENT = u'cs_comment'
2455 TYPE_MESSAGE = u'message'
2458 TYPE_MESSAGE = u'message'
2456 TYPE_MENTION = u'mention' # not used
2459 TYPE_MENTION = u'mention' # not used
2457 TYPE_REGISTRATION = u'registration'
2460 TYPE_REGISTRATION = u'registration'
2458 TYPE_PULL_REQUEST = u'pull_request'
2461 TYPE_PULL_REQUEST = u'pull_request'
2459 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2462 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2460
2463
2461 notification_id = Column(Integer(), primary_key=True)
2464 notification_id = Column(Integer(), primary_key=True)
2462 subject = Column(Unicode(512), nullable=False)
2465 subject = Column(Unicode(512), nullable=False)
2463 body = Column(UnicodeText(), nullable=False)
2466 body = Column(UnicodeText(), nullable=False)
2464 created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2467 created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2465 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2468 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2466 type_ = Column('type', Unicode(255), nullable=False)
2469 type_ = Column('type', Unicode(255), nullable=False)
2467
2470
2468 created_by_user = relationship('User')
2471 created_by_user = relationship('User')
2469 notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
2472 notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
2470
2473
2471 @property
2474 @property
2472 def recipients(self):
2475 def recipients(self):
2473 return [x.user for x in UserNotification.query()
2476 return [x.user for x in UserNotification.query()
2474 .filter(UserNotification.notification == self)
2477 .filter(UserNotification.notification == self)
2475 .order_by(UserNotification.user_id.asc()).all()]
2478 .order_by(UserNotification.user_id.asc()).all()]
2476
2479
2477 @classmethod
2480 @classmethod
2478 def create(cls, created_by, subject, body, recipients, type_=None):
2481 def create(cls, created_by, subject, body, recipients, type_=None):
2479 if type_ is None:
2482 if type_ is None:
2480 type_ = Notification.TYPE_MESSAGE
2483 type_ = Notification.TYPE_MESSAGE
2481
2484
2482 notification = cls()
2485 notification = cls()
2483 notification.created_by_user = created_by
2486 notification.created_by_user = created_by
2484 notification.subject = subject
2487 notification.subject = subject
2485 notification.body = body
2488 notification.body = body
2486 notification.type_ = type_
2489 notification.type_ = type_
2487 notification.created_on = datetime.datetime.now()
2490 notification.created_on = datetime.datetime.now()
2488
2491
2489 for recipient in recipients:
2492 for recipient in recipients:
2490 un = UserNotification()
2493 un = UserNotification()
2491 un.notification = notification
2494 un.notification = notification
2492 un.user_id = recipient.user_id
2495 un.user_id = recipient.user_id
2493 # Mark notifications to self "pre-read" - should perhaps just be skipped
2496 # Mark notifications to self "pre-read" - should perhaps just be skipped
2494 if recipient == created_by:
2497 if recipient == created_by:
2495 un.read = True
2498 un.read = True
2496 Session().add(un)
2499 Session().add(un)
2497
2500
2498 Session().add(notification)
2501 Session().add(notification)
2499 Session().flush() # assign notification.notification_id
2502 Session().flush() # assign notification.notification_id
2500 return notification
2503 return notification
2501
2504
2502 @property
2505 @property
2503 def description(self):
2506 def description(self):
2504 from kallithea.model.notification import NotificationModel
2507 from kallithea.model.notification import NotificationModel
2505 return NotificationModel().make_description(self)
2508 return NotificationModel().make_description(self)
2506
2509
2507
2510
2508 class UserNotification(Base, BaseDbModel):
2511 class UserNotification(Base, BaseDbModel):
2509 __tablename__ = 'user_to_notification'
2512 __tablename__ = 'user_to_notification'
2510 __table_args__ = (
2513 __table_args__ = (
2511 UniqueConstraint('user_id', 'notification_id'),
2514 UniqueConstraint('user_id', 'notification_id'),
2512 _table_args_default_dict,
2515 _table_args_default_dict,
2513 )
2516 )
2514
2517
2515 user_id = Column(Integer(), ForeignKey('users.user_id'), primary_key=True)
2518 user_id = Column(Integer(), ForeignKey('users.user_id'), primary_key=True)
2516 notification_id = Column(Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2519 notification_id = Column(Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2517 read = Column(Boolean, nullable=False, default=False)
2520 read = Column(Boolean, nullable=False, default=False)
2518 sent_on = Column(DateTime(timezone=False), nullable=True) # FIXME: not nullable?
2521 sent_on = Column(DateTime(timezone=False), nullable=True) # FIXME: not nullable?
2519
2522
2520 user = relationship('User')
2523 user = relationship('User')
2521 notification = relationship('Notification')
2524 notification = relationship('Notification')
2522
2525
2523 def mark_as_read(self):
2526 def mark_as_read(self):
2524 self.read = True
2527 self.read = True
2525
2528
2526
2529
2527 class Gist(Base, BaseDbModel):
2530 class Gist(Base, BaseDbModel):
2528 __tablename__ = 'gists'
2531 __tablename__ = 'gists'
2529 __table_args__ = (
2532 __table_args__ = (
2530 Index('g_gist_access_id_idx', 'gist_access_id'),
2533 Index('g_gist_access_id_idx', 'gist_access_id'),
2531 Index('g_created_on_idx', 'created_on'),
2534 Index('g_created_on_idx', 'created_on'),
2532 _table_args_default_dict,
2535 _table_args_default_dict,
2533 )
2536 )
2534
2537
2535 GIST_PUBLIC = u'public'
2538 GIST_PUBLIC = u'public'
2536 GIST_PRIVATE = u'private'
2539 GIST_PRIVATE = u'private'
2537 DEFAULT_FILENAME = u'gistfile1.txt'
2540 DEFAULT_FILENAME = u'gistfile1.txt'
2538
2541
2539 gist_id = Column(Integer(), primary_key=True)
2542 gist_id = Column(Integer(), primary_key=True)
2540 gist_access_id = Column(Unicode(250), nullable=False)
2543 gist_access_id = Column(Unicode(250), nullable=False)
2541 gist_description = Column(UnicodeText(), nullable=False)
2544 gist_description = Column(UnicodeText(), nullable=False)
2542 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2545 owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2543 gist_expires = Column(Float(53), nullable=False)
2546 gist_expires = Column(Float(53), nullable=False)
2544 gist_type = Column(Unicode(128), nullable=False)
2547 gist_type = Column(Unicode(128), nullable=False)
2545 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2548 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2546 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2549 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2547
2550
2548 owner = relationship('User')
2551 owner = relationship('User')
2549
2552
2550 @hybrid_property
2553 @hybrid_property
2551 def is_expired(self):
2554 def is_expired(self):
2552 return (self.gist_expires != -1) & (time.time() > self.gist_expires)
2555 return (self.gist_expires != -1) & (time.time() > self.gist_expires)
2553
2556
2554 def __repr__(self):
2557 def __repr__(self):
2555 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
2558 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
2556
2559
2557 @classmethod
2560 @classmethod
2558 def guess_instance(cls, value):
2561 def guess_instance(cls, value):
2559 return super(Gist, cls).guess_instance(value, Gist.get_by_access_id)
2562 return super(Gist, cls).guess_instance(value, Gist.get_by_access_id)
2560
2563
2561 @classmethod
2564 @classmethod
2562 def get_or_404(cls, id_):
2565 def get_or_404(cls, id_):
2563 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2566 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2564 if res is None:
2567 if res is None:
2565 raise HTTPNotFound
2568 raise HTTPNotFound
2566 return res
2569 return res
2567
2570
2568 @classmethod
2571 @classmethod
2569 def get_by_access_id(cls, gist_access_id):
2572 def get_by_access_id(cls, gist_access_id):
2570 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2573 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2571
2574
2572 def gist_url(self):
2575 def gist_url(self):
2573 import kallithea
2576 import kallithea
2574 alias_url = kallithea.CONFIG.get('gist_alias_url')
2577 alias_url = kallithea.CONFIG.get('gist_alias_url')
2575 if alias_url:
2578 if alias_url:
2576 return alias_url.replace('{gistid}', self.gist_access_id)
2579 return alias_url.replace('{gistid}', self.gist_access_id)
2577
2580
2578 import kallithea.lib.helpers as h
2581 import kallithea.lib.helpers as h
2579 return h.canonical_url('gist', gist_id=self.gist_access_id)
2582 return h.canonical_url('gist', gist_id=self.gist_access_id)
2580
2583
2581 @classmethod
2584 @classmethod
2582 def base_path(cls):
2585 def base_path(cls):
2583 """
2586 """
2584 Returns base path where all gists are stored
2587 Returns base path where all gists are stored
2585
2588
2586 :param cls:
2589 :param cls:
2587 """
2590 """
2588 from kallithea.model.gist import GIST_STORE_LOC
2591 from kallithea.model.gist import GIST_STORE_LOC
2589 q = Session().query(Ui) \
2592 q = Session().query(Ui) \
2590 .filter(Ui.ui_key == URL_SEP)
2593 .filter(Ui.ui_key == URL_SEP)
2591 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2594 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2592 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2595 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2593
2596
2594 def get_api_data(self):
2597 def get_api_data(self):
2595 """
2598 """
2596 Common function for generating gist related data for API
2599 Common function for generating gist related data for API
2597 """
2600 """
2598 gist = self
2601 gist = self
2599 data = dict(
2602 data = dict(
2600 gist_id=gist.gist_id,
2603 gist_id=gist.gist_id,
2601 type=gist.gist_type,
2604 type=gist.gist_type,
2602 access_id=gist.gist_access_id,
2605 access_id=gist.gist_access_id,
2603 description=gist.gist_description,
2606 description=gist.gist_description,
2604 url=gist.gist_url(),
2607 url=gist.gist_url(),
2605 expires=gist.gist_expires,
2608 expires=gist.gist_expires,
2606 created_on=gist.created_on,
2609 created_on=gist.created_on,
2607 )
2610 )
2608 return data
2611 return data
2609
2612
2610 def __json__(self):
2613 def __json__(self):
2611 data = dict(
2614 data = dict(
2612 )
2615 )
2613 data.update(self.get_api_data())
2616 data.update(self.get_api_data())
2614 return data
2617 return data
2615 ## SCM functions
2618 ## SCM functions
2616
2619
2617 @property
2620 @property
2618 def scm_instance(self):
2621 def scm_instance(self):
2619 from kallithea.lib.vcs import get_repo
2622 from kallithea.lib.vcs import get_repo
2620 base_path = self.base_path()
2623 base_path = self.base_path()
2621 return get_repo(os.path.join(*map(safe_str,
2624 return get_repo(os.path.join(*map(safe_str,
2622 [base_path, self.gist_access_id])))
2625 [base_path, self.gist_access_id])))
@@ -1,2505 +1,2508 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
14
15 """
15 """
16 Tests for the JSON-RPC web api.
16 Tests for the JSON-RPC web api.
17 """
17 """
18
18
19 import os
19 import os
20 import random
20 import random
21 import mock
21 import mock
22
22
23 from kallithea.tests.base import *
23 from kallithea.tests.base import *
24 from kallithea.tests.fixture import Fixture
24 from kallithea.tests.fixture import Fixture
25 from kallithea.lib.compat import json
25 from kallithea.lib.compat import json
26 from kallithea.lib.auth import AuthUser
26 from kallithea.lib.auth import AuthUser
27 from kallithea.model.user import UserModel
27 from kallithea.model.user import UserModel
28 from kallithea.model.user_group import UserGroupModel
28 from kallithea.model.user_group import UserGroupModel
29 from kallithea.model.repo import RepoModel
29 from kallithea.model.repo import RepoModel
30 from kallithea.model.repo_group import RepoGroupModel
30 from kallithea.model.repo_group import RepoGroupModel
31 from kallithea.model.meta import Session
31 from kallithea.model.meta import Session
32 from kallithea.model.scm import ScmModel
32 from kallithea.model.scm import ScmModel
33 from kallithea.model.gist import GistModel
33 from kallithea.model.gist import GistModel
34 from kallithea.model.db import Repository, User, Setting, Ui
34 from kallithea.model.db import Repository, User, Setting, Ui
35 from kallithea.lib.utils2 import time_to_datetime
35 from kallithea.lib.utils2 import time_to_datetime
36
36
37
37
38 API_URL = '/_admin/api'
38 API_URL = '/_admin/api'
39 TEST_USER_GROUP = u'test_user_group'
39 TEST_USER_GROUP = u'test_user_group'
40 TEST_REPO_GROUP = u'test_repo_group'
40 TEST_REPO_GROUP = u'test_repo_group'
41
41
42 fixture = Fixture()
42 fixture = Fixture()
43
43
44
44
45 def _build_data(apikey, method, **kw):
45 def _build_data(apikey, method, **kw):
46 """
46 """
47 Builds API data with given random ID
47 Builds API data with given random ID
48
48
49 :param random_id:
49 :param random_id:
50 """
50 """
51 random_id = random.randrange(1, 9999)
51 random_id = random.randrange(1, 9999)
52 return random_id, json.dumps({
52 return random_id, json.dumps({
53 "id": random_id,
53 "id": random_id,
54 "api_key": apikey,
54 "api_key": apikey,
55 "method": method,
55 "method": method,
56 "args": kw
56 "args": kw
57 })
57 })
58
58
59
59
60 jsonify = lambda obj: json.loads(json.dumps(obj))
60 jsonify = lambda obj: json.loads(json.dumps(obj))
61
61
62
62
63 def crash(*args, **kwargs):
63 def crash(*args, **kwargs):
64 raise Exception('Total Crash !')
64 raise Exception('Total Crash !')
65
65
66
66
67 def api_call(test_obj, params):
67 def api_call(test_obj, params):
68 response = test_obj.app.post(API_URL, content_type='application/json',
68 response = test_obj.app.post(API_URL, content_type='application/json',
69 params=params)
69 params=params)
70 return response
70 return response
71
71
72
72
73 ## helpers
73 ## helpers
74 def make_user_group(name=TEST_USER_GROUP):
74 def make_user_group(name=TEST_USER_GROUP):
75 gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
75 gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
76 UserGroupModel().add_user_to_group(user_group=gr,
76 UserGroupModel().add_user_to_group(user_group=gr,
77 user=TEST_USER_ADMIN_LOGIN)
77 user=TEST_USER_ADMIN_LOGIN)
78 Session().commit()
78 Session().commit()
79 return gr
79 return gr
80
80
81
81
82 def make_repo_group(name=TEST_REPO_GROUP):
82 def make_repo_group(name=TEST_REPO_GROUP):
83 gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
83 gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
84 Session().commit()
84 Session().commit()
85 return gr
85 return gr
86
86
87
87
88 class _BaseTestApi(object):
88 class _BaseTestApi(object):
89 REPO = None
89 REPO = None
90 REPO_TYPE = None
90 REPO_TYPE = None
91
91
92 @classmethod
92 @classmethod
93 def setup_class(cls):
93 def setup_class(cls):
94 cls.usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
94 cls.usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
95 cls.apikey = cls.usr.api_key
95 cls.apikey = cls.usr.api_key
96 cls.test_user = UserModel().create_or_update(
96 cls.test_user = UserModel().create_or_update(
97 username='test-api',
97 username='test-api',
98 password='test',
98 password='test',
99 email='test@example.com',
99 email='test@example.com',
100 firstname=u'first',
100 firstname=u'first',
101 lastname=u'last'
101 lastname=u'last'
102 )
102 )
103 Session().commit()
103 Session().commit()
104 cls.TEST_USER_LOGIN = cls.test_user.username
104 cls.TEST_USER_LOGIN = cls.test_user.username
105 cls.apikey_regular = cls.test_user.api_key
105 cls.apikey_regular = cls.test_user.api_key
106
106
107 @classmethod
107 @classmethod
108 def teardown_class(cls):
108 def teardown_class(cls):
109 pass
109 pass
110
110
111 def setup_method(self, method):
111 def setup_method(self, method):
112 make_user_group()
112 make_user_group()
113 make_repo_group()
113 make_repo_group()
114
114
115 def teardown_method(self, method):
115 def teardown_method(self, method):
116 fixture.destroy_user_group(TEST_USER_GROUP)
116 fixture.destroy_user_group(TEST_USER_GROUP)
117 fixture.destroy_gists()
117 fixture.destroy_gists()
118 fixture.destroy_repo_group(TEST_REPO_GROUP)
118 fixture.destroy_repo_group(TEST_REPO_GROUP)
119
119
120 def _compare_ok(self, id_, expected, given):
120 def _compare_ok(self, id_, expected, given):
121 expected = jsonify({
121 expected = jsonify({
122 'id': id_,
122 'id': id_,
123 'error': None,
123 'error': None,
124 'result': expected
124 'result': expected
125 })
125 })
126 given = json.loads(given)
126 given = json.loads(given)
127 assert expected == given
127 assert expected == given
128
128
129 def _compare_error(self, id_, expected, given):
129 def _compare_error(self, id_, expected, given):
130 expected = jsonify({
130 expected = jsonify({
131 'id': id_,
131 'id': id_,
132 'error': expected,
132 'error': expected,
133 'result': None
133 'result': None
134 })
134 })
135 given = json.loads(given)
135 given = json.loads(given)
136 assert expected == given
136 assert expected == given
137
137
138 def test_Optional_object(self):
138 def test_Optional_object(self):
139 from kallithea.controllers.api.api import Optional
139 from kallithea.controllers.api.api import Optional
140
140
141 option1 = Optional(None)
141 option1 = Optional(None)
142 assert '<Optional:%s>' % None == repr(option1)
142 assert '<Optional:%s>' % None == repr(option1)
143 assert option1() == None
143 assert option1() == None
144
144
145 assert 1 == Optional.extract(Optional(1))
145 assert 1 == Optional.extract(Optional(1))
146 assert 'trololo' == Optional.extract('trololo')
146 assert 'trololo' == Optional.extract('trololo')
147
147
148 def test_Optional_OAttr(self):
148 def test_Optional_OAttr(self):
149 from kallithea.controllers.api.api import Optional, OAttr
149 from kallithea.controllers.api.api import Optional, OAttr
150
150
151 option1 = Optional(OAttr('apiuser'))
151 option1 = Optional(OAttr('apiuser'))
152 assert 'apiuser' == Optional.extract(option1)
152 assert 'apiuser' == Optional.extract(option1)
153
153
154 def test_OAttr_object(self):
154 def test_OAttr_object(self):
155 from kallithea.controllers.api.api import OAttr
155 from kallithea.controllers.api.api import OAttr
156
156
157 oattr1 = OAttr('apiuser')
157 oattr1 = OAttr('apiuser')
158 assert '<OptionalAttr:apiuser>' == repr(oattr1)
158 assert '<OptionalAttr:apiuser>' == repr(oattr1)
159 assert oattr1() == oattr1
159 assert oattr1() == oattr1
160
160
161 def test_api_wrong_key(self):
161 def test_api_wrong_key(self):
162 id_, params = _build_data('trololo', 'get_user')
162 id_, params = _build_data('trololo', 'get_user')
163 response = api_call(self, params)
163 response = api_call(self, params)
164
164
165 expected = 'Invalid API key'
165 expected = 'Invalid API key'
166 self._compare_error(id_, expected, given=response.body)
166 self._compare_error(id_, expected, given=response.body)
167
167
168 def test_api_missing_non_optional_param(self):
168 def test_api_missing_non_optional_param(self):
169 id_, params = _build_data(self.apikey, 'get_repo')
169 id_, params = _build_data(self.apikey, 'get_repo')
170 response = api_call(self, params)
170 response = api_call(self, params)
171
171
172 expected = 'Missing non optional `repoid` arg in JSON DATA'
172 expected = 'Missing non optional `repoid` arg in JSON DATA'
173 self._compare_error(id_, expected, given=response.body)
173 self._compare_error(id_, expected, given=response.body)
174
174
175 def test_api_missing_non_optional_param_args_null(self):
175 def test_api_missing_non_optional_param_args_null(self):
176 id_, params = _build_data(self.apikey, 'get_repo')
176 id_, params = _build_data(self.apikey, 'get_repo')
177 params = params.replace('"args": {}', '"args": null')
177 params = params.replace('"args": {}', '"args": null')
178 response = api_call(self, params)
178 response = api_call(self, params)
179
179
180 expected = 'Missing non optional `repoid` arg in JSON DATA'
180 expected = 'Missing non optional `repoid` arg in JSON DATA'
181 self._compare_error(id_, expected, given=response.body)
181 self._compare_error(id_, expected, given=response.body)
182
182
183 def test_api_missing_non_optional_param_args_bad(self):
183 def test_api_missing_non_optional_param_args_bad(self):
184 id_, params = _build_data(self.apikey, 'get_repo')
184 id_, params = _build_data(self.apikey, 'get_repo')
185 params = params.replace('"args": {}', '"args": 1')
185 params = params.replace('"args": {}', '"args": 1')
186 response = api_call(self, params)
186 response = api_call(self, params)
187
187
188 expected = 'Missing non optional `repoid` arg in JSON DATA'
188 expected = 'Missing non optional `repoid` arg in JSON DATA'
189 self._compare_error(id_, expected, given=response.body)
189 self._compare_error(id_, expected, given=response.body)
190
190
191 def test_api_args_is_null(self):
191 def test_api_args_is_null(self):
192 id_, params = _build_data(self.apikey, 'get_users', )
192 id_, params = _build_data(self.apikey, 'get_users', )
193 params = params.replace('"args": {}', '"args": null')
193 params = params.replace('"args": {}', '"args": null')
194 response = api_call(self, params)
194 response = api_call(self, params)
195 assert response.status == '200 OK'
195 assert response.status == '200 OK'
196
196
197 def test_api_args_is_bad(self):
197 def test_api_args_is_bad(self):
198 id_, params = _build_data(self.apikey, 'get_users', )
198 id_, params = _build_data(self.apikey, 'get_users', )
199 params = params.replace('"args": {}', '"args": 1')
199 params = params.replace('"args": {}', '"args": 1')
200 response = api_call(self, params)
200 response = api_call(self, params)
201 assert response.status == '200 OK'
201 assert response.status == '200 OK'
202
202
203 def test_api_args_different_args(self):
203 def test_api_args_different_args(self):
204 import string
204 import string
205 expected = {
205 expected = {
206 'ascii_letters': string.ascii_letters,
206 'ascii_letters': string.ascii_letters,
207 'ws': string.whitespace,
207 'ws': string.whitespace,
208 'printables': string.printable
208 'printables': string.printable
209 }
209 }
210 id_, params = _build_data(self.apikey, 'test', args=expected)
210 id_, params = _build_data(self.apikey, 'test', args=expected)
211 response = api_call(self, params)
211 response = api_call(self, params)
212 assert response.status == '200 OK'
212 assert response.status == '200 OK'
213 self._compare_ok(id_, expected, response.body)
213 self._compare_ok(id_, expected, response.body)
214
214
215 def test_api_get_users(self):
215 def test_api_get_users(self):
216 id_, params = _build_data(self.apikey, 'get_users', )
216 id_, params = _build_data(self.apikey, 'get_users', )
217 response = api_call(self, params)
217 response = api_call(self, params)
218 ret_all = []
218 ret_all = []
219 _users = User.query().filter_by(is_default_user=False) \
219 _users = User.query().filter_by(is_default_user=False) \
220 .order_by(User.username).all()
220 .order_by(User.username).all()
221 for usr in _users:
221 for usr in _users:
222 ret = usr.get_api_data()
222 ret = usr.get_api_data()
223 ret_all.append(jsonify(ret))
223 ret_all.append(jsonify(ret))
224 expected = ret_all
224 expected = ret_all
225 self._compare_ok(id_, expected, given=response.body)
225 self._compare_ok(id_, expected, given=response.body)
226
226
227 def test_api_get_user(self):
227 def test_api_get_user(self):
228 id_, params = _build_data(self.apikey, 'get_user',
228 id_, params = _build_data(self.apikey, 'get_user',
229 userid=TEST_USER_ADMIN_LOGIN)
229 userid=TEST_USER_ADMIN_LOGIN)
230 response = api_call(self, params)
230 response = api_call(self, params)
231
231
232 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
232 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
233 ret = usr.get_api_data()
233 ret = usr.get_api_data()
234 ret['permissions'] = AuthUser(dbuser=usr).permissions
234 ret['permissions'] = AuthUser(dbuser=usr).permissions
235
235
236 expected = ret
236 expected = ret
237 self._compare_ok(id_, expected, given=response.body)
237 self._compare_ok(id_, expected, given=response.body)
238
238
239 def test_api_get_user_that_does_not_exist(self):
239 def test_api_get_user_that_does_not_exist(self):
240 id_, params = _build_data(self.apikey, 'get_user',
240 id_, params = _build_data(self.apikey, 'get_user',
241 userid='trololo')
241 userid='trololo')
242 response = api_call(self, params)
242 response = api_call(self, params)
243
243
244 expected = "user `%s` does not exist" % 'trololo'
244 expected = "user `%s` does not exist" % 'trololo'
245 self._compare_error(id_, expected, given=response.body)
245 self._compare_error(id_, expected, given=response.body)
246
246
247 def test_api_get_user_without_giving_userid(self):
247 def test_api_get_user_without_giving_userid(self):
248 id_, params = _build_data(self.apikey, 'get_user')
248 id_, params = _build_data(self.apikey, 'get_user')
249 response = api_call(self, params)
249 response = api_call(self, params)
250
250
251 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
251 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
252 ret = usr.get_api_data()
252 ret = usr.get_api_data()
253 ret['permissions'] = AuthUser(dbuser=usr).permissions
253 ret['permissions'] = AuthUser(dbuser=usr).permissions
254
254
255 expected = ret
255 expected = ret
256 self._compare_ok(id_, expected, given=response.body)
256 self._compare_ok(id_, expected, given=response.body)
257
257
258 def test_api_get_user_without_giving_userid_non_admin(self):
258 def test_api_get_user_without_giving_userid_non_admin(self):
259 id_, params = _build_data(self.apikey_regular, 'get_user')
259 id_, params = _build_data(self.apikey_regular, 'get_user')
260 response = api_call(self, params)
260 response = api_call(self, params)
261
261
262 usr = User.get_by_username(self.TEST_USER_LOGIN)
262 usr = User.get_by_username(self.TEST_USER_LOGIN)
263 ret = usr.get_api_data()
263 ret = usr.get_api_data()
264 ret['permissions'] = AuthUser(dbuser=usr).permissions
264 ret['permissions'] = AuthUser(dbuser=usr).permissions
265
265
266 expected = ret
266 expected = ret
267 self._compare_ok(id_, expected, given=response.body)
267 self._compare_ok(id_, expected, given=response.body)
268
268
269 def test_api_get_user_with_giving_userid_non_admin(self):
269 def test_api_get_user_with_giving_userid_non_admin(self):
270 id_, params = _build_data(self.apikey_regular, 'get_user',
270 id_, params = _build_data(self.apikey_regular, 'get_user',
271 userid=self.TEST_USER_LOGIN)
271 userid=self.TEST_USER_LOGIN)
272 response = api_call(self, params)
272 response = api_call(self, params)
273
273
274 expected = 'userid is not the same as your user'
274 expected = 'userid is not the same as your user'
275 self._compare_error(id_, expected, given=response.body)
275 self._compare_error(id_, expected, given=response.body)
276
276
277 def test_api_pull(self):
277 def test_api_pull(self):
278 repo_name = u'test_pull'
278 repo_name = u'test_pull'
279 r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
279 r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
280 r.clone_uri = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
280 r.clone_uri = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
281 Session().commit()
281 Session().commit()
282
282
283 id_, params = _build_data(self.apikey, 'pull',
283 id_, params = _build_data(self.apikey, 'pull',
284 repoid=repo_name,)
284 repoid=repo_name,)
285 response = api_call(self, params)
285 response = api_call(self, params)
286
286
287 expected = {'msg': 'Pulled from `%s`' % repo_name,
287 expected = {'msg': 'Pulled from `%s`' % repo_name,
288 'repository': repo_name}
288 'repository': repo_name}
289 self._compare_ok(id_, expected, given=response.body)
289 self._compare_ok(id_, expected, given=response.body)
290
290
291 fixture.destroy_repo(repo_name)
291 fixture.destroy_repo(repo_name)
292
292
293 def test_api_pull_error(self):
293 def test_api_pull_error(self):
294 id_, params = _build_data(self.apikey, 'pull',
294 id_, params = _build_data(self.apikey, 'pull',
295 repoid=self.REPO, )
295 repoid=self.REPO, )
296 response = api_call(self, params)
296 response = api_call(self, params)
297
297
298 expected = 'Unable to pull changes from `%s`' % self.REPO
298 expected = 'Unable to pull changes from `%s`' % self.REPO
299 self._compare_error(id_, expected, given=response.body)
299 self._compare_error(id_, expected, given=response.body)
300
300
301 def test_api_rescan_repos(self):
301 def test_api_rescan_repos(self):
302 id_, params = _build_data(self.apikey, 'rescan_repos')
302 id_, params = _build_data(self.apikey, 'rescan_repos')
303 response = api_call(self, params)
303 response = api_call(self, params)
304
304
305 expected = {'added': [], 'removed': []}
305 expected = {'added': [], 'removed': []}
306 self._compare_ok(id_, expected, given=response.body)
306 self._compare_ok(id_, expected, given=response.body)
307
307
308 @mock.patch.object(ScmModel, 'repo_scan', crash)
308 @mock.patch.object(ScmModel, 'repo_scan', crash)
309 def test_api_rescann_error(self):
309 def test_api_rescann_error(self):
310 id_, params = _build_data(self.apikey, 'rescan_repos', )
310 id_, params = _build_data(self.apikey, 'rescan_repos', )
311 response = api_call(self, params)
311 response = api_call(self, params)
312
312
313 expected = 'Error occurred during rescan repositories action'
313 expected = 'Error occurred during rescan repositories action'
314 self._compare_error(id_, expected, given=response.body)
314 self._compare_error(id_, expected, given=response.body)
315
315
316 def test_api_invalidate_cache(self):
316 def test_api_invalidate_cache(self):
317 repo = RepoModel().get_by_repo_name(self.REPO)
317 repo = RepoModel().get_by_repo_name(self.REPO)
318 repo.scm_instance_cached() # seed cache
318 repo.scm_instance_cached() # seed cache
319
319
320 id_, params = _build_data(self.apikey, 'invalidate_cache',
320 id_, params = _build_data(self.apikey, 'invalidate_cache',
321 repoid=self.REPO)
321 repoid=self.REPO)
322 response = api_call(self, params)
322 response = api_call(self, params)
323
323
324 expected = {
324 expected = {
325 'msg': "Cache for repository `%s` was invalidated" % (self.REPO,),
325 'msg': "Cache for repository `%s` was invalidated" % (self.REPO,),
326 'repository': self.REPO
326 'repository': self.REPO
327 }
327 }
328 self._compare_ok(id_, expected, given=response.body)
328 self._compare_ok(id_, expected, given=response.body)
329
329
330 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
330 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
331 def test_api_invalidate_cache_error(self):
331 def test_api_invalidate_cache_error(self):
332 id_, params = _build_data(self.apikey, 'invalidate_cache',
332 id_, params = _build_data(self.apikey, 'invalidate_cache',
333 repoid=self.REPO)
333 repoid=self.REPO)
334 response = api_call(self, params)
334 response = api_call(self, params)
335
335
336 expected = 'Error occurred during cache invalidation action'
336 expected = 'Error occurred during cache invalidation action'
337 self._compare_error(id_, expected, given=response.body)
337 self._compare_error(id_, expected, given=response.body)
338
338
339 def test_api_invalidate_cache_regular_user_no_permission(self):
339 def test_api_invalidate_cache_regular_user_no_permission(self):
340 repo = RepoModel().get_by_repo_name(self.REPO)
340 repo = RepoModel().get_by_repo_name(self.REPO)
341 repo.scm_instance_cached() # seed cache
341 repo.scm_instance_cached() # seed cache
342
342
343 id_, params = _build_data(self.apikey_regular, 'invalidate_cache',
343 id_, params = _build_data(self.apikey_regular, 'invalidate_cache',
344 repoid=self.REPO)
344 repoid=self.REPO)
345 response = api_call(self, params)
345 response = api_call(self, params)
346
346
347 expected = "repository `%s` does not exist" % (self.REPO,)
347 expected = "repository `%s` does not exist" % (self.REPO,)
348 self._compare_error(id_, expected, given=response.body)
348 self._compare_error(id_, expected, given=response.body)
349
349
350 def test_api_lock_repo_lock_acquire(self):
350 def test_api_lock_repo_lock_acquire(self):
351 id_, params = _build_data(self.apikey, 'lock',
351 id_, params = _build_data(self.apikey, 'lock',
352 userid=TEST_USER_ADMIN_LOGIN,
352 userid=TEST_USER_ADMIN_LOGIN,
353 repoid=self.REPO,
353 repoid=self.REPO,
354 locked=True)
354 locked=True)
355 response = api_call(self, params)
355 response = api_call(self, params)
356 expected = {
356 expected = {
357 'repo': self.REPO, 'locked': True,
357 'repo': self.REPO, 'locked': True,
358 'locked_since': response.json['result']['locked_since'],
358 'locked_since': response.json['result']['locked_since'],
359 'locked_by': TEST_USER_ADMIN_LOGIN,
359 'locked_by': TEST_USER_ADMIN_LOGIN,
360 'lock_state_changed': True,
360 'lock_state_changed': True,
361 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
361 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
362 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
362 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
363 }
363 }
364 self._compare_ok(id_, expected, given=response.body)
364 self._compare_ok(id_, expected, given=response.body)
365
365
366 def test_api_lock_repo_lock_acquire_by_non_admin(self):
366 def test_api_lock_repo_lock_acquire_by_non_admin(self):
367 repo_name = u'api_delete_me'
367 repo_name = u'api_delete_me'
368 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
368 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
369 cur_user=self.TEST_USER_LOGIN)
369 cur_user=self.TEST_USER_LOGIN)
370 try:
370 try:
371 id_, params = _build_data(self.apikey_regular, 'lock',
371 id_, params = _build_data(self.apikey_regular, 'lock',
372 repoid=repo_name,
372 repoid=repo_name,
373 locked=True)
373 locked=True)
374 response = api_call(self, params)
374 response = api_call(self, params)
375 expected = {
375 expected = {
376 'repo': repo_name,
376 'repo': repo_name,
377 'locked': True,
377 'locked': True,
378 'locked_since': response.json['result']['locked_since'],
378 'locked_since': response.json['result']['locked_since'],
379 'locked_by': self.TEST_USER_LOGIN,
379 'locked_by': self.TEST_USER_LOGIN,
380 'lock_state_changed': True,
380 'lock_state_changed': True,
381 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
381 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
382 % (self.TEST_USER_LOGIN, repo_name, True))
382 % (self.TEST_USER_LOGIN, repo_name, True))
383 }
383 }
384 self._compare_ok(id_, expected, given=response.body)
384 self._compare_ok(id_, expected, given=response.body)
385 finally:
385 finally:
386 fixture.destroy_repo(repo_name)
386 fixture.destroy_repo(repo_name)
387
387
388 def test_api_lock_repo_lock_acquire_non_admin_with_userid(self):
388 def test_api_lock_repo_lock_acquire_non_admin_with_userid(self):
389 repo_name = u'api_delete_me'
389 repo_name = u'api_delete_me'
390 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
390 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
391 cur_user=self.TEST_USER_LOGIN)
391 cur_user=self.TEST_USER_LOGIN)
392 try:
392 try:
393 id_, params = _build_data(self.apikey_regular, 'lock',
393 id_, params = _build_data(self.apikey_regular, 'lock',
394 userid=TEST_USER_ADMIN_LOGIN,
394 userid=TEST_USER_ADMIN_LOGIN,
395 repoid=repo_name,
395 repoid=repo_name,
396 locked=True)
396 locked=True)
397 response = api_call(self, params)
397 response = api_call(self, params)
398 expected = 'userid is not the same as your user'
398 expected = 'userid is not the same as your user'
399 self._compare_error(id_, expected, given=response.body)
399 self._compare_error(id_, expected, given=response.body)
400 finally:
400 finally:
401 fixture.destroy_repo(repo_name)
401 fixture.destroy_repo(repo_name)
402
402
403 def test_api_lock_repo_lock_acquire_non_admin_not_his_repo(self):
403 def test_api_lock_repo_lock_acquire_non_admin_not_his_repo(self):
404 id_, params = _build_data(self.apikey_regular, 'lock',
404 id_, params = _build_data(self.apikey_regular, 'lock',
405 repoid=self.REPO,
405 repoid=self.REPO,
406 locked=True)
406 locked=True)
407 response = api_call(self, params)
407 response = api_call(self, params)
408 expected = 'repository `%s` does not exist' % (self.REPO)
408 expected = 'repository `%s` does not exist' % (self.REPO)
409 self._compare_error(id_, expected, given=response.body)
409 self._compare_error(id_, expected, given=response.body)
410
410
411 def test_api_lock_repo_lock_release(self):
411 def test_api_lock_repo_lock_release(self):
412 id_, params = _build_data(self.apikey, 'lock',
412 id_, params = _build_data(self.apikey, 'lock',
413 userid=TEST_USER_ADMIN_LOGIN,
413 userid=TEST_USER_ADMIN_LOGIN,
414 repoid=self.REPO,
414 repoid=self.REPO,
415 locked=False)
415 locked=False)
416 response = api_call(self, params)
416 response = api_call(self, params)
417 expected = {
417 expected = {
418 'repo': self.REPO,
418 'repo': self.REPO,
419 'locked': False,
419 'locked': False,
420 'locked_since': None,
420 'locked_since': None,
421 'locked_by': TEST_USER_ADMIN_LOGIN,
421 'locked_by': TEST_USER_ADMIN_LOGIN,
422 'lock_state_changed': True,
422 'lock_state_changed': True,
423 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
423 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
424 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
424 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
425 }
425 }
426 self._compare_ok(id_, expected, given=response.body)
426 self._compare_ok(id_, expected, given=response.body)
427
427
428 def test_api_lock_repo_lock_acquire_optional_userid(self):
428 def test_api_lock_repo_lock_acquire_optional_userid(self):
429 id_, params = _build_data(self.apikey, 'lock',
429 id_, params = _build_data(self.apikey, 'lock',
430 repoid=self.REPO,
430 repoid=self.REPO,
431 locked=True)
431 locked=True)
432 response = api_call(self, params)
432 response = api_call(self, params)
433 time_ = response.json['result']['locked_since']
433 time_ = response.json['result']['locked_since']
434 expected = {
434 expected = {
435 'repo': self.REPO,
435 'repo': self.REPO,
436 'locked': True,
436 'locked': True,
437 'locked_since': time_,
437 'locked_since': time_,
438 'locked_by': TEST_USER_ADMIN_LOGIN,
438 'locked_by': TEST_USER_ADMIN_LOGIN,
439 'lock_state_changed': True,
439 'lock_state_changed': True,
440 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
440 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
441 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
441 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
442 }
442 }
443
443
444 self._compare_ok(id_, expected, given=response.body)
444 self._compare_ok(id_, expected, given=response.body)
445
445
446 def test_api_lock_repo_lock_optional_locked(self):
446 def test_api_lock_repo_lock_optional_locked(self):
447 try:
447 try:
448 id_, params = _build_data(self.apikey, 'lock',
448 id_, params = _build_data(self.apikey, 'lock',
449 repoid=self.REPO)
449 repoid=self.REPO)
450 response = api_call(self, params)
450 response = api_call(self, params)
451 time_ = response.json['result']['locked_since']
451 time_ = response.json['result']['locked_since']
452 expected = {
452 expected = {
453 'repo': self.REPO,
453 'repo': self.REPO,
454 'locked': True,
454 'locked': True,
455 'locked_since': time_,
455 'locked_since': time_,
456 'locked_by': TEST_USER_ADMIN_LOGIN,
456 'locked_by': TEST_USER_ADMIN_LOGIN,
457 'lock_state_changed': False,
457 'lock_state_changed': False,
458 'msg': ('Repo `%s` locked by `%s` on `%s`.'
458 'msg': ('Repo `%s` locked by `%s` on `%s`.'
459 % (self.REPO, TEST_USER_ADMIN_LOGIN,
459 % (self.REPO, TEST_USER_ADMIN_LOGIN,
460 json.dumps(time_to_datetime(time_))))
460 json.dumps(time_to_datetime(time_))))
461 }
461 }
462 self._compare_ok(id_, expected, given=response.body)
462 self._compare_ok(id_, expected, given=response.body)
463 finally:
463 finally:
464 # cleanup
464 # cleanup
465 Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
465 Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
466
466
467 def test_api_lock_repo_lock_optional_not_locked(self):
467 def test_api_lock_repo_lock_optional_not_locked(self):
468 repo_name = u'api_not_locked'
468 repo_name = u'api_not_locked'
469 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
469 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
470 cur_user=self.TEST_USER_LOGIN)
470 cur_user=self.TEST_USER_LOGIN)
471 assert repo.locked == [None, None]
471 assert repo.locked == [None, None]
472 try:
472 try:
473 id_, params = _build_data(self.apikey, 'lock',
473 id_, params = _build_data(self.apikey, 'lock',
474 repoid=repo.repo_id)
474 repoid=repo.repo_id)
475 response = api_call(self, params)
475 response = api_call(self, params)
476 expected = {
476 expected = {
477 'repo': repo_name,
477 'repo': repo_name,
478 'locked': False,
478 'locked': False,
479 'locked_since': None,
479 'locked_since': None,
480 'locked_by': None,
480 'locked_by': None,
481 'lock_state_changed': False,
481 'lock_state_changed': False,
482 'msg': ('Repo `%s` not locked.' % (repo_name,))
482 'msg': ('Repo `%s` not locked.' % (repo_name,))
483 }
483 }
484 self._compare_ok(id_, expected, given=response.body)
484 self._compare_ok(id_, expected, given=response.body)
485 finally:
485 finally:
486 fixture.destroy_repo(repo_name)
486 fixture.destroy_repo(repo_name)
487
487
488 @mock.patch.object(Repository, 'lock', crash)
488 @mock.patch.object(Repository, 'lock', crash)
489 def test_api_lock_error(self):
489 def test_api_lock_error(self):
490 id_, params = _build_data(self.apikey, 'lock',
490 id_, params = _build_data(self.apikey, 'lock',
491 userid=TEST_USER_ADMIN_LOGIN,
491 userid=TEST_USER_ADMIN_LOGIN,
492 repoid=self.REPO,
492 repoid=self.REPO,
493 locked=True)
493 locked=True)
494 response = api_call(self, params)
494 response = api_call(self, params)
495
495
496 expected = 'Error occurred locking repository `%s`' % self.REPO
496 expected = 'Error occurred locking repository `%s`' % self.REPO
497 self._compare_error(id_, expected, given=response.body)
497 self._compare_error(id_, expected, given=response.body)
498
498
499 def test_api_get_locks_regular_user(self):
499 def test_api_get_locks_regular_user(self):
500 id_, params = _build_data(self.apikey_regular, 'get_locks')
500 id_, params = _build_data(self.apikey_regular, 'get_locks')
501 response = api_call(self, params)
501 response = api_call(self, params)
502 expected = []
502 expected = []
503 self._compare_ok(id_, expected, given=response.body)
503 self._compare_ok(id_, expected, given=response.body)
504
504
505 def test_api_get_locks_with_userid_regular_user(self):
505 def test_api_get_locks_with_userid_regular_user(self):
506 id_, params = _build_data(self.apikey_regular, 'get_locks',
506 id_, params = _build_data(self.apikey_regular, 'get_locks',
507 userid=TEST_USER_ADMIN_LOGIN)
507 userid=TEST_USER_ADMIN_LOGIN)
508 response = api_call(self, params)
508 response = api_call(self, params)
509 expected = 'userid is not the same as your user'
509 expected = 'userid is not the same as your user'
510 self._compare_error(id_, expected, given=response.body)
510 self._compare_error(id_, expected, given=response.body)
511
511
512 def test_api_get_locks(self):
512 def test_api_get_locks(self):
513 id_, params = _build_data(self.apikey, 'get_locks')
513 id_, params = _build_data(self.apikey, 'get_locks')
514 response = api_call(self, params)
514 response = api_call(self, params)
515 expected = []
515 expected = []
516 self._compare_ok(id_, expected, given=response.body)
516 self._compare_ok(id_, expected, given=response.body)
517
517
518 def test_api_get_locks_with_one_locked_repo(self):
518 def test_api_get_locks_with_one_locked_repo(self):
519 repo_name = u'api_delete_me'
519 repo_name = u'api_delete_me'
520 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
520 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
521 cur_user=self.TEST_USER_LOGIN)
521 cur_user=self.TEST_USER_LOGIN)
522 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
522 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
523 try:
523 try:
524 id_, params = _build_data(self.apikey, 'get_locks')
524 id_, params = _build_data(self.apikey, 'get_locks')
525 response = api_call(self, params)
525 response = api_call(self, params)
526 expected = [repo.get_api_data()]
526 expected = [repo.get_api_data()]
527 self._compare_ok(id_, expected, given=response.body)
527 self._compare_ok(id_, expected, given=response.body)
528 finally:
528 finally:
529 fixture.destroy_repo(repo_name)
529 fixture.destroy_repo(repo_name)
530
530
531 def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
531 def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
532 repo_name = u'api_delete_me'
532 repo_name = u'api_delete_me'
533 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
533 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
534 cur_user=self.TEST_USER_LOGIN)
534 cur_user=self.TEST_USER_LOGIN)
535 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
535 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
536 try:
536 try:
537 id_, params = _build_data(self.apikey, 'get_locks',
537 id_, params = _build_data(self.apikey, 'get_locks',
538 userid=self.TEST_USER_LOGIN)
538 userid=self.TEST_USER_LOGIN)
539 response = api_call(self, params)
539 response = api_call(self, params)
540 expected = [repo.get_api_data()]
540 expected = [repo.get_api_data()]
541 self._compare_ok(id_, expected, given=response.body)
541 self._compare_ok(id_, expected, given=response.body)
542 finally:
542 finally:
543 fixture.destroy_repo(repo_name)
543 fixture.destroy_repo(repo_name)
544
544
545 def test_api_get_locks_with_userid(self):
545 def test_api_get_locks_with_userid(self):
546 id_, params = _build_data(self.apikey, 'get_locks',
546 id_, params = _build_data(self.apikey, 'get_locks',
547 userid=TEST_USER_REGULAR_LOGIN)
547 userid=TEST_USER_REGULAR_LOGIN)
548 response = api_call(self, params)
548 response = api_call(self, params)
549 expected = []
549 expected = []
550 self._compare_ok(id_, expected, given=response.body)
550 self._compare_ok(id_, expected, given=response.body)
551
551
552 def test_api_create_existing_user(self):
552 def test_api_create_existing_user(self):
553 id_, params = _build_data(self.apikey, 'create_user',
553 id_, params = _build_data(self.apikey, 'create_user',
554 username=TEST_USER_ADMIN_LOGIN,
554 username=TEST_USER_ADMIN_LOGIN,
555 email='test@example.com',
555 email='test@example.com',
556 password='trololo')
556 password='trololo')
557 response = api_call(self, params)
557 response = api_call(self, params)
558
558
559 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
559 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
560 self._compare_error(id_, expected, given=response.body)
560 self._compare_error(id_, expected, given=response.body)
561
561
562 def test_api_create_user_with_existing_email(self):
562 def test_api_create_user_with_existing_email(self):
563 id_, params = _build_data(self.apikey, 'create_user',
563 id_, params = _build_data(self.apikey, 'create_user',
564 username=TEST_USER_ADMIN_LOGIN + 'new',
564 username=TEST_USER_ADMIN_LOGIN + 'new',
565 email=TEST_USER_REGULAR_EMAIL,
565 email=TEST_USER_REGULAR_EMAIL,
566 password='trololo')
566 password='trololo')
567 response = api_call(self, params)
567 response = api_call(self, params)
568
568
569 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
569 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
570 self._compare_error(id_, expected, given=response.body)
570 self._compare_error(id_, expected, given=response.body)
571
571
572 def test_api_create_user(self):
572 def test_api_create_user(self):
573 username = 'test_new_api_user'
573 username = 'test_new_api_user'
574 email = username + "@example.com"
574 email = username + "@example.com"
575
575
576 id_, params = _build_data(self.apikey, 'create_user',
576 id_, params = _build_data(self.apikey, 'create_user',
577 username=username,
577 username=username,
578 email=email,
578 email=email,
579 password='trololo')
579 password='trololo')
580 response = api_call(self, params)
580 response = api_call(self, params)
581
581
582 usr = User.get_by_username(username)
582 usr = User.get_by_username(username)
583 ret = dict(
583 ret = dict(
584 msg='created new user `%s`' % username,
584 msg='created new user `%s`' % username,
585 user=jsonify(usr.get_api_data())
585 user=jsonify(usr.get_api_data())
586 )
586 )
587
587
588 try:
588 try:
589 expected = ret
589 expected = ret
590 self._compare_ok(id_, expected, given=response.body)
590 self._compare_ok(id_, expected, given=response.body)
591 finally:
591 finally:
592 fixture.destroy_user(usr.user_id)
592 fixture.destroy_user(usr.user_id)
593
593
594 def test_api_create_user_without_password(self):
594 def test_api_create_user_without_password(self):
595 username = 'test_new_api_user_passwordless'
595 username = 'test_new_api_user_passwordless'
596 email = username + "@example.com"
596 email = username + "@example.com"
597
597
598 id_, params = _build_data(self.apikey, 'create_user',
598 id_, params = _build_data(self.apikey, 'create_user',
599 username=username,
599 username=username,
600 email=email)
600 email=email)
601 response = api_call(self, params)
601 response = api_call(self, params)
602
602
603 usr = User.get_by_username(username)
603 usr = User.get_by_username(username)
604 ret = dict(
604 ret = dict(
605 msg='created new user `%s`' % username,
605 msg='created new user `%s`' % username,
606 user=jsonify(usr.get_api_data())
606 user=jsonify(usr.get_api_data())
607 )
607 )
608 try:
608 try:
609 expected = ret
609 expected = ret
610 self._compare_ok(id_, expected, given=response.body)
610 self._compare_ok(id_, expected, given=response.body)
611 finally:
611 finally:
612 fixture.destroy_user(usr.user_id)
612 fixture.destroy_user(usr.user_id)
613
613
614 def test_api_create_user_with_extern_name(self):
614 def test_api_create_user_with_extern_name(self):
615 username = 'test_new_api_user_passwordless'
615 username = 'test_new_api_user_passwordless'
616 email = username + "@example.com"
616 email = username + "@example.com"
617
617
618 id_, params = _build_data(self.apikey, 'create_user',
618 id_, params = _build_data(self.apikey, 'create_user',
619 username=username,
619 username=username,
620 email=email, extern_name='internal')
620 email=email, extern_name='internal')
621 response = api_call(self, params)
621 response = api_call(self, params)
622
622
623 usr = User.get_by_username(username)
623 usr = User.get_by_username(username)
624 ret = dict(
624 ret = dict(
625 msg='created new user `%s`' % username,
625 msg='created new user `%s`' % username,
626 user=jsonify(usr.get_api_data())
626 user=jsonify(usr.get_api_data())
627 )
627 )
628 try:
628 try:
629 expected = ret
629 expected = ret
630 self._compare_ok(id_, expected, given=response.body)
630 self._compare_ok(id_, expected, given=response.body)
631 finally:
631 finally:
632 fixture.destroy_user(usr.user_id)
632 fixture.destroy_user(usr.user_id)
633
633
634 @mock.patch.object(UserModel, 'create_or_update', crash)
634 @mock.patch.object(UserModel, 'create_or_update', crash)
635 def test_api_create_user_when_exception_happened(self):
635 def test_api_create_user_when_exception_happened(self):
636
636
637 username = 'test_new_api_user'
637 username = 'test_new_api_user'
638 email = username + "@example.com"
638 email = username + "@example.com"
639
639
640 id_, params = _build_data(self.apikey, 'create_user',
640 id_, params = _build_data(self.apikey, 'create_user',
641 username=username,
641 username=username,
642 email=email,
642 email=email,
643 password='trololo')
643 password='trololo')
644 response = api_call(self, params)
644 response = api_call(self, params)
645 expected = 'failed to create user `%s`' % username
645 expected = 'failed to create user `%s`' % username
646 self._compare_error(id_, expected, given=response.body)
646 self._compare_error(id_, expected, given=response.body)
647
647
648 def test_api_delete_user(self):
648 def test_api_delete_user(self):
649 usr = UserModel().create_or_update(username=u'test_user',
649 usr = UserModel().create_or_update(username=u'test_user',
650 password=u'qweqwe',
650 password=u'qweqwe',
651 email=u'u232@example.com',
651 email=u'u232@example.com',
652 firstname=u'u1', lastname=u'u1')
652 firstname=u'u1', lastname=u'u1')
653 Session().commit()
653 Session().commit()
654 username = usr.username
654 username = usr.username
655 email = usr.email
655 email = usr.email
656 usr_id = usr.user_id
656 usr_id = usr.user_id
657 ## DELETE THIS USER NOW
657 ## DELETE THIS USER NOW
658
658
659 id_, params = _build_data(self.apikey, 'delete_user',
659 id_, params = _build_data(self.apikey, 'delete_user',
660 userid=username, )
660 userid=username, )
661 response = api_call(self, params)
661 response = api_call(self, params)
662
662
663 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
663 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
664 'user': None}
664 'user': None}
665 expected = ret
665 expected = ret
666 self._compare_ok(id_, expected, given=response.body)
666 self._compare_ok(id_, expected, given=response.body)
667
667
668 @mock.patch.object(UserModel, 'delete', crash)
668 @mock.patch.object(UserModel, 'delete', crash)
669 def test_api_delete_user_when_exception_happened(self):
669 def test_api_delete_user_when_exception_happened(self):
670 usr = UserModel().create_or_update(username=u'test_user',
670 usr = UserModel().create_or_update(username=u'test_user',
671 password=u'qweqwe',
671 password=u'qweqwe',
672 email=u'u232@example.com',
672 email=u'u232@example.com',
673 firstname=u'u1', lastname=u'u1')
673 firstname=u'u1', lastname=u'u1')
674 Session().commit()
674 Session().commit()
675 username = usr.username
675 username = usr.username
676
676
677 id_, params = _build_data(self.apikey, 'delete_user',
677 id_, params = _build_data(self.apikey, 'delete_user',
678 userid=username, )
678 userid=username, )
679 response = api_call(self, params)
679 response = api_call(self, params)
680 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
680 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
681 usr.username)
681 usr.username)
682 expected = ret
682 expected = ret
683 self._compare_error(id_, expected, given=response.body)
683 self._compare_error(id_, expected, given=response.body)
684
684
685 @parametrize('name,expected', [
685 @parametrize('name,expected', [
686 ('firstname', 'new_username'),
686 ('firstname', 'new_username'),
687 ('lastname', 'new_username'),
687 ('lastname', 'new_username'),
688 ('email', 'new_username'),
688 ('email', 'new_username'),
689 ('admin', True),
689 ('admin', True),
690 ('admin', False),
690 ('admin', False),
691 ('extern_type', 'ldap'),
691 ('extern_type', 'ldap'),
692 ('extern_type', None),
692 ('extern_type', None),
693 ('extern_name', 'test'),
693 ('extern_name', 'test'),
694 ('extern_name', None),
694 ('extern_name', None),
695 ('active', False),
695 ('active', False),
696 ('active', True),
696 ('active', True),
697 ('password', 'newpass'),
697 ('password', 'newpass'),
698 ])
698 ])
699 def test_api_update_user(self, name, expected):
699 def test_api_update_user(self, name, expected):
700 usr = User.get_by_username(self.TEST_USER_LOGIN)
700 usr = User.get_by_username(self.TEST_USER_LOGIN)
701 kw = {name: expected,
701 kw = {name: expected,
702 'userid': usr.user_id}
702 'userid': usr.user_id}
703 id_, params = _build_data(self.apikey, 'update_user', **kw)
703 id_, params = _build_data(self.apikey, 'update_user', **kw)
704 response = api_call(self, params)
704 response = api_call(self, params)
705
705
706 ret = {
706 ret = {
707 'msg': 'updated user ID:%s %s' % (
707 'msg': 'updated user ID:%s %s' % (
708 usr.user_id, self.TEST_USER_LOGIN),
708 usr.user_id, self.TEST_USER_LOGIN),
709 'user': jsonify(User \
709 'user': jsonify(User \
710 .get_by_username(self.TEST_USER_LOGIN) \
710 .get_by_username(self.TEST_USER_LOGIN) \
711 .get_api_data())
711 .get_api_data())
712 }
712 }
713
713
714 expected = ret
714 expected = ret
715 self._compare_ok(id_, expected, given=response.body)
715 self._compare_ok(id_, expected, given=response.body)
716
716
717 def test_api_update_user_no_changed_params(self):
717 def test_api_update_user_no_changed_params(self):
718 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
718 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
719 ret = jsonify(usr.get_api_data())
719 ret = jsonify(usr.get_api_data())
720 id_, params = _build_data(self.apikey, 'update_user',
720 id_, params = _build_data(self.apikey, 'update_user',
721 userid=TEST_USER_ADMIN_LOGIN)
721 userid=TEST_USER_ADMIN_LOGIN)
722
722
723 response = api_call(self, params)
723 response = api_call(self, params)
724 ret = {
724 ret = {
725 'msg': 'updated user ID:%s %s' % (
725 'msg': 'updated user ID:%s %s' % (
726 usr.user_id, TEST_USER_ADMIN_LOGIN),
726 usr.user_id, TEST_USER_ADMIN_LOGIN),
727 'user': ret
727 'user': ret
728 }
728 }
729 expected = ret
729 expected = ret
730 self._compare_ok(id_, expected, given=response.body)
730 self._compare_ok(id_, expected, given=response.body)
731
731
732 def test_api_update_user_by_user_id(self):
732 def test_api_update_user_by_user_id(self):
733 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
733 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
734 ret = jsonify(usr.get_api_data())
734 ret = jsonify(usr.get_api_data())
735 id_, params = _build_data(self.apikey, 'update_user',
735 id_, params = _build_data(self.apikey, 'update_user',
736 userid=usr.user_id)
736 userid=usr.user_id)
737
737
738 response = api_call(self, params)
738 response = api_call(self, params)
739 ret = {
739 ret = {
740 'msg': 'updated user ID:%s %s' % (
740 'msg': 'updated user ID:%s %s' % (
741 usr.user_id, TEST_USER_ADMIN_LOGIN),
741 usr.user_id, TEST_USER_ADMIN_LOGIN),
742 'user': ret
742 'user': ret
743 }
743 }
744 expected = ret
744 expected = ret
745 self._compare_ok(id_, expected, given=response.body)
745 self._compare_ok(id_, expected, given=response.body)
746
746
747 def test_api_update_user_default_user(self):
747 def test_api_update_user_default_user(self):
748 usr = User.get_default_user()
748 usr = User.get_default_user()
749 id_, params = _build_data(self.apikey, 'update_user',
749 id_, params = _build_data(self.apikey, 'update_user',
750 userid=usr.user_id)
750 userid=usr.user_id)
751
751
752 response = api_call(self, params)
752 response = api_call(self, params)
753 expected = 'editing default user is forbidden'
753 expected = 'editing default user is forbidden'
754 self._compare_error(id_, expected, given=response.body)
754 self._compare_error(id_, expected, given=response.body)
755
755
756 @mock.patch.object(UserModel, 'update_user', crash)
756 @mock.patch.object(UserModel, 'update_user', crash)
757 def test_api_update_user_when_exception_happens(self):
757 def test_api_update_user_when_exception_happens(self):
758 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
758 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
759 ret = jsonify(usr.get_api_data())
759 ret = jsonify(usr.get_api_data())
760 id_, params = _build_data(self.apikey, 'update_user',
760 id_, params = _build_data(self.apikey, 'update_user',
761 userid=usr.user_id)
761 userid=usr.user_id)
762
762
763 response = api_call(self, params)
763 response = api_call(self, params)
764 ret = 'failed to update user `%s`' % usr.user_id
764 ret = 'failed to update user `%s`' % usr.user_id
765
765
766 expected = ret
766 expected = ret
767 self._compare_error(id_, expected, given=response.body)
767 self._compare_error(id_, expected, given=response.body)
768
768
769 def test_api_get_repo(self):
769 def test_api_get_repo(self):
770 new_group = u'some_new_group'
770 new_group = u'some_new_group'
771 make_user_group(new_group)
771 make_user_group(new_group)
772 RepoModel().grant_user_group_permission(repo=self.REPO,
772 RepoModel().grant_user_group_permission(repo=self.REPO,
773 group_name=new_group,
773 group_name=new_group,
774 perm='repository.read')
774 perm='repository.read')
775 Session().commit()
775 Session().commit()
776 id_, params = _build_data(self.apikey, 'get_repo',
776 id_, params = _build_data(self.apikey, 'get_repo',
777 repoid=self.REPO)
777 repoid=self.REPO)
778 response = api_call(self, params)
778 response = api_call(self, params)
779 assert u"tags" not in response.json[u'result']
779 assert u"tags" not in response.json[u'result']
780 assert u'pull_requests' not in response.json[u'result']
780
781
781 repo = RepoModel().get_by_repo_name(self.REPO)
782 repo = RepoModel().get_by_repo_name(self.REPO)
782 ret = repo.get_api_data()
783 ret = repo.get_api_data()
783
784
784 members = []
785 members = []
785 followers = []
786 followers = []
786 for user in repo.repo_to_perm:
787 for user in repo.repo_to_perm:
787 perm = user.permission.permission_name
788 perm = user.permission.permission_name
788 user = user.user
789 user = user.user
789 user_data = {'name': user.username, 'type': "user",
790 user_data = {'name': user.username, 'type': "user",
790 'permission': perm}
791 'permission': perm}
791 members.append(user_data)
792 members.append(user_data)
792
793
793 for user_group in repo.users_group_to_perm:
794 for user_group in repo.users_group_to_perm:
794 perm = user_group.permission.permission_name
795 perm = user_group.permission.permission_name
795 user_group = user_group.users_group
796 user_group = user_group.users_group
796 user_group_data = {'name': user_group.users_group_name,
797 user_group_data = {'name': user_group.users_group_name,
797 'type': "user_group", 'permission': perm}
798 'type': "user_group", 'permission': perm}
798 members.append(user_group_data)
799 members.append(user_group_data)
799
800
800 for user in repo.followers:
801 for user in repo.followers:
801 followers.append(user.user.get_api_data())
802 followers.append(user.user.get_api_data())
802
803
803 ret['members'] = members
804 ret['members'] = members
804 ret['followers'] = followers
805 ret['followers'] = followers
805
806
806 expected = ret
807 expected = ret
807 self._compare_ok(id_, expected, given=response.body)
808 self._compare_ok(id_, expected, given=response.body)
808 fixture.destroy_user_group(new_group)
809 fixture.destroy_user_group(new_group)
809
810
810 id_, params = _build_data(self.apikey, 'get_repo', repoid=self.REPO,
811 id_, params = _build_data(self.apikey, 'get_repo', repoid=self.REPO,
811 with_revision_names=True)
812 with_revision_names=True,
813 with_pullrequests=True)
812 response = api_call(self, params)
814 response = api_call(self, params)
813 assert u"v0.2.0" in response.json[u'result'][u'tags']
815 assert u"v0.2.0" in response.json[u'result'][u'tags']
816 assert u'pull_requests' in response.json[u'result']
814
817
815 @parametrize('grant_perm', [
818 @parametrize('grant_perm', [
816 ('repository.admin'),
819 ('repository.admin'),
817 ('repository.write'),
820 ('repository.write'),
818 ('repository.read'),
821 ('repository.read'),
819 ])
822 ])
820 def test_api_get_repo_by_non_admin(self, grant_perm):
823 def test_api_get_repo_by_non_admin(self, grant_perm):
821 RepoModel().grant_user_permission(repo=self.REPO,
824 RepoModel().grant_user_permission(repo=self.REPO,
822 user=self.TEST_USER_LOGIN,
825 user=self.TEST_USER_LOGIN,
823 perm=grant_perm)
826 perm=grant_perm)
824 Session().commit()
827 Session().commit()
825 id_, params = _build_data(self.apikey_regular, 'get_repo',
828 id_, params = _build_data(self.apikey_regular, 'get_repo',
826 repoid=self.REPO)
829 repoid=self.REPO)
827 response = api_call(self, params)
830 response = api_call(self, params)
828
831
829 repo = RepoModel().get_by_repo_name(self.REPO)
832 repo = RepoModel().get_by_repo_name(self.REPO)
830 ret = repo.get_api_data()
833 ret = repo.get_api_data()
831
834
832 members = []
835 members = []
833 followers = []
836 followers = []
834 assert 2 == len(repo.repo_to_perm)
837 assert 2 == len(repo.repo_to_perm)
835 for user in repo.repo_to_perm:
838 for user in repo.repo_to_perm:
836 perm = user.permission.permission_name
839 perm = user.permission.permission_name
837 user_obj = user.user
840 user_obj = user.user
838 user_data = {'name': user_obj.username, 'type': "user",
841 user_data = {'name': user_obj.username, 'type': "user",
839 'permission': perm}
842 'permission': perm}
840 members.append(user_data)
843 members.append(user_data)
841
844
842 for user_group in repo.users_group_to_perm:
845 for user_group in repo.users_group_to_perm:
843 perm = user_group.permission.permission_name
846 perm = user_group.permission.permission_name
844 user_group_obj = user_group.users_group
847 user_group_obj = user_group.users_group
845 user_group_data = {'name': user_group_obj.users_group_name,
848 user_group_data = {'name': user_group_obj.users_group_name,
846 'type': "user_group", 'permission': perm}
849 'type': "user_group", 'permission': perm}
847 members.append(user_group_data)
850 members.append(user_group_data)
848
851
849 for user in repo.followers:
852 for user in repo.followers:
850 followers.append(user.user.get_api_data())
853 followers.append(user.user.get_api_data())
851
854
852 ret['members'] = members
855 ret['members'] = members
853 ret['followers'] = followers
856 ret['followers'] = followers
854
857
855 expected = ret
858 expected = ret
856 try:
859 try:
857 self._compare_ok(id_, expected, given=response.body)
860 self._compare_ok(id_, expected, given=response.body)
858 finally:
861 finally:
859 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
862 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
860
863
861 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
864 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
862 RepoModel().grant_user_permission(repo=self.REPO,
865 RepoModel().grant_user_permission(repo=self.REPO,
863 user=self.TEST_USER_LOGIN,
866 user=self.TEST_USER_LOGIN,
864 perm='repository.none')
867 perm='repository.none')
865
868
866 id_, params = _build_data(self.apikey_regular, 'get_repo',
869 id_, params = _build_data(self.apikey_regular, 'get_repo',
867 repoid=self.REPO)
870 repoid=self.REPO)
868 response = api_call(self, params)
871 response = api_call(self, params)
869
872
870 expected = 'repository `%s` does not exist' % (self.REPO)
873 expected = 'repository `%s` does not exist' % (self.REPO)
871 self._compare_error(id_, expected, given=response.body)
874 self._compare_error(id_, expected, given=response.body)
872
875
873 def test_api_get_repo_that_doesn_not_exist(self):
876 def test_api_get_repo_that_doesn_not_exist(self):
874 id_, params = _build_data(self.apikey, 'get_repo',
877 id_, params = _build_data(self.apikey, 'get_repo',
875 repoid='no-such-repo')
878 repoid='no-such-repo')
876 response = api_call(self, params)
879 response = api_call(self, params)
877
880
878 ret = 'repository `%s` does not exist' % 'no-such-repo'
881 ret = 'repository `%s` does not exist' % 'no-such-repo'
879 expected = ret
882 expected = ret
880 self._compare_error(id_, expected, given=response.body)
883 self._compare_error(id_, expected, given=response.body)
881
884
882 def test_api_get_repos(self):
885 def test_api_get_repos(self):
883 id_, params = _build_data(self.apikey, 'get_repos')
886 id_, params = _build_data(self.apikey, 'get_repos')
884 response = api_call(self, params)
887 response = api_call(self, params)
885
888
886 expected = jsonify([
889 expected = jsonify([
887 repo.get_api_data()
890 repo.get_api_data()
888 for repo in Repository.query()
891 for repo in Repository.query()
889 ])
892 ])
890
893
891 self._compare_ok(id_, expected, given=response.body)
894 self._compare_ok(id_, expected, given=response.body)
892
895
893 def test_api_get_repos_non_admin(self):
896 def test_api_get_repos_non_admin(self):
894 id_, params = _build_data(self.apikey_regular, 'get_repos')
897 id_, params = _build_data(self.apikey_regular, 'get_repos')
895 response = api_call(self, params)
898 response = api_call(self, params)
896
899
897 expected = jsonify([
900 expected = jsonify([
898 repo.get_api_data()
901 repo.get_api_data()
899 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN)
902 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN)
900 ])
903 ])
901
904
902 self._compare_ok(id_, expected, given=response.body)
905 self._compare_ok(id_, expected, given=response.body)
903
906
904 @parametrize('name,ret_type', [
907 @parametrize('name,ret_type', [
905 ('all', 'all'),
908 ('all', 'all'),
906 ('dirs', 'dirs'),
909 ('dirs', 'dirs'),
907 ('files', 'files'),
910 ('files', 'files'),
908 ])
911 ])
909 def test_api_get_repo_nodes(self, name, ret_type):
912 def test_api_get_repo_nodes(self, name, ret_type):
910 rev = 'tip'
913 rev = 'tip'
911 path = '/'
914 path = '/'
912 id_, params = _build_data(self.apikey, 'get_repo_nodes',
915 id_, params = _build_data(self.apikey, 'get_repo_nodes',
913 repoid=self.REPO, revision=rev,
916 repoid=self.REPO, revision=rev,
914 root_path=path,
917 root_path=path,
915 ret_type=ret_type)
918 ret_type=ret_type)
916 response = api_call(self, params)
919 response = api_call(self, params)
917
920
918 # we don't the actual return types here since it's tested somewhere
921 # we don't the actual return types here since it's tested somewhere
919 # else
922 # else
920 expected = response.json['result']
923 expected = response.json['result']
921 self._compare_ok(id_, expected, given=response.body)
924 self._compare_ok(id_, expected, given=response.body)
922
925
923 def test_api_get_repo_nodes_bad_revisions(self):
926 def test_api_get_repo_nodes_bad_revisions(self):
924 rev = 'i-dont-exist'
927 rev = 'i-dont-exist'
925 path = '/'
928 path = '/'
926 id_, params = _build_data(self.apikey, 'get_repo_nodes',
929 id_, params = _build_data(self.apikey, 'get_repo_nodes',
927 repoid=self.REPO, revision=rev,
930 repoid=self.REPO, revision=rev,
928 root_path=path, )
931 root_path=path, )
929 response = api_call(self, params)
932 response = api_call(self, params)
930
933
931 expected = 'failed to get repo: `%s` nodes' % self.REPO
934 expected = 'failed to get repo: `%s` nodes' % self.REPO
932 self._compare_error(id_, expected, given=response.body)
935 self._compare_error(id_, expected, given=response.body)
933
936
934 def test_api_get_repo_nodes_bad_path(self):
937 def test_api_get_repo_nodes_bad_path(self):
935 rev = 'tip'
938 rev = 'tip'
936 path = '/idontexits'
939 path = '/idontexits'
937 id_, params = _build_data(self.apikey, 'get_repo_nodes',
940 id_, params = _build_data(self.apikey, 'get_repo_nodes',
938 repoid=self.REPO, revision=rev,
941 repoid=self.REPO, revision=rev,
939 root_path=path, )
942 root_path=path, )
940 response = api_call(self, params)
943 response = api_call(self, params)
941
944
942 expected = 'failed to get repo: `%s` nodes' % self.REPO
945 expected = 'failed to get repo: `%s` nodes' % self.REPO
943 self._compare_error(id_, expected, given=response.body)
946 self._compare_error(id_, expected, given=response.body)
944
947
945 def test_api_get_repo_nodes_bad_ret_type(self):
948 def test_api_get_repo_nodes_bad_ret_type(self):
946 rev = 'tip'
949 rev = 'tip'
947 path = '/'
950 path = '/'
948 ret_type = 'error'
951 ret_type = 'error'
949 id_, params = _build_data(self.apikey, 'get_repo_nodes',
952 id_, params = _build_data(self.apikey, 'get_repo_nodes',
950 repoid=self.REPO, revision=rev,
953 repoid=self.REPO, revision=rev,
951 root_path=path,
954 root_path=path,
952 ret_type=ret_type)
955 ret_type=ret_type)
953 response = api_call(self, params)
956 response = api_call(self, params)
954
957
955 expected = ('ret_type must be one of %s'
958 expected = ('ret_type must be one of %s'
956 % (','.join(['files', 'dirs', 'all'])))
959 % (','.join(['files', 'dirs', 'all'])))
957 self._compare_error(id_, expected, given=response.body)
960 self._compare_error(id_, expected, given=response.body)
958
961
959 @parametrize('name,ret_type,grant_perm', [
962 @parametrize('name,ret_type,grant_perm', [
960 ('all', 'all', 'repository.write'),
963 ('all', 'all', 'repository.write'),
961 ('dirs', 'dirs', 'repository.admin'),
964 ('dirs', 'dirs', 'repository.admin'),
962 ('files', 'files', 'repository.read'),
965 ('files', 'files', 'repository.read'),
963 ])
966 ])
964 def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
967 def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
965 RepoModel().grant_user_permission(repo=self.REPO,
968 RepoModel().grant_user_permission(repo=self.REPO,
966 user=self.TEST_USER_LOGIN,
969 user=self.TEST_USER_LOGIN,
967 perm=grant_perm)
970 perm=grant_perm)
968 Session().commit()
971 Session().commit()
969
972
970 rev = 'tip'
973 rev = 'tip'
971 path = '/'
974 path = '/'
972 id_, params = _build_data(self.apikey_regular, 'get_repo_nodes',
975 id_, params = _build_data(self.apikey_regular, 'get_repo_nodes',
973 repoid=self.REPO, revision=rev,
976 repoid=self.REPO, revision=rev,
974 root_path=path,
977 root_path=path,
975 ret_type=ret_type)
978 ret_type=ret_type)
976 response = api_call(self, params)
979 response = api_call(self, params)
977
980
978 # we don't the actual return types here since it's tested somewhere
981 # we don't the actual return types here since it's tested somewhere
979 # else
982 # else
980 expected = response.json['result']
983 expected = response.json['result']
981 try:
984 try:
982 self._compare_ok(id_, expected, given=response.body)
985 self._compare_ok(id_, expected, given=response.body)
983 finally:
986 finally:
984 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
987 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
985
988
986 def test_api_create_repo(self):
989 def test_api_create_repo(self):
987 repo_name = u'api-repo'
990 repo_name = u'api-repo'
988 id_, params = _build_data(self.apikey, 'create_repo',
991 id_, params = _build_data(self.apikey, 'create_repo',
989 repo_name=repo_name,
992 repo_name=repo_name,
990 owner=TEST_USER_ADMIN_LOGIN,
993 owner=TEST_USER_ADMIN_LOGIN,
991 repo_type=self.REPO_TYPE,
994 repo_type=self.REPO_TYPE,
992 )
995 )
993 response = api_call(self, params)
996 response = api_call(self, params)
994
997
995 repo = RepoModel().get_by_repo_name(repo_name)
998 repo = RepoModel().get_by_repo_name(repo_name)
996 assert repo != None
999 assert repo != None
997 ret = {
1000 ret = {
998 'msg': 'Created new repository `%s`' % repo_name,
1001 'msg': 'Created new repository `%s`' % repo_name,
999 'success': True,
1002 'success': True,
1000 'task': None,
1003 'task': None,
1001 }
1004 }
1002 expected = ret
1005 expected = ret
1003 self._compare_ok(id_, expected, given=response.body)
1006 self._compare_ok(id_, expected, given=response.body)
1004 fixture.destroy_repo(repo_name)
1007 fixture.destroy_repo(repo_name)
1005
1008
1006 def test_api_create_repo_and_repo_group(self):
1009 def test_api_create_repo_and_repo_group(self):
1007 repo_name = u'my_gr/api-repo'
1010 repo_name = u'my_gr/api-repo'
1008 id_, params = _build_data(self.apikey, 'create_repo',
1011 id_, params = _build_data(self.apikey, 'create_repo',
1009 repo_name=repo_name,
1012 repo_name=repo_name,
1010 owner=TEST_USER_ADMIN_LOGIN,
1013 owner=TEST_USER_ADMIN_LOGIN,
1011 repo_type=self.REPO_TYPE,)
1014 repo_type=self.REPO_TYPE,)
1012 response = api_call(self, params)
1015 response = api_call(self, params)
1013 print params
1016 print params
1014 repo = RepoModel().get_by_repo_name(repo_name)
1017 repo = RepoModel().get_by_repo_name(repo_name)
1015 assert repo != None
1018 assert repo != None
1016 ret = {
1019 ret = {
1017 'msg': 'Created new repository `%s`' % repo_name,
1020 'msg': 'Created new repository `%s`' % repo_name,
1018 'success': True,
1021 'success': True,
1019 'task': None,
1022 'task': None,
1020 }
1023 }
1021 expected = ret
1024 expected = ret
1022 self._compare_ok(id_, expected, given=response.body)
1025 self._compare_ok(id_, expected, given=response.body)
1023 fixture.destroy_repo(repo_name)
1026 fixture.destroy_repo(repo_name)
1024 fixture.destroy_repo_group(u'my_gr')
1027 fixture.destroy_repo_group(u'my_gr')
1025
1028
1026 def test_api_create_repo_in_repo_group_without_permission(self):
1029 def test_api_create_repo_in_repo_group_without_permission(self):
1027 repo_group_name = u'%s/api-repo-repo' % TEST_REPO_GROUP
1030 repo_group_name = u'%s/api-repo-repo' % TEST_REPO_GROUP
1028 repo_name = u'%s/api-repo' % repo_group_name
1031 repo_name = u'%s/api-repo' % repo_group_name
1029
1032
1030 rg = fixture.create_repo_group(repo_group_name)
1033 rg = fixture.create_repo_group(repo_group_name)
1031 Session().commit()
1034 Session().commit()
1032 RepoGroupModel().grant_user_permission(repo_group_name,
1035 RepoGroupModel().grant_user_permission(repo_group_name,
1033 self.TEST_USER_LOGIN,
1036 self.TEST_USER_LOGIN,
1034 'group.none')
1037 'group.none')
1035 Session().commit()
1038 Session().commit()
1036
1039
1037 id_, params = _build_data(self.apikey_regular, 'create_repo',
1040 id_, params = _build_data(self.apikey_regular, 'create_repo',
1038 repo_name=repo_name,
1041 repo_name=repo_name,
1039 repo_type=self.REPO_TYPE,
1042 repo_type=self.REPO_TYPE,
1040 )
1043 )
1041 response = api_call(self, params)
1044 response = api_call(self, params)
1042
1045
1043 # Current result when API access control is different from Web:
1046 # Current result when API access control is different from Web:
1044 ret = {
1047 ret = {
1045 'msg': 'Created new repository `%s`' % repo_name,
1048 'msg': 'Created new repository `%s`' % repo_name,
1046 'success': True,
1049 'success': True,
1047 'task': None,
1050 'task': None,
1048 }
1051 }
1049 expected = ret
1052 expected = ret
1050 self._compare_ok(id_, expected, given=response.body)
1053 self._compare_ok(id_, expected, given=response.body)
1051 fixture.destroy_repo(repo_name)
1054 fixture.destroy_repo(repo_name)
1052
1055
1053 # Expected and arguably more correct result:
1056 # Expected and arguably more correct result:
1054 #expected = 'failed to create repository `%s`' % repo_name
1057 #expected = 'failed to create repository `%s`' % repo_name
1055 #self._compare_error(id_, expected, given=response.body)
1058 #self._compare_error(id_, expected, given=response.body)
1056
1059
1057 fixture.destroy_repo_group(repo_group_name)
1060 fixture.destroy_repo_group(repo_group_name)
1058
1061
1059 def test_api_create_repo_unknown_owner(self):
1062 def test_api_create_repo_unknown_owner(self):
1060 repo_name = u'api-repo'
1063 repo_name = u'api-repo'
1061 owner = 'i-dont-exist'
1064 owner = 'i-dont-exist'
1062 id_, params = _build_data(self.apikey, 'create_repo',
1065 id_, params = _build_data(self.apikey, 'create_repo',
1063 repo_name=repo_name,
1066 repo_name=repo_name,
1064 owner=owner,
1067 owner=owner,
1065 repo_type=self.REPO_TYPE,
1068 repo_type=self.REPO_TYPE,
1066 )
1069 )
1067 response = api_call(self, params)
1070 response = api_call(self, params)
1068 expected = 'user `%s` does not exist' % owner
1071 expected = 'user `%s` does not exist' % owner
1069 self._compare_error(id_, expected, given=response.body)
1072 self._compare_error(id_, expected, given=response.body)
1070
1073
1071 def test_api_create_repo_dont_specify_owner(self):
1074 def test_api_create_repo_dont_specify_owner(self):
1072 repo_name = u'api-repo'
1075 repo_name = u'api-repo'
1073 owner = 'i-dont-exist'
1076 owner = 'i-dont-exist'
1074 id_, params = _build_data(self.apikey, 'create_repo',
1077 id_, params = _build_data(self.apikey, 'create_repo',
1075 repo_name=repo_name,
1078 repo_name=repo_name,
1076 repo_type=self.REPO_TYPE,
1079 repo_type=self.REPO_TYPE,
1077 )
1080 )
1078 response = api_call(self, params)
1081 response = api_call(self, params)
1079
1082
1080 repo = RepoModel().get_by_repo_name(repo_name)
1083 repo = RepoModel().get_by_repo_name(repo_name)
1081 assert repo != None
1084 assert repo != None
1082 ret = {
1085 ret = {
1083 'msg': 'Created new repository `%s`' % repo_name,
1086 'msg': 'Created new repository `%s`' % repo_name,
1084 'success': True,
1087 'success': True,
1085 'task': None,
1088 'task': None,
1086 }
1089 }
1087 expected = ret
1090 expected = ret
1088 self._compare_ok(id_, expected, given=response.body)
1091 self._compare_ok(id_, expected, given=response.body)
1089 fixture.destroy_repo(repo_name)
1092 fixture.destroy_repo(repo_name)
1090
1093
1091 def test_api_create_repo_by_non_admin(self):
1094 def test_api_create_repo_by_non_admin(self):
1092 repo_name = u'api-repo'
1095 repo_name = u'api-repo'
1093 owner = 'i-dont-exist'
1096 owner = 'i-dont-exist'
1094 id_, params = _build_data(self.apikey_regular, 'create_repo',
1097 id_, params = _build_data(self.apikey_regular, 'create_repo',
1095 repo_name=repo_name,
1098 repo_name=repo_name,
1096 repo_type=self.REPO_TYPE,
1099 repo_type=self.REPO_TYPE,
1097 )
1100 )
1098 response = api_call(self, params)
1101 response = api_call(self, params)
1099
1102
1100 repo = RepoModel().get_by_repo_name(repo_name)
1103 repo = RepoModel().get_by_repo_name(repo_name)
1101 assert repo != None
1104 assert repo != None
1102 ret = {
1105 ret = {
1103 'msg': 'Created new repository `%s`' % repo_name,
1106 'msg': 'Created new repository `%s`' % repo_name,
1104 'success': True,
1107 'success': True,
1105 'task': None,
1108 'task': None,
1106 }
1109 }
1107 expected = ret
1110 expected = ret
1108 self._compare_ok(id_, expected, given=response.body)
1111 self._compare_ok(id_, expected, given=response.body)
1109 fixture.destroy_repo(repo_name)
1112 fixture.destroy_repo(repo_name)
1110
1113
1111 def test_api_create_repo_by_non_admin_specify_owner(self):
1114 def test_api_create_repo_by_non_admin_specify_owner(self):
1112 repo_name = u'api-repo'
1115 repo_name = u'api-repo'
1113 owner = 'i-dont-exist'
1116 owner = 'i-dont-exist'
1114 id_, params = _build_data(self.apikey_regular, 'create_repo',
1117 id_, params = _build_data(self.apikey_regular, 'create_repo',
1115 repo_name=repo_name,
1118 repo_name=repo_name,
1116 repo_type=self.REPO_TYPE,
1119 repo_type=self.REPO_TYPE,
1117 owner=owner)
1120 owner=owner)
1118 response = api_call(self, params)
1121 response = api_call(self, params)
1119
1122
1120 expected = 'Only Kallithea admin can specify `owner` param'
1123 expected = 'Only Kallithea admin can specify `owner` param'
1121 self._compare_error(id_, expected, given=response.body)
1124 self._compare_error(id_, expected, given=response.body)
1122 fixture.destroy_repo(repo_name)
1125 fixture.destroy_repo(repo_name)
1123
1126
1124 def test_api_create_repo_exists(self):
1127 def test_api_create_repo_exists(self):
1125 repo_name = self.REPO
1128 repo_name = self.REPO
1126 id_, params = _build_data(self.apikey, 'create_repo',
1129 id_, params = _build_data(self.apikey, 'create_repo',
1127 repo_name=repo_name,
1130 repo_name=repo_name,
1128 owner=TEST_USER_ADMIN_LOGIN,
1131 owner=TEST_USER_ADMIN_LOGIN,
1129 repo_type=self.REPO_TYPE,)
1132 repo_type=self.REPO_TYPE,)
1130 response = api_call(self, params)
1133 response = api_call(self, params)
1131 expected = "repo `%s` already exist" % repo_name
1134 expected = "repo `%s` already exist" % repo_name
1132 self._compare_error(id_, expected, given=response.body)
1135 self._compare_error(id_, expected, given=response.body)
1133
1136
1134 @mock.patch.object(RepoModel, 'create', crash)
1137 @mock.patch.object(RepoModel, 'create', crash)
1135 def test_api_create_repo_exception_occurred(self):
1138 def test_api_create_repo_exception_occurred(self):
1136 repo_name = u'api-repo'
1139 repo_name = u'api-repo'
1137 id_, params = _build_data(self.apikey, 'create_repo',
1140 id_, params = _build_data(self.apikey, 'create_repo',
1138 repo_name=repo_name,
1141 repo_name=repo_name,
1139 owner=TEST_USER_ADMIN_LOGIN,
1142 owner=TEST_USER_ADMIN_LOGIN,
1140 repo_type=self.REPO_TYPE,)
1143 repo_type=self.REPO_TYPE,)
1141 response = api_call(self, params)
1144 response = api_call(self, params)
1142 expected = 'failed to create repository `%s`' % repo_name
1145 expected = 'failed to create repository `%s`' % repo_name
1143 self._compare_error(id_, expected, given=response.body)
1146 self._compare_error(id_, expected, given=response.body)
1144
1147
1145 @parametrize('changing_attr,updates', [
1148 @parametrize('changing_attr,updates', [
1146 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1149 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1147 ('description', {'description': u'new description'}),
1150 ('description', {'description': u'new description'}),
1148 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1151 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1149 ('clone_uri', {'clone_uri': None}),
1152 ('clone_uri', {'clone_uri': None}),
1150 ('landing_rev', {'landing_rev': 'branch:master'}),
1153 ('landing_rev', {'landing_rev': 'branch:master'}),
1151 ('enable_statistics', {'enable_statistics': True}),
1154 ('enable_statistics', {'enable_statistics': True}),
1152 ('enable_locking', {'enable_locking': True}),
1155 ('enable_locking', {'enable_locking': True}),
1153 ('enable_downloads', {'enable_downloads': True}),
1156 ('enable_downloads', {'enable_downloads': True}),
1154 ('name', {'name': u'new_repo_name'}),
1157 ('name', {'name': u'new_repo_name'}),
1155 ('repo_group', {'group': u'test_group_for_update'}),
1158 ('repo_group', {'group': u'test_group_for_update'}),
1156 ])
1159 ])
1157 def test_api_update_repo(self, changing_attr, updates):
1160 def test_api_update_repo(self, changing_attr, updates):
1158 repo_name = u'api_update_me'
1161 repo_name = u'api_update_me'
1159 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1162 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1160 if changing_attr == 'repo_group':
1163 if changing_attr == 'repo_group':
1161 fixture.create_repo_group(updates['group'])
1164 fixture.create_repo_group(updates['group'])
1162
1165
1163 id_, params = _build_data(self.apikey, 'update_repo',
1166 id_, params = _build_data(self.apikey, 'update_repo',
1164 repoid=repo_name, **updates)
1167 repoid=repo_name, **updates)
1165 response = api_call(self, params)
1168 response = api_call(self, params)
1166 if changing_attr == 'name':
1169 if changing_attr == 'name':
1167 repo_name = updates['name']
1170 repo_name = updates['name']
1168 if changing_attr == 'repo_group':
1171 if changing_attr == 'repo_group':
1169 repo_name = u'/'.join([updates['group'], repo_name])
1172 repo_name = u'/'.join([updates['group'], repo_name])
1170 try:
1173 try:
1171 expected = {
1174 expected = {
1172 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1175 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1173 'repository': repo.get_api_data()
1176 'repository': repo.get_api_data()
1174 }
1177 }
1175 self._compare_ok(id_, expected, given=response.body)
1178 self._compare_ok(id_, expected, given=response.body)
1176 finally:
1179 finally:
1177 fixture.destroy_repo(repo_name)
1180 fixture.destroy_repo(repo_name)
1178 if changing_attr == 'repo_group':
1181 if changing_attr == 'repo_group':
1179 fixture.destroy_repo_group(updates['group'])
1182 fixture.destroy_repo_group(updates['group'])
1180
1183
1181 @parametrize('changing_attr,updates', [
1184 @parametrize('changing_attr,updates', [
1182 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1185 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1183 ('description', {'description': u'new description'}),
1186 ('description', {'description': u'new description'}),
1184 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1187 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1185 ('clone_uri', {'clone_uri': None}),
1188 ('clone_uri', {'clone_uri': None}),
1186 ('landing_rev', {'landing_rev': 'branch:master'}),
1189 ('landing_rev', {'landing_rev': 'branch:master'}),
1187 ('enable_statistics', {'enable_statistics': True}),
1190 ('enable_statistics', {'enable_statistics': True}),
1188 ('enable_locking', {'enable_locking': True}),
1191 ('enable_locking', {'enable_locking': True}),
1189 ('enable_downloads', {'enable_downloads': True}),
1192 ('enable_downloads', {'enable_downloads': True}),
1190 ('name', {'name': u'new_repo_name'}),
1193 ('name', {'name': u'new_repo_name'}),
1191 ('repo_group', {'group': u'test_group_for_update'}),
1194 ('repo_group', {'group': u'test_group_for_update'}),
1192 ])
1195 ])
1193 def test_api_update_group_repo(self, changing_attr, updates):
1196 def test_api_update_group_repo(self, changing_attr, updates):
1194 group_name = u'lololo'
1197 group_name = u'lololo'
1195 fixture.create_repo_group(group_name)
1198 fixture.create_repo_group(group_name)
1196 repo_name = u'%s/api_update_me' % group_name
1199 repo_name = u'%s/api_update_me' % group_name
1197 repo = fixture.create_repo(repo_name, repo_group=group_name, repo_type=self.REPO_TYPE)
1200 repo = fixture.create_repo(repo_name, repo_group=group_name, repo_type=self.REPO_TYPE)
1198 if changing_attr == 'repo_group':
1201 if changing_attr == 'repo_group':
1199 fixture.create_repo_group(updates['group'])
1202 fixture.create_repo_group(updates['group'])
1200
1203
1201 id_, params = _build_data(self.apikey, 'update_repo',
1204 id_, params = _build_data(self.apikey, 'update_repo',
1202 repoid=repo_name, **updates)
1205 repoid=repo_name, **updates)
1203 response = api_call(self, params)
1206 response = api_call(self, params)
1204 if changing_attr == 'name':
1207 if changing_attr == 'name':
1205 repo_name = u'%s/%s' % (group_name, updates['name'])
1208 repo_name = u'%s/%s' % (group_name, updates['name'])
1206 if changing_attr == 'repo_group':
1209 if changing_attr == 'repo_group':
1207 repo_name = u'/'.join([updates['group'], repo_name.rsplit('/', 1)[-1]])
1210 repo_name = u'/'.join([updates['group'], repo_name.rsplit('/', 1)[-1]])
1208 try:
1211 try:
1209 expected = {
1212 expected = {
1210 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1213 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1211 'repository': repo.get_api_data()
1214 'repository': repo.get_api_data()
1212 }
1215 }
1213 self._compare_ok(id_, expected, given=response.body)
1216 self._compare_ok(id_, expected, given=response.body)
1214 finally:
1217 finally:
1215 fixture.destroy_repo(repo_name)
1218 fixture.destroy_repo(repo_name)
1216 if changing_attr == 'repo_group':
1219 if changing_attr == 'repo_group':
1217 fixture.destroy_repo_group(updates['group'])
1220 fixture.destroy_repo_group(updates['group'])
1218 fixture.destroy_repo_group(group_name)
1221 fixture.destroy_repo_group(group_name)
1219
1222
1220 def test_api_update_repo_repo_group_does_not_exist(self):
1223 def test_api_update_repo_repo_group_does_not_exist(self):
1221 repo_name = u'admin_owned'
1224 repo_name = u'admin_owned'
1222 fixture.create_repo(repo_name)
1225 fixture.create_repo(repo_name)
1223 updates = {'group': 'test_group_for_update'}
1226 updates = {'group': 'test_group_for_update'}
1224 id_, params = _build_data(self.apikey, 'update_repo',
1227 id_, params = _build_data(self.apikey, 'update_repo',
1225 repoid=repo_name, **updates)
1228 repoid=repo_name, **updates)
1226 response = api_call(self, params)
1229 response = api_call(self, params)
1227 try:
1230 try:
1228 expected = 'repository group `%s` does not exist' % updates['group']
1231 expected = 'repository group `%s` does not exist' % updates['group']
1229 self._compare_error(id_, expected, given=response.body)
1232 self._compare_error(id_, expected, given=response.body)
1230 finally:
1233 finally:
1231 fixture.destroy_repo(repo_name)
1234 fixture.destroy_repo(repo_name)
1232
1235
1233 def test_api_update_repo_regular_user_not_allowed(self):
1236 def test_api_update_repo_regular_user_not_allowed(self):
1234 repo_name = u'admin_owned'
1237 repo_name = u'admin_owned'
1235 fixture.create_repo(repo_name)
1238 fixture.create_repo(repo_name)
1236 updates = {'description': 'something else'}
1239 updates = {'description': 'something else'}
1237 id_, params = _build_data(self.apikey_regular, 'update_repo',
1240 id_, params = _build_data(self.apikey_regular, 'update_repo',
1238 repoid=repo_name, **updates)
1241 repoid=repo_name, **updates)
1239 response = api_call(self, params)
1242 response = api_call(self, params)
1240 try:
1243 try:
1241 expected = 'repository `%s` does not exist' % repo_name
1244 expected = 'repository `%s` does not exist' % repo_name
1242 self._compare_error(id_, expected, given=response.body)
1245 self._compare_error(id_, expected, given=response.body)
1243 finally:
1246 finally:
1244 fixture.destroy_repo(repo_name)
1247 fixture.destroy_repo(repo_name)
1245
1248
1246 @mock.patch.object(RepoModel, 'update', crash)
1249 @mock.patch.object(RepoModel, 'update', crash)
1247 def test_api_update_repo_exception_occurred(self):
1250 def test_api_update_repo_exception_occurred(self):
1248 repo_name = u'api_update_me'
1251 repo_name = u'api_update_me'
1249 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1252 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1250 id_, params = _build_data(self.apikey, 'update_repo',
1253 id_, params = _build_data(self.apikey, 'update_repo',
1251 repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
1254 repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
1252 response = api_call(self, params)
1255 response = api_call(self, params)
1253 try:
1256 try:
1254 expected = 'failed to update repo `%s`' % repo_name
1257 expected = 'failed to update repo `%s`' % repo_name
1255 self._compare_error(id_, expected, given=response.body)
1258 self._compare_error(id_, expected, given=response.body)
1256 finally:
1259 finally:
1257 fixture.destroy_repo(repo_name)
1260 fixture.destroy_repo(repo_name)
1258
1261
1259 def test_api_update_repo_regular_user_change_repo_name(self):
1262 def test_api_update_repo_regular_user_change_repo_name(self):
1260 repo_name = u'admin_owned'
1263 repo_name = u'admin_owned'
1261 new_repo_name = u'new_repo_name'
1264 new_repo_name = u'new_repo_name'
1262 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1265 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1263 RepoModel().grant_user_permission(repo=repo_name,
1266 RepoModel().grant_user_permission(repo=repo_name,
1264 user=self.TEST_USER_LOGIN,
1267 user=self.TEST_USER_LOGIN,
1265 perm='repository.admin')
1268 perm='repository.admin')
1266 UserModel().revoke_perm('default', 'hg.create.repository')
1269 UserModel().revoke_perm('default', 'hg.create.repository')
1267 UserModel().grant_perm('default', 'hg.create.none')
1270 UserModel().grant_perm('default', 'hg.create.none')
1268 updates = {'name': new_repo_name}
1271 updates = {'name': new_repo_name}
1269 id_, params = _build_data(self.apikey_regular, 'update_repo',
1272 id_, params = _build_data(self.apikey_regular, 'update_repo',
1270 repoid=repo_name, **updates)
1273 repoid=repo_name, **updates)
1271 response = api_call(self, params)
1274 response = api_call(self, params)
1272 try:
1275 try:
1273 expected = 'no permission to create (or move) repositories'
1276 expected = 'no permission to create (or move) repositories'
1274 self._compare_error(id_, expected, given=response.body)
1277 self._compare_error(id_, expected, given=response.body)
1275 finally:
1278 finally:
1276 fixture.destroy_repo(repo_name)
1279 fixture.destroy_repo(repo_name)
1277 fixture.destroy_repo(new_repo_name)
1280 fixture.destroy_repo(new_repo_name)
1278
1281
1279 def test_api_update_repo_regular_user_change_repo_name_allowed(self):
1282 def test_api_update_repo_regular_user_change_repo_name_allowed(self):
1280 repo_name = u'admin_owned'
1283 repo_name = u'admin_owned'
1281 new_repo_name = u'new_repo_name'
1284 new_repo_name = u'new_repo_name'
1282 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1285 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1283 RepoModel().grant_user_permission(repo=repo_name,
1286 RepoModel().grant_user_permission(repo=repo_name,
1284 user=self.TEST_USER_LOGIN,
1287 user=self.TEST_USER_LOGIN,
1285 perm='repository.admin')
1288 perm='repository.admin')
1286 UserModel().revoke_perm('default', 'hg.create.none')
1289 UserModel().revoke_perm('default', 'hg.create.none')
1287 UserModel().grant_perm('default', 'hg.create.repository')
1290 UserModel().grant_perm('default', 'hg.create.repository')
1288 updates = {'name': new_repo_name}
1291 updates = {'name': new_repo_name}
1289 id_, params = _build_data(self.apikey_regular, 'update_repo',
1292 id_, params = _build_data(self.apikey_regular, 'update_repo',
1290 repoid=repo_name, **updates)
1293 repoid=repo_name, **updates)
1291 response = api_call(self, params)
1294 response = api_call(self, params)
1292 try:
1295 try:
1293 expected = {
1296 expected = {
1294 'msg': 'updated repo ID:%s %s' % (repo.repo_id, new_repo_name),
1297 'msg': 'updated repo ID:%s %s' % (repo.repo_id, new_repo_name),
1295 'repository': repo.get_api_data()
1298 'repository': repo.get_api_data()
1296 }
1299 }
1297 self._compare_ok(id_, expected, given=response.body)
1300 self._compare_ok(id_, expected, given=response.body)
1298 finally:
1301 finally:
1299 fixture.destroy_repo(repo_name)
1302 fixture.destroy_repo(repo_name)
1300 fixture.destroy_repo(new_repo_name)
1303 fixture.destroy_repo(new_repo_name)
1301
1304
1302 def test_api_update_repo_regular_user_change_owner(self):
1305 def test_api_update_repo_regular_user_change_owner(self):
1303 repo_name = u'admin_owned'
1306 repo_name = u'admin_owned'
1304 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1307 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1305 RepoModel().grant_user_permission(repo=repo_name,
1308 RepoModel().grant_user_permission(repo=repo_name,
1306 user=self.TEST_USER_LOGIN,
1309 user=self.TEST_USER_LOGIN,
1307 perm='repository.admin')
1310 perm='repository.admin')
1308 updates = {'owner': TEST_USER_ADMIN_LOGIN}
1311 updates = {'owner': TEST_USER_ADMIN_LOGIN}
1309 id_, params = _build_data(self.apikey_regular, 'update_repo',
1312 id_, params = _build_data(self.apikey_regular, 'update_repo',
1310 repoid=repo_name, **updates)
1313 repoid=repo_name, **updates)
1311 response = api_call(self, params)
1314 response = api_call(self, params)
1312 try:
1315 try:
1313 expected = 'Only Kallithea admin can specify `owner` param'
1316 expected = 'Only Kallithea admin can specify `owner` param'
1314 self._compare_error(id_, expected, given=response.body)
1317 self._compare_error(id_, expected, given=response.body)
1315 finally:
1318 finally:
1316 fixture.destroy_repo(repo_name)
1319 fixture.destroy_repo(repo_name)
1317
1320
1318 def test_api_delete_repo(self):
1321 def test_api_delete_repo(self):
1319 repo_name = u'api_delete_me'
1322 repo_name = u'api_delete_me'
1320 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1323 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1321
1324
1322 id_, params = _build_data(self.apikey, 'delete_repo',
1325 id_, params = _build_data(self.apikey, 'delete_repo',
1323 repoid=repo_name, )
1326 repoid=repo_name, )
1324 response = api_call(self, params)
1327 response = api_call(self, params)
1325
1328
1326 ret = {
1329 ret = {
1327 'msg': 'Deleted repository `%s`' % repo_name,
1330 'msg': 'Deleted repository `%s`' % repo_name,
1328 'success': True
1331 'success': True
1329 }
1332 }
1330 try:
1333 try:
1331 expected = ret
1334 expected = ret
1332 self._compare_ok(id_, expected, given=response.body)
1335 self._compare_ok(id_, expected, given=response.body)
1333 finally:
1336 finally:
1334 fixture.destroy_repo(repo_name)
1337 fixture.destroy_repo(repo_name)
1335
1338
1336 def test_api_delete_repo_by_non_admin(self):
1339 def test_api_delete_repo_by_non_admin(self):
1337 repo_name = u'api_delete_me'
1340 repo_name = u'api_delete_me'
1338 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
1341 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
1339 cur_user=self.TEST_USER_LOGIN)
1342 cur_user=self.TEST_USER_LOGIN)
1340 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1343 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1341 repoid=repo_name, )
1344 repoid=repo_name, )
1342 response = api_call(self, params)
1345 response = api_call(self, params)
1343
1346
1344 ret = {
1347 ret = {
1345 'msg': 'Deleted repository `%s`' % repo_name,
1348 'msg': 'Deleted repository `%s`' % repo_name,
1346 'success': True
1349 'success': True
1347 }
1350 }
1348 try:
1351 try:
1349 expected = ret
1352 expected = ret
1350 self._compare_ok(id_, expected, given=response.body)
1353 self._compare_ok(id_, expected, given=response.body)
1351 finally:
1354 finally:
1352 fixture.destroy_repo(repo_name)
1355 fixture.destroy_repo(repo_name)
1353
1356
1354 def test_api_delete_repo_by_non_admin_no_permission(self):
1357 def test_api_delete_repo_by_non_admin_no_permission(self):
1355 repo_name = u'api_delete_me'
1358 repo_name = u'api_delete_me'
1356 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1359 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1357 try:
1360 try:
1358 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1361 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1359 repoid=repo_name, )
1362 repoid=repo_name, )
1360 response = api_call(self, params)
1363 response = api_call(self, params)
1361 expected = 'repository `%s` does not exist' % (repo_name)
1364 expected = 'repository `%s` does not exist' % (repo_name)
1362 self._compare_error(id_, expected, given=response.body)
1365 self._compare_error(id_, expected, given=response.body)
1363 finally:
1366 finally:
1364 fixture.destroy_repo(repo_name)
1367 fixture.destroy_repo(repo_name)
1365
1368
1366 def test_api_delete_repo_exception_occurred(self):
1369 def test_api_delete_repo_exception_occurred(self):
1367 repo_name = u'api_delete_me'
1370 repo_name = u'api_delete_me'
1368 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1371 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1369 try:
1372 try:
1370 with mock.patch.object(RepoModel, 'delete', crash):
1373 with mock.patch.object(RepoModel, 'delete', crash):
1371 id_, params = _build_data(self.apikey, 'delete_repo',
1374 id_, params = _build_data(self.apikey, 'delete_repo',
1372 repoid=repo_name, )
1375 repoid=repo_name, )
1373 response = api_call(self, params)
1376 response = api_call(self, params)
1374
1377
1375 expected = 'failed to delete repository `%s`' % repo_name
1378 expected = 'failed to delete repository `%s`' % repo_name
1376 self._compare_error(id_, expected, given=response.body)
1379 self._compare_error(id_, expected, given=response.body)
1377 finally:
1380 finally:
1378 fixture.destroy_repo(repo_name)
1381 fixture.destroy_repo(repo_name)
1379
1382
1380 def test_api_fork_repo(self):
1383 def test_api_fork_repo(self):
1381 fork_name = u'api-repo-fork'
1384 fork_name = u'api-repo-fork'
1382 id_, params = _build_data(self.apikey, 'fork_repo',
1385 id_, params = _build_data(self.apikey, 'fork_repo',
1383 repoid=self.REPO,
1386 repoid=self.REPO,
1384 fork_name=fork_name,
1387 fork_name=fork_name,
1385 owner=TEST_USER_ADMIN_LOGIN,
1388 owner=TEST_USER_ADMIN_LOGIN,
1386 )
1389 )
1387 response = api_call(self, params)
1390 response = api_call(self, params)
1388
1391
1389 ret = {
1392 ret = {
1390 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1393 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1391 fork_name),
1394 fork_name),
1392 'success': True,
1395 'success': True,
1393 'task': None,
1396 'task': None,
1394 }
1397 }
1395 expected = ret
1398 expected = ret
1396 self._compare_ok(id_, expected, given=response.body)
1399 self._compare_ok(id_, expected, given=response.body)
1397 fixture.destroy_repo(fork_name)
1400 fixture.destroy_repo(fork_name)
1398
1401
1399 @parametrize('fork_name', [
1402 @parametrize('fork_name', [
1400 u'api-repo-fork',
1403 u'api-repo-fork',
1401 u'%s/api-repo-fork' % TEST_REPO_GROUP,
1404 u'%s/api-repo-fork' % TEST_REPO_GROUP,
1402 ])
1405 ])
1403 def test_api_fork_repo_non_admin(self, fork_name):
1406 def test_api_fork_repo_non_admin(self, fork_name):
1404 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1407 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1405 repoid=self.REPO,
1408 repoid=self.REPO,
1406 fork_name=fork_name,
1409 fork_name=fork_name,
1407 )
1410 )
1408 response = api_call(self, params)
1411 response = api_call(self, params)
1409
1412
1410 ret = {
1413 ret = {
1411 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1414 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1412 fork_name),
1415 fork_name),
1413 'success': True,
1416 'success': True,
1414 'task': None,
1417 'task': None,
1415 }
1418 }
1416 expected = ret
1419 expected = ret
1417 self._compare_ok(id_, expected, given=response.body)
1420 self._compare_ok(id_, expected, given=response.body)
1418 fixture.destroy_repo(fork_name)
1421 fixture.destroy_repo(fork_name)
1419
1422
1420 def test_api_fork_repo_non_admin_specify_owner(self):
1423 def test_api_fork_repo_non_admin_specify_owner(self):
1421 fork_name = u'api-repo-fork'
1424 fork_name = u'api-repo-fork'
1422 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1425 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1423 repoid=self.REPO,
1426 repoid=self.REPO,
1424 fork_name=fork_name,
1427 fork_name=fork_name,
1425 owner=TEST_USER_ADMIN_LOGIN,
1428 owner=TEST_USER_ADMIN_LOGIN,
1426 )
1429 )
1427 response = api_call(self, params)
1430 response = api_call(self, params)
1428 expected = 'Only Kallithea admin can specify `owner` param'
1431 expected = 'Only Kallithea admin can specify `owner` param'
1429 self._compare_error(id_, expected, given=response.body)
1432 self._compare_error(id_, expected, given=response.body)
1430 fixture.destroy_repo(fork_name)
1433 fixture.destroy_repo(fork_name)
1431
1434
1432 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
1435 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
1433 RepoModel().grant_user_permission(repo=self.REPO,
1436 RepoModel().grant_user_permission(repo=self.REPO,
1434 user=self.TEST_USER_LOGIN,
1437 user=self.TEST_USER_LOGIN,
1435 perm='repository.none')
1438 perm='repository.none')
1436 fork_name = u'api-repo-fork'
1439 fork_name = u'api-repo-fork'
1437 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1440 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1438 repoid=self.REPO,
1441 repoid=self.REPO,
1439 fork_name=fork_name,
1442 fork_name=fork_name,
1440 )
1443 )
1441 response = api_call(self, params)
1444 response = api_call(self, params)
1442 expected = 'repository `%s` does not exist' % (self.REPO)
1445 expected = 'repository `%s` does not exist' % (self.REPO)
1443 self._compare_error(id_, expected, given=response.body)
1446 self._compare_error(id_, expected, given=response.body)
1444 fixture.destroy_repo(fork_name)
1447 fixture.destroy_repo(fork_name)
1445
1448
1446 @parametrize('name,perm', [
1449 @parametrize('name,perm', [
1447 ('read', 'repository.read'),
1450 ('read', 'repository.read'),
1448 ('write', 'repository.write'),
1451 ('write', 'repository.write'),
1449 ('admin', 'repository.admin'),
1452 ('admin', 'repository.admin'),
1450 ])
1453 ])
1451 def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
1454 def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
1452 fork_name = u'api-repo-fork'
1455 fork_name = u'api-repo-fork'
1453 # regardless of base repository permission, forking is disallowed
1456 # regardless of base repository permission, forking is disallowed
1454 # when repository creation is disabled
1457 # when repository creation is disabled
1455 RepoModel().grant_user_permission(repo=self.REPO,
1458 RepoModel().grant_user_permission(repo=self.REPO,
1456 user=self.TEST_USER_LOGIN,
1459 user=self.TEST_USER_LOGIN,
1457 perm=perm)
1460 perm=perm)
1458 UserModel().revoke_perm('default', 'hg.create.repository')
1461 UserModel().revoke_perm('default', 'hg.create.repository')
1459 UserModel().grant_perm('default', 'hg.create.none')
1462 UserModel().grant_perm('default', 'hg.create.none')
1460 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1463 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1461 repoid=self.REPO,
1464 repoid=self.REPO,
1462 fork_name=fork_name,
1465 fork_name=fork_name,
1463 )
1466 )
1464 response = api_call(self, params)
1467 response = api_call(self, params)
1465 expected = 'no permission to create repositories'
1468 expected = 'no permission to create repositories'
1466 self._compare_error(id_, expected, given=response.body)
1469 self._compare_error(id_, expected, given=response.body)
1467 fixture.destroy_repo(fork_name)
1470 fixture.destroy_repo(fork_name)
1468
1471
1469 def test_api_fork_repo_unknown_owner(self):
1472 def test_api_fork_repo_unknown_owner(self):
1470 fork_name = u'api-repo-fork'
1473 fork_name = u'api-repo-fork'
1471 owner = 'i-dont-exist'
1474 owner = 'i-dont-exist'
1472 id_, params = _build_data(self.apikey, 'fork_repo',
1475 id_, params = _build_data(self.apikey, 'fork_repo',
1473 repoid=self.REPO,
1476 repoid=self.REPO,
1474 fork_name=fork_name,
1477 fork_name=fork_name,
1475 owner=owner,
1478 owner=owner,
1476 )
1479 )
1477 response = api_call(self, params)
1480 response = api_call(self, params)
1478 expected = 'user `%s` does not exist' % owner
1481 expected = 'user `%s` does not exist' % owner
1479 self._compare_error(id_, expected, given=response.body)
1482 self._compare_error(id_, expected, given=response.body)
1480
1483
1481 def test_api_fork_repo_fork_exists(self):
1484 def test_api_fork_repo_fork_exists(self):
1482 fork_name = u'api-repo-fork'
1485 fork_name = u'api-repo-fork'
1483 fixture.create_fork(self.REPO, fork_name)
1486 fixture.create_fork(self.REPO, fork_name)
1484
1487
1485 try:
1488 try:
1486 fork_name = u'api-repo-fork'
1489 fork_name = u'api-repo-fork'
1487
1490
1488 id_, params = _build_data(self.apikey, 'fork_repo',
1491 id_, params = _build_data(self.apikey, 'fork_repo',
1489 repoid=self.REPO,
1492 repoid=self.REPO,
1490 fork_name=fork_name,
1493 fork_name=fork_name,
1491 owner=TEST_USER_ADMIN_LOGIN,
1494 owner=TEST_USER_ADMIN_LOGIN,
1492 )
1495 )
1493 response = api_call(self, params)
1496 response = api_call(self, params)
1494
1497
1495 expected = "fork `%s` already exist" % fork_name
1498 expected = "fork `%s` already exist" % fork_name
1496 self._compare_error(id_, expected, given=response.body)
1499 self._compare_error(id_, expected, given=response.body)
1497 finally:
1500 finally:
1498 fixture.destroy_repo(fork_name)
1501 fixture.destroy_repo(fork_name)
1499
1502
1500 def test_api_fork_repo_repo_exists(self):
1503 def test_api_fork_repo_repo_exists(self):
1501 fork_name = self.REPO
1504 fork_name = self.REPO
1502
1505
1503 id_, params = _build_data(self.apikey, 'fork_repo',
1506 id_, params = _build_data(self.apikey, 'fork_repo',
1504 repoid=self.REPO,
1507 repoid=self.REPO,
1505 fork_name=fork_name,
1508 fork_name=fork_name,
1506 owner=TEST_USER_ADMIN_LOGIN,
1509 owner=TEST_USER_ADMIN_LOGIN,
1507 )
1510 )
1508 response = api_call(self, params)
1511 response = api_call(self, params)
1509
1512
1510 expected = "repo `%s` already exist" % fork_name
1513 expected = "repo `%s` already exist" % fork_name
1511 self._compare_error(id_, expected, given=response.body)
1514 self._compare_error(id_, expected, given=response.body)
1512
1515
1513 @mock.patch.object(RepoModel, 'create_fork', crash)
1516 @mock.patch.object(RepoModel, 'create_fork', crash)
1514 def test_api_fork_repo_exception_occurred(self):
1517 def test_api_fork_repo_exception_occurred(self):
1515 fork_name = u'api-repo-fork'
1518 fork_name = u'api-repo-fork'
1516 id_, params = _build_data(self.apikey, 'fork_repo',
1519 id_, params = _build_data(self.apikey, 'fork_repo',
1517 repoid=self.REPO,
1520 repoid=self.REPO,
1518 fork_name=fork_name,
1521 fork_name=fork_name,
1519 owner=TEST_USER_ADMIN_LOGIN,
1522 owner=TEST_USER_ADMIN_LOGIN,
1520 )
1523 )
1521 response = api_call(self, params)
1524 response = api_call(self, params)
1522
1525
1523 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
1526 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
1524 fork_name)
1527 fork_name)
1525 self._compare_error(id_, expected, given=response.body)
1528 self._compare_error(id_, expected, given=response.body)
1526
1529
1527 def test_api_get_user_group(self):
1530 def test_api_get_user_group(self):
1528 id_, params = _build_data(self.apikey, 'get_user_group',
1531 id_, params = _build_data(self.apikey, 'get_user_group',
1529 usergroupid=TEST_USER_GROUP)
1532 usergroupid=TEST_USER_GROUP)
1530 response = api_call(self, params)
1533 response = api_call(self, params)
1531
1534
1532 user_group = UserGroupModel().get_group(TEST_USER_GROUP)
1535 user_group = UserGroupModel().get_group(TEST_USER_GROUP)
1533 members = []
1536 members = []
1534 for user in user_group.members:
1537 for user in user_group.members:
1535 user = user.user
1538 user = user.user
1536 members.append(user.get_api_data())
1539 members.append(user.get_api_data())
1537
1540
1538 ret = user_group.get_api_data()
1541 ret = user_group.get_api_data()
1539 ret['members'] = members
1542 ret['members'] = members
1540 expected = ret
1543 expected = ret
1541 self._compare_ok(id_, expected, given=response.body)
1544 self._compare_ok(id_, expected, given=response.body)
1542
1545
1543 def test_api_get_user_groups(self):
1546 def test_api_get_user_groups(self):
1544 gr_name = u'test_user_group2'
1547 gr_name = u'test_user_group2'
1545 make_user_group(gr_name)
1548 make_user_group(gr_name)
1546
1549
1547 try:
1550 try:
1548 id_, params = _build_data(self.apikey, 'get_user_groups', )
1551 id_, params = _build_data(self.apikey, 'get_user_groups', )
1549 response = api_call(self, params)
1552 response = api_call(self, params)
1550
1553
1551 expected = []
1554 expected = []
1552 for gr_name in [TEST_USER_GROUP, u'test_user_group2']:
1555 for gr_name in [TEST_USER_GROUP, u'test_user_group2']:
1553 user_group = UserGroupModel().get_group(gr_name)
1556 user_group = UserGroupModel().get_group(gr_name)
1554 ret = user_group.get_api_data()
1557 ret = user_group.get_api_data()
1555 expected.append(ret)
1558 expected.append(ret)
1556 self._compare_ok(id_, expected, given=response.body)
1559 self._compare_ok(id_, expected, given=response.body)
1557 finally:
1560 finally:
1558 fixture.destroy_user_group(gr_name)
1561 fixture.destroy_user_group(gr_name)
1559
1562
1560 def test_api_create_user_group(self):
1563 def test_api_create_user_group(self):
1561 group_name = u'some_new_group'
1564 group_name = u'some_new_group'
1562 id_, params = _build_data(self.apikey, 'create_user_group',
1565 id_, params = _build_data(self.apikey, 'create_user_group',
1563 group_name=group_name)
1566 group_name=group_name)
1564 response = api_call(self, params)
1567 response = api_call(self, params)
1565
1568
1566 ret = {
1569 ret = {
1567 'msg': 'created new user group `%s`' % group_name,
1570 'msg': 'created new user group `%s`' % group_name,
1568 'user_group': jsonify(UserGroupModel() \
1571 'user_group': jsonify(UserGroupModel() \
1569 .get_by_name(group_name) \
1572 .get_by_name(group_name) \
1570 .get_api_data())
1573 .get_api_data())
1571 }
1574 }
1572 expected = ret
1575 expected = ret
1573 self._compare_ok(id_, expected, given=response.body)
1576 self._compare_ok(id_, expected, given=response.body)
1574
1577
1575 fixture.destroy_user_group(group_name)
1578 fixture.destroy_user_group(group_name)
1576
1579
1577 def test_api_get_user_group_that_exist(self):
1580 def test_api_get_user_group_that_exist(self):
1578 id_, params = _build_data(self.apikey, 'create_user_group',
1581 id_, params = _build_data(self.apikey, 'create_user_group',
1579 group_name=TEST_USER_GROUP)
1582 group_name=TEST_USER_GROUP)
1580 response = api_call(self, params)
1583 response = api_call(self, params)
1581
1584
1582 expected = "user group `%s` already exist" % TEST_USER_GROUP
1585 expected = "user group `%s` already exist" % TEST_USER_GROUP
1583 self._compare_error(id_, expected, given=response.body)
1586 self._compare_error(id_, expected, given=response.body)
1584
1587
1585 @mock.patch.object(UserGroupModel, 'create', crash)
1588 @mock.patch.object(UserGroupModel, 'create', crash)
1586 def test_api_get_user_group_exception_occurred(self):
1589 def test_api_get_user_group_exception_occurred(self):
1587 group_name = u'exception_happens'
1590 group_name = u'exception_happens'
1588 id_, params = _build_data(self.apikey, 'create_user_group',
1591 id_, params = _build_data(self.apikey, 'create_user_group',
1589 group_name=group_name)
1592 group_name=group_name)
1590 response = api_call(self, params)
1593 response = api_call(self, params)
1591
1594
1592 expected = 'failed to create group `%s`' % group_name
1595 expected = 'failed to create group `%s`' % group_name
1593 self._compare_error(id_, expected, given=response.body)
1596 self._compare_error(id_, expected, given=response.body)
1594
1597
1595 @parametrize('changing_attr,updates', [
1598 @parametrize('changing_attr,updates', [
1596 ('group_name', {'group_name': u'new_group_name'}),
1599 ('group_name', {'group_name': u'new_group_name'}),
1597 ('group_name', {'group_name': u'test_group_for_update'}),
1600 ('group_name', {'group_name': u'test_group_for_update'}),
1598 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1601 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1599 ('active', {'active': False}),
1602 ('active', {'active': False}),
1600 ('active', {'active': True}),
1603 ('active', {'active': True}),
1601 ])
1604 ])
1602 def test_api_update_user_group(self, changing_attr, updates):
1605 def test_api_update_user_group(self, changing_attr, updates):
1603 gr_name = u'test_group_for_update'
1606 gr_name = u'test_group_for_update'
1604 user_group = fixture.create_user_group(gr_name)
1607 user_group = fixture.create_user_group(gr_name)
1605 try:
1608 try:
1606 id_, params = _build_data(self.apikey, 'update_user_group',
1609 id_, params = _build_data(self.apikey, 'update_user_group',
1607 usergroupid=gr_name, **updates)
1610 usergroupid=gr_name, **updates)
1608 response = api_call(self, params)
1611 response = api_call(self, params)
1609 expected = {
1612 expected = {
1610 'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
1613 'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
1611 user_group.users_group_name),
1614 user_group.users_group_name),
1612 'user_group': user_group.get_api_data()
1615 'user_group': user_group.get_api_data()
1613 }
1616 }
1614 self._compare_ok(id_, expected, given=response.body)
1617 self._compare_ok(id_, expected, given=response.body)
1615 finally:
1618 finally:
1616 if changing_attr == 'group_name':
1619 if changing_attr == 'group_name':
1617 # switch to updated name for proper cleanup
1620 # switch to updated name for proper cleanup
1618 gr_name = updates['group_name']
1621 gr_name = updates['group_name']
1619 fixture.destroy_user_group(gr_name)
1622 fixture.destroy_user_group(gr_name)
1620
1623
1621 @mock.patch.object(UserGroupModel, 'update', crash)
1624 @mock.patch.object(UserGroupModel, 'update', crash)
1622 def test_api_update_user_group_exception_occurred(self):
1625 def test_api_update_user_group_exception_occurred(self):
1623 gr_name = u'test_group'
1626 gr_name = u'test_group'
1624 fixture.create_user_group(gr_name)
1627 fixture.create_user_group(gr_name)
1625 try:
1628 try:
1626 id_, params = _build_data(self.apikey, 'update_user_group',
1629 id_, params = _build_data(self.apikey, 'update_user_group',
1627 usergroupid=gr_name)
1630 usergroupid=gr_name)
1628 response = api_call(self, params)
1631 response = api_call(self, params)
1629 expected = 'failed to update user group `%s`' % gr_name
1632 expected = 'failed to update user group `%s`' % gr_name
1630 self._compare_error(id_, expected, given=response.body)
1633 self._compare_error(id_, expected, given=response.body)
1631 finally:
1634 finally:
1632 fixture.destroy_user_group(gr_name)
1635 fixture.destroy_user_group(gr_name)
1633
1636
1634 def test_api_add_user_to_user_group(self):
1637 def test_api_add_user_to_user_group(self):
1635 gr_name = u'test_group'
1638 gr_name = u'test_group'
1636 fixture.create_user_group(gr_name)
1639 fixture.create_user_group(gr_name)
1637 try:
1640 try:
1638 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1641 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1639 usergroupid=gr_name,
1642 usergroupid=gr_name,
1640 userid=TEST_USER_ADMIN_LOGIN)
1643 userid=TEST_USER_ADMIN_LOGIN)
1641 response = api_call(self, params)
1644 response = api_call(self, params)
1642 expected = {
1645 expected = {
1643 'msg': 'added member `%s` to user group `%s`' % (
1646 'msg': 'added member `%s` to user group `%s`' % (
1644 TEST_USER_ADMIN_LOGIN, gr_name),
1647 TEST_USER_ADMIN_LOGIN, gr_name),
1645 'success': True
1648 'success': True
1646 }
1649 }
1647 self._compare_ok(id_, expected, given=response.body)
1650 self._compare_ok(id_, expected, given=response.body)
1648 finally:
1651 finally:
1649 fixture.destroy_user_group(gr_name)
1652 fixture.destroy_user_group(gr_name)
1650
1653
1651 def test_api_add_user_to_user_group_that_doesnt_exist(self):
1654 def test_api_add_user_to_user_group_that_doesnt_exist(self):
1652 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1655 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1653 usergroupid='false-group',
1656 usergroupid='false-group',
1654 userid=TEST_USER_ADMIN_LOGIN)
1657 userid=TEST_USER_ADMIN_LOGIN)
1655 response = api_call(self, params)
1658 response = api_call(self, params)
1656
1659
1657 expected = 'user group `%s` does not exist' % 'false-group'
1660 expected = 'user group `%s` does not exist' % 'false-group'
1658 self._compare_error(id_, expected, given=response.body)
1661 self._compare_error(id_, expected, given=response.body)
1659
1662
1660 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
1663 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
1661 def test_api_add_user_to_user_group_exception_occurred(self):
1664 def test_api_add_user_to_user_group_exception_occurred(self):
1662 gr_name = u'test_group'
1665 gr_name = u'test_group'
1663 fixture.create_user_group(gr_name)
1666 fixture.create_user_group(gr_name)
1664 try:
1667 try:
1665 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1668 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1666 usergroupid=gr_name,
1669 usergroupid=gr_name,
1667 userid=TEST_USER_ADMIN_LOGIN)
1670 userid=TEST_USER_ADMIN_LOGIN)
1668 response = api_call(self, params)
1671 response = api_call(self, params)
1669 expected = 'failed to add member to user group `%s`' % gr_name
1672 expected = 'failed to add member to user group `%s`' % gr_name
1670 self._compare_error(id_, expected, given=response.body)
1673 self._compare_error(id_, expected, given=response.body)
1671 finally:
1674 finally:
1672 fixture.destroy_user_group(gr_name)
1675 fixture.destroy_user_group(gr_name)
1673
1676
1674 def test_api_remove_user_from_user_group(self):
1677 def test_api_remove_user_from_user_group(self):
1675 gr_name = u'test_group_3'
1678 gr_name = u'test_group_3'
1676 gr = fixture.create_user_group(gr_name)
1679 gr = fixture.create_user_group(gr_name)
1677 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1680 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1678 Session().commit()
1681 Session().commit()
1679 try:
1682 try:
1680 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1683 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1681 usergroupid=gr_name,
1684 usergroupid=gr_name,
1682 userid=TEST_USER_ADMIN_LOGIN)
1685 userid=TEST_USER_ADMIN_LOGIN)
1683 response = api_call(self, params)
1686 response = api_call(self, params)
1684 expected = {
1687 expected = {
1685 'msg': 'removed member `%s` from user group `%s`' % (
1688 'msg': 'removed member `%s` from user group `%s`' % (
1686 TEST_USER_ADMIN_LOGIN, gr_name
1689 TEST_USER_ADMIN_LOGIN, gr_name
1687 ),
1690 ),
1688 'success': True}
1691 'success': True}
1689 self._compare_ok(id_, expected, given=response.body)
1692 self._compare_ok(id_, expected, given=response.body)
1690 finally:
1693 finally:
1691 fixture.destroy_user_group(gr_name)
1694 fixture.destroy_user_group(gr_name)
1692
1695
1693 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
1696 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
1694 def test_api_remove_user_from_user_group_exception_occurred(self):
1697 def test_api_remove_user_from_user_group_exception_occurred(self):
1695 gr_name = u'test_group_3'
1698 gr_name = u'test_group_3'
1696 gr = fixture.create_user_group(gr_name)
1699 gr = fixture.create_user_group(gr_name)
1697 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1700 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1698 Session().commit()
1701 Session().commit()
1699 try:
1702 try:
1700 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1703 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1701 usergroupid=gr_name,
1704 usergroupid=gr_name,
1702 userid=TEST_USER_ADMIN_LOGIN)
1705 userid=TEST_USER_ADMIN_LOGIN)
1703 response = api_call(self, params)
1706 response = api_call(self, params)
1704 expected = 'failed to remove member from user group `%s`' % gr_name
1707 expected = 'failed to remove member from user group `%s`' % gr_name
1705 self._compare_error(id_, expected, given=response.body)
1708 self._compare_error(id_, expected, given=response.body)
1706 finally:
1709 finally:
1707 fixture.destroy_user_group(gr_name)
1710 fixture.destroy_user_group(gr_name)
1708
1711
1709 def test_api_delete_user_group(self):
1712 def test_api_delete_user_group(self):
1710 gr_name = u'test_group'
1713 gr_name = u'test_group'
1711 ugroup = fixture.create_user_group(gr_name)
1714 ugroup = fixture.create_user_group(gr_name)
1712 gr_id = ugroup.users_group_id
1715 gr_id = ugroup.users_group_id
1713 try:
1716 try:
1714 id_, params = _build_data(self.apikey, 'delete_user_group',
1717 id_, params = _build_data(self.apikey, 'delete_user_group',
1715 usergroupid=gr_name)
1718 usergroupid=gr_name)
1716 response = api_call(self, params)
1719 response = api_call(self, params)
1717 expected = {
1720 expected = {
1718 'user_group': None,
1721 'user_group': None,
1719 'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
1722 'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
1720 }
1723 }
1721 self._compare_ok(id_, expected, given=response.body)
1724 self._compare_ok(id_, expected, given=response.body)
1722 finally:
1725 finally:
1723 if UserGroupModel().get_by_name(gr_name):
1726 if UserGroupModel().get_by_name(gr_name):
1724 fixture.destroy_user_group(gr_name)
1727 fixture.destroy_user_group(gr_name)
1725
1728
1726 def test_api_delete_user_group_that_is_assigned(self):
1729 def test_api_delete_user_group_that_is_assigned(self):
1727 gr_name = u'test_group'
1730 gr_name = u'test_group'
1728 ugroup = fixture.create_user_group(gr_name)
1731 ugroup = fixture.create_user_group(gr_name)
1729 gr_id = ugroup.users_group_id
1732 gr_id = ugroup.users_group_id
1730
1733
1731 ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
1734 ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
1732 msg = 'User Group assigned to %s' % ugr_to_perm.repository.repo_name
1735 msg = 'User Group assigned to %s' % ugr_to_perm.repository.repo_name
1733
1736
1734 try:
1737 try:
1735 id_, params = _build_data(self.apikey, 'delete_user_group',
1738 id_, params = _build_data(self.apikey, 'delete_user_group',
1736 usergroupid=gr_name)
1739 usergroupid=gr_name)
1737 response = api_call(self, params)
1740 response = api_call(self, params)
1738 expected = msg
1741 expected = msg
1739 self._compare_error(id_, expected, given=response.body)
1742 self._compare_error(id_, expected, given=response.body)
1740 finally:
1743 finally:
1741 if UserGroupModel().get_by_name(gr_name):
1744 if UserGroupModel().get_by_name(gr_name):
1742 fixture.destroy_user_group(gr_name)
1745 fixture.destroy_user_group(gr_name)
1743
1746
1744 def test_api_delete_user_group_exception_occurred(self):
1747 def test_api_delete_user_group_exception_occurred(self):
1745 gr_name = u'test_group'
1748 gr_name = u'test_group'
1746 ugroup = fixture.create_user_group(gr_name)
1749 ugroup = fixture.create_user_group(gr_name)
1747 gr_id = ugroup.users_group_id
1750 gr_id = ugroup.users_group_id
1748 id_, params = _build_data(self.apikey, 'delete_user_group',
1751 id_, params = _build_data(self.apikey, 'delete_user_group',
1749 usergroupid=gr_name)
1752 usergroupid=gr_name)
1750
1753
1751 try:
1754 try:
1752 with mock.patch.object(UserGroupModel, 'delete', crash):
1755 with mock.patch.object(UserGroupModel, 'delete', crash):
1753 response = api_call(self, params)
1756 response = api_call(self, params)
1754 expected = 'failed to delete user group ID:%s %s' % (gr_id, gr_name)
1757 expected = 'failed to delete user group ID:%s %s' % (gr_id, gr_name)
1755 self._compare_error(id_, expected, given=response.body)
1758 self._compare_error(id_, expected, given=response.body)
1756 finally:
1759 finally:
1757 fixture.destroy_user_group(gr_name)
1760 fixture.destroy_user_group(gr_name)
1758
1761
1759 @parametrize('name,perm', [
1762 @parametrize('name,perm', [
1760 ('none', 'repository.none'),
1763 ('none', 'repository.none'),
1761 ('read', 'repository.read'),
1764 ('read', 'repository.read'),
1762 ('write', 'repository.write'),
1765 ('write', 'repository.write'),
1763 ('admin', 'repository.admin'),
1766 ('admin', 'repository.admin'),
1764 ])
1767 ])
1765 def test_api_grant_user_permission(self, name, perm):
1768 def test_api_grant_user_permission(self, name, perm):
1766 id_, params = _build_data(self.apikey,
1769 id_, params = _build_data(self.apikey,
1767 'grant_user_permission',
1770 'grant_user_permission',
1768 repoid=self.REPO,
1771 repoid=self.REPO,
1769 userid=TEST_USER_ADMIN_LOGIN,
1772 userid=TEST_USER_ADMIN_LOGIN,
1770 perm=perm)
1773 perm=perm)
1771 response = api_call(self, params)
1774 response = api_call(self, params)
1772
1775
1773 ret = {
1776 ret = {
1774 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1777 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1775 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1778 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1776 ),
1779 ),
1777 'success': True
1780 'success': True
1778 }
1781 }
1779 expected = ret
1782 expected = ret
1780 self._compare_ok(id_, expected, given=response.body)
1783 self._compare_ok(id_, expected, given=response.body)
1781
1784
1782 def test_api_grant_user_permission_wrong_permission(self):
1785 def test_api_grant_user_permission_wrong_permission(self):
1783 perm = 'haha.no.permission'
1786 perm = 'haha.no.permission'
1784 id_, params = _build_data(self.apikey,
1787 id_, params = _build_data(self.apikey,
1785 'grant_user_permission',
1788 'grant_user_permission',
1786 repoid=self.REPO,
1789 repoid=self.REPO,
1787 userid=TEST_USER_ADMIN_LOGIN,
1790 userid=TEST_USER_ADMIN_LOGIN,
1788 perm=perm)
1791 perm=perm)
1789 response = api_call(self, params)
1792 response = api_call(self, params)
1790
1793
1791 expected = 'permission `%s` does not exist' % perm
1794 expected = 'permission `%s` does not exist' % perm
1792 self._compare_error(id_, expected, given=response.body)
1795 self._compare_error(id_, expected, given=response.body)
1793
1796
1794 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1797 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1795 def test_api_grant_user_permission_exception_when_adding(self):
1798 def test_api_grant_user_permission_exception_when_adding(self):
1796 perm = 'repository.read'
1799 perm = 'repository.read'
1797 id_, params = _build_data(self.apikey,
1800 id_, params = _build_data(self.apikey,
1798 'grant_user_permission',
1801 'grant_user_permission',
1799 repoid=self.REPO,
1802 repoid=self.REPO,
1800 userid=TEST_USER_ADMIN_LOGIN,
1803 userid=TEST_USER_ADMIN_LOGIN,
1801 perm=perm)
1804 perm=perm)
1802 response = api_call(self, params)
1805 response = api_call(self, params)
1803
1806
1804 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1807 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1805 TEST_USER_ADMIN_LOGIN, self.REPO
1808 TEST_USER_ADMIN_LOGIN, self.REPO
1806 )
1809 )
1807 self._compare_error(id_, expected, given=response.body)
1810 self._compare_error(id_, expected, given=response.body)
1808
1811
1809 def test_api_revoke_user_permission(self):
1812 def test_api_revoke_user_permission(self):
1810 id_, params = _build_data(self.apikey,
1813 id_, params = _build_data(self.apikey,
1811 'revoke_user_permission',
1814 'revoke_user_permission',
1812 repoid=self.REPO,
1815 repoid=self.REPO,
1813 userid=TEST_USER_ADMIN_LOGIN, )
1816 userid=TEST_USER_ADMIN_LOGIN, )
1814 response = api_call(self, params)
1817 response = api_call(self, params)
1815
1818
1816 expected = {
1819 expected = {
1817 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1820 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1818 TEST_USER_ADMIN_LOGIN, self.REPO
1821 TEST_USER_ADMIN_LOGIN, self.REPO
1819 ),
1822 ),
1820 'success': True
1823 'success': True
1821 }
1824 }
1822 self._compare_ok(id_, expected, given=response.body)
1825 self._compare_ok(id_, expected, given=response.body)
1823
1826
1824 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1827 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1825 def test_api_revoke_user_permission_exception_when_adding(self):
1828 def test_api_revoke_user_permission_exception_when_adding(self):
1826 id_, params = _build_data(self.apikey,
1829 id_, params = _build_data(self.apikey,
1827 'revoke_user_permission',
1830 'revoke_user_permission',
1828 repoid=self.REPO,
1831 repoid=self.REPO,
1829 userid=TEST_USER_ADMIN_LOGIN, )
1832 userid=TEST_USER_ADMIN_LOGIN, )
1830 response = api_call(self, params)
1833 response = api_call(self, params)
1831
1834
1832 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1835 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1833 TEST_USER_ADMIN_LOGIN, self.REPO
1836 TEST_USER_ADMIN_LOGIN, self.REPO
1834 )
1837 )
1835 self._compare_error(id_, expected, given=response.body)
1838 self._compare_error(id_, expected, given=response.body)
1836
1839
1837 @parametrize('name,perm', [
1840 @parametrize('name,perm', [
1838 ('none', 'repository.none'),
1841 ('none', 'repository.none'),
1839 ('read', 'repository.read'),
1842 ('read', 'repository.read'),
1840 ('write', 'repository.write'),
1843 ('write', 'repository.write'),
1841 ('admin', 'repository.admin'),
1844 ('admin', 'repository.admin'),
1842 ])
1845 ])
1843 def test_api_grant_user_group_permission(self, name, perm):
1846 def test_api_grant_user_group_permission(self, name, perm):
1844 id_, params = _build_data(self.apikey,
1847 id_, params = _build_data(self.apikey,
1845 'grant_user_group_permission',
1848 'grant_user_group_permission',
1846 repoid=self.REPO,
1849 repoid=self.REPO,
1847 usergroupid=TEST_USER_GROUP,
1850 usergroupid=TEST_USER_GROUP,
1848 perm=perm)
1851 perm=perm)
1849 response = api_call(self, params)
1852 response = api_call(self, params)
1850
1853
1851 ret = {
1854 ret = {
1852 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1855 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1853 perm, TEST_USER_GROUP, self.REPO
1856 perm, TEST_USER_GROUP, self.REPO
1854 ),
1857 ),
1855 'success': True
1858 'success': True
1856 }
1859 }
1857 expected = ret
1860 expected = ret
1858 self._compare_ok(id_, expected, given=response.body)
1861 self._compare_ok(id_, expected, given=response.body)
1859
1862
1860 def test_api_grant_user_group_permission_wrong_permission(self):
1863 def test_api_grant_user_group_permission_wrong_permission(self):
1861 perm = 'haha.no.permission'
1864 perm = 'haha.no.permission'
1862 id_, params = _build_data(self.apikey,
1865 id_, params = _build_data(self.apikey,
1863 'grant_user_group_permission',
1866 'grant_user_group_permission',
1864 repoid=self.REPO,
1867 repoid=self.REPO,
1865 usergroupid=TEST_USER_GROUP,
1868 usergroupid=TEST_USER_GROUP,
1866 perm=perm)
1869 perm=perm)
1867 response = api_call(self, params)
1870 response = api_call(self, params)
1868
1871
1869 expected = 'permission `%s` does not exist' % perm
1872 expected = 'permission `%s` does not exist' % perm
1870 self._compare_error(id_, expected, given=response.body)
1873 self._compare_error(id_, expected, given=response.body)
1871
1874
1872 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
1875 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
1873 def test_api_grant_user_group_permission_exception_when_adding(self):
1876 def test_api_grant_user_group_permission_exception_when_adding(self):
1874 perm = 'repository.read'
1877 perm = 'repository.read'
1875 id_, params = _build_data(self.apikey,
1878 id_, params = _build_data(self.apikey,
1876 'grant_user_group_permission',
1879 'grant_user_group_permission',
1877 repoid=self.REPO,
1880 repoid=self.REPO,
1878 usergroupid=TEST_USER_GROUP,
1881 usergroupid=TEST_USER_GROUP,
1879 perm=perm)
1882 perm=perm)
1880 response = api_call(self, params)
1883 response = api_call(self, params)
1881
1884
1882 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1885 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1883 TEST_USER_GROUP, self.REPO
1886 TEST_USER_GROUP, self.REPO
1884 )
1887 )
1885 self._compare_error(id_, expected, given=response.body)
1888 self._compare_error(id_, expected, given=response.body)
1886
1889
1887 def test_api_revoke_user_group_permission(self):
1890 def test_api_revoke_user_group_permission(self):
1888 RepoModel().grant_user_group_permission(repo=self.REPO,
1891 RepoModel().grant_user_group_permission(repo=self.REPO,
1889 group_name=TEST_USER_GROUP,
1892 group_name=TEST_USER_GROUP,
1890 perm='repository.read')
1893 perm='repository.read')
1891 Session().commit()
1894 Session().commit()
1892 id_, params = _build_data(self.apikey,
1895 id_, params = _build_data(self.apikey,
1893 'revoke_user_group_permission',
1896 'revoke_user_group_permission',
1894 repoid=self.REPO,
1897 repoid=self.REPO,
1895 usergroupid=TEST_USER_GROUP, )
1898 usergroupid=TEST_USER_GROUP, )
1896 response = api_call(self, params)
1899 response = api_call(self, params)
1897
1900
1898 expected = {
1901 expected = {
1899 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1902 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1900 TEST_USER_GROUP, self.REPO
1903 TEST_USER_GROUP, self.REPO
1901 ),
1904 ),
1902 'success': True
1905 'success': True
1903 }
1906 }
1904 self._compare_ok(id_, expected, given=response.body)
1907 self._compare_ok(id_, expected, given=response.body)
1905
1908
1906 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
1909 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
1907 def test_api_revoke_user_group_permission_exception_when_adding(self):
1910 def test_api_revoke_user_group_permission_exception_when_adding(self):
1908 id_, params = _build_data(self.apikey,
1911 id_, params = _build_data(self.apikey,
1909 'revoke_user_group_permission',
1912 'revoke_user_group_permission',
1910 repoid=self.REPO,
1913 repoid=self.REPO,
1911 usergroupid=TEST_USER_GROUP, )
1914 usergroupid=TEST_USER_GROUP, )
1912 response = api_call(self, params)
1915 response = api_call(self, params)
1913
1916
1914 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1917 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1915 TEST_USER_GROUP, self.REPO
1918 TEST_USER_GROUP, self.REPO
1916 )
1919 )
1917 self._compare_error(id_, expected, given=response.body)
1920 self._compare_error(id_, expected, given=response.body)
1918
1921
1919 @parametrize('name,perm,apply_to_children', [
1922 @parametrize('name,perm,apply_to_children', [
1920 ('none', 'group.none', 'none'),
1923 ('none', 'group.none', 'none'),
1921 ('read', 'group.read', 'none'),
1924 ('read', 'group.read', 'none'),
1922 ('write', 'group.write', 'none'),
1925 ('write', 'group.write', 'none'),
1923 ('admin', 'group.admin', 'none'),
1926 ('admin', 'group.admin', 'none'),
1924
1927
1925 ('none', 'group.none', 'all'),
1928 ('none', 'group.none', 'all'),
1926 ('read', 'group.read', 'all'),
1929 ('read', 'group.read', 'all'),
1927 ('write', 'group.write', 'all'),
1930 ('write', 'group.write', 'all'),
1928 ('admin', 'group.admin', 'all'),
1931 ('admin', 'group.admin', 'all'),
1929
1932
1930 ('none', 'group.none', 'repos'),
1933 ('none', 'group.none', 'repos'),
1931 ('read', 'group.read', 'repos'),
1934 ('read', 'group.read', 'repos'),
1932 ('write', 'group.write', 'repos'),
1935 ('write', 'group.write', 'repos'),
1933 ('admin', 'group.admin', 'repos'),
1936 ('admin', 'group.admin', 'repos'),
1934
1937
1935 ('none', 'group.none', 'groups'),
1938 ('none', 'group.none', 'groups'),
1936 ('read', 'group.read', 'groups'),
1939 ('read', 'group.read', 'groups'),
1937 ('write', 'group.write', 'groups'),
1940 ('write', 'group.write', 'groups'),
1938 ('admin', 'group.admin', 'groups'),
1941 ('admin', 'group.admin', 'groups'),
1939 ])
1942 ])
1940 def test_api_grant_user_permission_to_repo_group(self, name, perm, apply_to_children):
1943 def test_api_grant_user_permission_to_repo_group(self, name, perm, apply_to_children):
1941 id_, params = _build_data(self.apikey,
1944 id_, params = _build_data(self.apikey,
1942 'grant_user_permission_to_repo_group',
1945 'grant_user_permission_to_repo_group',
1943 repogroupid=TEST_REPO_GROUP,
1946 repogroupid=TEST_REPO_GROUP,
1944 userid=TEST_USER_ADMIN_LOGIN,
1947 userid=TEST_USER_ADMIN_LOGIN,
1945 perm=perm, apply_to_children=apply_to_children)
1948 perm=perm, apply_to_children=apply_to_children)
1946 response = api_call(self, params)
1949 response = api_call(self, params)
1947
1950
1948 ret = {
1951 ret = {
1949 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1952 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1950 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1953 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1951 ),
1954 ),
1952 'success': True
1955 'success': True
1953 }
1956 }
1954 expected = ret
1957 expected = ret
1955 self._compare_ok(id_, expected, given=response.body)
1958 self._compare_ok(id_, expected, given=response.body)
1956
1959
1957 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
1960 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
1958 ('none_fails', 'group.none', 'none', False, False),
1961 ('none_fails', 'group.none', 'none', False, False),
1959 ('read_fails', 'group.read', 'none', False, False),
1962 ('read_fails', 'group.read', 'none', False, False),
1960 ('write_fails', 'group.write', 'none', False, False),
1963 ('write_fails', 'group.write', 'none', False, False),
1961 ('admin_fails', 'group.admin', 'none', False, False),
1964 ('admin_fails', 'group.admin', 'none', False, False),
1962
1965
1963 # with granted perms
1966 # with granted perms
1964 ('none_ok', 'group.none', 'none', True, True),
1967 ('none_ok', 'group.none', 'none', True, True),
1965 ('read_ok', 'group.read', 'none', True, True),
1968 ('read_ok', 'group.read', 'none', True, True),
1966 ('write_ok', 'group.write', 'none', True, True),
1969 ('write_ok', 'group.write', 'none', True, True),
1967 ('admin_ok', 'group.admin', 'none', True, True),
1970 ('admin_ok', 'group.admin', 'none', True, True),
1968 ])
1971 ])
1969 def test_api_grant_user_permission_to_repo_group_by_regular_user(
1972 def test_api_grant_user_permission_to_repo_group_by_regular_user(
1970 self, name, perm, apply_to_children, grant_admin, access_ok):
1973 self, name, perm, apply_to_children, grant_admin, access_ok):
1971 if grant_admin:
1974 if grant_admin:
1972 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
1975 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
1973 self.TEST_USER_LOGIN,
1976 self.TEST_USER_LOGIN,
1974 'group.admin')
1977 'group.admin')
1975 Session().commit()
1978 Session().commit()
1976
1979
1977 id_, params = _build_data(self.apikey_regular,
1980 id_, params = _build_data(self.apikey_regular,
1978 'grant_user_permission_to_repo_group',
1981 'grant_user_permission_to_repo_group',
1979 repogroupid=TEST_REPO_GROUP,
1982 repogroupid=TEST_REPO_GROUP,
1980 userid=TEST_USER_ADMIN_LOGIN,
1983 userid=TEST_USER_ADMIN_LOGIN,
1981 perm=perm, apply_to_children=apply_to_children)
1984 perm=perm, apply_to_children=apply_to_children)
1982 response = api_call(self, params)
1985 response = api_call(self, params)
1983 if access_ok:
1986 if access_ok:
1984 ret = {
1987 ret = {
1985 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1988 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1986 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1989 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1987 ),
1990 ),
1988 'success': True
1991 'success': True
1989 }
1992 }
1990 expected = ret
1993 expected = ret
1991 self._compare_ok(id_, expected, given=response.body)
1994 self._compare_ok(id_, expected, given=response.body)
1992 else:
1995 else:
1993 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
1996 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
1994 self._compare_error(id_, expected, given=response.body)
1997 self._compare_error(id_, expected, given=response.body)
1995
1998
1996 def test_api_grant_user_permission_to_repo_group_wrong_permission(self):
1999 def test_api_grant_user_permission_to_repo_group_wrong_permission(self):
1997 perm = 'haha.no.permission'
2000 perm = 'haha.no.permission'
1998 id_, params = _build_data(self.apikey,
2001 id_, params = _build_data(self.apikey,
1999 'grant_user_permission_to_repo_group',
2002 'grant_user_permission_to_repo_group',
2000 repogroupid=TEST_REPO_GROUP,
2003 repogroupid=TEST_REPO_GROUP,
2001 userid=TEST_USER_ADMIN_LOGIN,
2004 userid=TEST_USER_ADMIN_LOGIN,
2002 perm=perm)
2005 perm=perm)
2003 response = api_call(self, params)
2006 response = api_call(self, params)
2004
2007
2005 expected = 'permission `%s` does not exist' % perm
2008 expected = 'permission `%s` does not exist' % perm
2006 self._compare_error(id_, expected, given=response.body)
2009 self._compare_error(id_, expected, given=response.body)
2007
2010
2008 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
2011 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
2009 def test_api_grant_user_permission_to_repo_group_exception_when_adding(self):
2012 def test_api_grant_user_permission_to_repo_group_exception_when_adding(self):
2010 perm = 'group.read'
2013 perm = 'group.read'
2011 id_, params = _build_data(self.apikey,
2014 id_, params = _build_data(self.apikey,
2012 'grant_user_permission_to_repo_group',
2015 'grant_user_permission_to_repo_group',
2013 repogroupid=TEST_REPO_GROUP,
2016 repogroupid=TEST_REPO_GROUP,
2014 userid=TEST_USER_ADMIN_LOGIN,
2017 userid=TEST_USER_ADMIN_LOGIN,
2015 perm=perm)
2018 perm=perm)
2016 response = api_call(self, params)
2019 response = api_call(self, params)
2017
2020
2018 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2021 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2019 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2022 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2020 )
2023 )
2021 self._compare_error(id_, expected, given=response.body)
2024 self._compare_error(id_, expected, given=response.body)
2022
2025
2023 @parametrize('name,apply_to_children', [
2026 @parametrize('name,apply_to_children', [
2024 ('none', 'none'),
2027 ('none', 'none'),
2025 ('all', 'all'),
2028 ('all', 'all'),
2026 ('repos', 'repos'),
2029 ('repos', 'repos'),
2027 ('groups', 'groups'),
2030 ('groups', 'groups'),
2028 ])
2031 ])
2029 def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
2032 def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
2030 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2033 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2031 user=TEST_USER_ADMIN_LOGIN,
2034 user=TEST_USER_ADMIN_LOGIN,
2032 perm='group.read',)
2035 perm='group.read',)
2033 Session().commit()
2036 Session().commit()
2034
2037
2035 id_, params = _build_data(self.apikey,
2038 id_, params = _build_data(self.apikey,
2036 'revoke_user_permission_from_repo_group',
2039 'revoke_user_permission_from_repo_group',
2037 repogroupid=TEST_REPO_GROUP,
2040 repogroupid=TEST_REPO_GROUP,
2038 userid=TEST_USER_ADMIN_LOGIN,
2041 userid=TEST_USER_ADMIN_LOGIN,
2039 apply_to_children=apply_to_children,)
2042 apply_to_children=apply_to_children,)
2040 response = api_call(self, params)
2043 response = api_call(self, params)
2041
2044
2042 expected = {
2045 expected = {
2043 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2046 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2044 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2047 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2045 ),
2048 ),
2046 'success': True
2049 'success': True
2047 }
2050 }
2048 self._compare_ok(id_, expected, given=response.body)
2051 self._compare_ok(id_, expected, given=response.body)
2049
2052
2050 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2053 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2051 ('none', 'none', False, False),
2054 ('none', 'none', False, False),
2052 ('all', 'all', False, False),
2055 ('all', 'all', False, False),
2053 ('repos', 'repos', False, False),
2056 ('repos', 'repos', False, False),
2054 ('groups', 'groups', False, False),
2057 ('groups', 'groups', False, False),
2055
2058
2056 # after granting admin rights
2059 # after granting admin rights
2057 ('none', 'none', False, False),
2060 ('none', 'none', False, False),
2058 ('all', 'all', False, False),
2061 ('all', 'all', False, False),
2059 ('repos', 'repos', False, False),
2062 ('repos', 'repos', False, False),
2060 ('groups', 'groups', False, False),
2063 ('groups', 'groups', False, False),
2061 ])
2064 ])
2062 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
2065 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
2063 self, name, apply_to_children, grant_admin, access_ok):
2066 self, name, apply_to_children, grant_admin, access_ok):
2064 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2067 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2065 user=TEST_USER_ADMIN_LOGIN,
2068 user=TEST_USER_ADMIN_LOGIN,
2066 perm='group.read',)
2069 perm='group.read',)
2067 Session().commit()
2070 Session().commit()
2068
2071
2069 if grant_admin:
2072 if grant_admin:
2070 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2073 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2071 self.TEST_USER_LOGIN,
2074 self.TEST_USER_LOGIN,
2072 'group.admin')
2075 'group.admin')
2073 Session().commit()
2076 Session().commit()
2074
2077
2075 id_, params = _build_data(self.apikey_regular,
2078 id_, params = _build_data(self.apikey_regular,
2076 'revoke_user_permission_from_repo_group',
2079 'revoke_user_permission_from_repo_group',
2077 repogroupid=TEST_REPO_GROUP,
2080 repogroupid=TEST_REPO_GROUP,
2078 userid=TEST_USER_ADMIN_LOGIN,
2081 userid=TEST_USER_ADMIN_LOGIN,
2079 apply_to_children=apply_to_children,)
2082 apply_to_children=apply_to_children,)
2080 response = api_call(self, params)
2083 response = api_call(self, params)
2081 if access_ok:
2084 if access_ok:
2082 expected = {
2085 expected = {
2083 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2086 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2084 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2087 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2085 ),
2088 ),
2086 'success': True
2089 'success': True
2087 }
2090 }
2088 self._compare_ok(id_, expected, given=response.body)
2091 self._compare_ok(id_, expected, given=response.body)
2089 else:
2092 else:
2090 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2093 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2091 self._compare_error(id_, expected, given=response.body)
2094 self._compare_error(id_, expected, given=response.body)
2092
2095
2093 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
2096 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
2094 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(self):
2097 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(self):
2095 id_, params = _build_data(self.apikey,
2098 id_, params = _build_data(self.apikey,
2096 'revoke_user_permission_from_repo_group',
2099 'revoke_user_permission_from_repo_group',
2097 repogroupid=TEST_REPO_GROUP,
2100 repogroupid=TEST_REPO_GROUP,
2098 userid=TEST_USER_ADMIN_LOGIN, )
2101 userid=TEST_USER_ADMIN_LOGIN, )
2099 response = api_call(self, params)
2102 response = api_call(self, params)
2100
2103
2101 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2104 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2102 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2105 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2103 )
2106 )
2104 self._compare_error(id_, expected, given=response.body)
2107 self._compare_error(id_, expected, given=response.body)
2105
2108
2106 @parametrize('name,perm,apply_to_children', [
2109 @parametrize('name,perm,apply_to_children', [
2107 ('none', 'group.none', 'none'),
2110 ('none', 'group.none', 'none'),
2108 ('read', 'group.read', 'none'),
2111 ('read', 'group.read', 'none'),
2109 ('write', 'group.write', 'none'),
2112 ('write', 'group.write', 'none'),
2110 ('admin', 'group.admin', 'none'),
2113 ('admin', 'group.admin', 'none'),
2111
2114
2112 ('none', 'group.none', 'all'),
2115 ('none', 'group.none', 'all'),
2113 ('read', 'group.read', 'all'),
2116 ('read', 'group.read', 'all'),
2114 ('write', 'group.write', 'all'),
2117 ('write', 'group.write', 'all'),
2115 ('admin', 'group.admin', 'all'),
2118 ('admin', 'group.admin', 'all'),
2116
2119
2117 ('none', 'group.none', 'repos'),
2120 ('none', 'group.none', 'repos'),
2118 ('read', 'group.read', 'repos'),
2121 ('read', 'group.read', 'repos'),
2119 ('write', 'group.write', 'repos'),
2122 ('write', 'group.write', 'repos'),
2120 ('admin', 'group.admin', 'repos'),
2123 ('admin', 'group.admin', 'repos'),
2121
2124
2122 ('none', 'group.none', 'groups'),
2125 ('none', 'group.none', 'groups'),
2123 ('read', 'group.read', 'groups'),
2126 ('read', 'group.read', 'groups'),
2124 ('write', 'group.write', 'groups'),
2127 ('write', 'group.write', 'groups'),
2125 ('admin', 'group.admin', 'groups'),
2128 ('admin', 'group.admin', 'groups'),
2126 ])
2129 ])
2127 def test_api_grant_user_group_permission_to_repo_group(self, name, perm, apply_to_children):
2130 def test_api_grant_user_group_permission_to_repo_group(self, name, perm, apply_to_children):
2128 id_, params = _build_data(self.apikey,
2131 id_, params = _build_data(self.apikey,
2129 'grant_user_group_permission_to_repo_group',
2132 'grant_user_group_permission_to_repo_group',
2130 repogroupid=TEST_REPO_GROUP,
2133 repogroupid=TEST_REPO_GROUP,
2131 usergroupid=TEST_USER_GROUP,
2134 usergroupid=TEST_USER_GROUP,
2132 perm=perm,
2135 perm=perm,
2133 apply_to_children=apply_to_children,)
2136 apply_to_children=apply_to_children,)
2134 response = api_call(self, params)
2137 response = api_call(self, params)
2135
2138
2136 ret = {
2139 ret = {
2137 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2140 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2138 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2141 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2139 ),
2142 ),
2140 'success': True
2143 'success': True
2141 }
2144 }
2142 expected = ret
2145 expected = ret
2143 self._compare_ok(id_, expected, given=response.body)
2146 self._compare_ok(id_, expected, given=response.body)
2144
2147
2145 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
2148 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
2146 ('none_fails', 'group.none', 'none', False, False),
2149 ('none_fails', 'group.none', 'none', False, False),
2147 ('read_fails', 'group.read', 'none', False, False),
2150 ('read_fails', 'group.read', 'none', False, False),
2148 ('write_fails', 'group.write', 'none', False, False),
2151 ('write_fails', 'group.write', 'none', False, False),
2149 ('admin_fails', 'group.admin', 'none', False, False),
2152 ('admin_fails', 'group.admin', 'none', False, False),
2150
2153
2151 # with granted perms
2154 # with granted perms
2152 ('none_ok', 'group.none', 'none', True, True),
2155 ('none_ok', 'group.none', 'none', True, True),
2153 ('read_ok', 'group.read', 'none', True, True),
2156 ('read_ok', 'group.read', 'none', True, True),
2154 ('write_ok', 'group.write', 'none', True, True),
2157 ('write_ok', 'group.write', 'none', True, True),
2155 ('admin_ok', 'group.admin', 'none', True, True),
2158 ('admin_ok', 'group.admin', 'none', True, True),
2156 ])
2159 ])
2157 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
2160 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
2158 self, name, perm, apply_to_children, grant_admin, access_ok):
2161 self, name, perm, apply_to_children, grant_admin, access_ok):
2159 if grant_admin:
2162 if grant_admin:
2160 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2163 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2161 self.TEST_USER_LOGIN,
2164 self.TEST_USER_LOGIN,
2162 'group.admin')
2165 'group.admin')
2163 Session().commit()
2166 Session().commit()
2164
2167
2165 id_, params = _build_data(self.apikey_regular,
2168 id_, params = _build_data(self.apikey_regular,
2166 'grant_user_group_permission_to_repo_group',
2169 'grant_user_group_permission_to_repo_group',
2167 repogroupid=TEST_REPO_GROUP,
2170 repogroupid=TEST_REPO_GROUP,
2168 usergroupid=TEST_USER_GROUP,
2171 usergroupid=TEST_USER_GROUP,
2169 perm=perm,
2172 perm=perm,
2170 apply_to_children=apply_to_children,)
2173 apply_to_children=apply_to_children,)
2171 response = api_call(self, params)
2174 response = api_call(self, params)
2172 if access_ok:
2175 if access_ok:
2173 ret = {
2176 ret = {
2174 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2177 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2175 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2178 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2176 ),
2179 ),
2177 'success': True
2180 'success': True
2178 }
2181 }
2179 expected = ret
2182 expected = ret
2180 self._compare_ok(id_, expected, given=response.body)
2183 self._compare_ok(id_, expected, given=response.body)
2181 else:
2184 else:
2182 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2185 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2183 self._compare_error(id_, expected, given=response.body)
2186 self._compare_error(id_, expected, given=response.body)
2184
2187
2185 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(self):
2188 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(self):
2186 perm = 'haha.no.permission'
2189 perm = 'haha.no.permission'
2187 id_, params = _build_data(self.apikey,
2190 id_, params = _build_data(self.apikey,
2188 'grant_user_group_permission_to_repo_group',
2191 'grant_user_group_permission_to_repo_group',
2189 repogroupid=TEST_REPO_GROUP,
2192 repogroupid=TEST_REPO_GROUP,
2190 usergroupid=TEST_USER_GROUP,
2193 usergroupid=TEST_USER_GROUP,
2191 perm=perm)
2194 perm=perm)
2192 response = api_call(self, params)
2195 response = api_call(self, params)
2193
2196
2194 expected = 'permission `%s` does not exist' % perm
2197 expected = 'permission `%s` does not exist' % perm
2195 self._compare_error(id_, expected, given=response.body)
2198 self._compare_error(id_, expected, given=response.body)
2196
2199
2197 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
2200 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
2198 def test_api_grant_user_group_permission_exception_when_adding_to_repo_group(self):
2201 def test_api_grant_user_group_permission_exception_when_adding_to_repo_group(self):
2199 perm = 'group.read'
2202 perm = 'group.read'
2200 id_, params = _build_data(self.apikey,
2203 id_, params = _build_data(self.apikey,
2201 'grant_user_group_permission_to_repo_group',
2204 'grant_user_group_permission_to_repo_group',
2202 repogroupid=TEST_REPO_GROUP,
2205 repogroupid=TEST_REPO_GROUP,
2203 usergroupid=TEST_USER_GROUP,
2206 usergroupid=TEST_USER_GROUP,
2204 perm=perm)
2207 perm=perm)
2205 response = api_call(self, params)
2208 response = api_call(self, params)
2206
2209
2207 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2210 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2208 TEST_USER_GROUP, TEST_REPO_GROUP
2211 TEST_USER_GROUP, TEST_REPO_GROUP
2209 )
2212 )
2210 self._compare_error(id_, expected, given=response.body)
2213 self._compare_error(id_, expected, given=response.body)
2211
2214
2212 @parametrize('name,apply_to_children', [
2215 @parametrize('name,apply_to_children', [
2213 ('none', 'none'),
2216 ('none', 'none'),
2214 ('all', 'all'),
2217 ('all', 'all'),
2215 ('repos', 'repos'),
2218 ('repos', 'repos'),
2216 ('groups', 'groups'),
2219 ('groups', 'groups'),
2217 ])
2220 ])
2218 def test_api_revoke_user_group_permission_from_repo_group(self, name, apply_to_children):
2221 def test_api_revoke_user_group_permission_from_repo_group(self, name, apply_to_children):
2219 RepoGroupModel().grant_user_group_permission(repo_group=TEST_REPO_GROUP,
2222 RepoGroupModel().grant_user_group_permission(repo_group=TEST_REPO_GROUP,
2220 group_name=TEST_USER_GROUP,
2223 group_name=TEST_USER_GROUP,
2221 perm='group.read',)
2224 perm='group.read',)
2222 Session().commit()
2225 Session().commit()
2223 id_, params = _build_data(self.apikey,
2226 id_, params = _build_data(self.apikey,
2224 'revoke_user_group_permission_from_repo_group',
2227 'revoke_user_group_permission_from_repo_group',
2225 repogroupid=TEST_REPO_GROUP,
2228 repogroupid=TEST_REPO_GROUP,
2226 usergroupid=TEST_USER_GROUP,
2229 usergroupid=TEST_USER_GROUP,
2227 apply_to_children=apply_to_children,)
2230 apply_to_children=apply_to_children,)
2228 response = api_call(self, params)
2231 response = api_call(self, params)
2229
2232
2230 expected = {
2233 expected = {
2231 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2234 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2232 apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2235 apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2233 ),
2236 ),
2234 'success': True
2237 'success': True
2235 }
2238 }
2236 self._compare_ok(id_, expected, given=response.body)
2239 self._compare_ok(id_, expected, given=response.body)
2237
2240
2238 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2241 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2239 ('none', 'none', False, False),
2242 ('none', 'none', False, False),
2240 ('all', 'all', False, False),
2243 ('all', 'all', False, False),
2241 ('repos', 'repos', False, False),
2244 ('repos', 'repos', False, False),
2242 ('groups', 'groups', False, False),
2245 ('groups', 'groups', False, False),
2243
2246
2244 # after granting admin rights
2247 # after granting admin rights
2245 ('none', 'none', False, False),
2248 ('none', 'none', False, False),
2246 ('all', 'all', False, False),
2249 ('all', 'all', False, False),
2247 ('repos', 'repos', False, False),
2250 ('repos', 'repos', False, False),
2248 ('groups', 'groups', False, False),
2251 ('groups', 'groups', False, False),
2249 ])
2252 ])
2250 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
2253 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
2251 self, name, apply_to_children, grant_admin, access_ok):
2254 self, name, apply_to_children, grant_admin, access_ok):
2252 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2255 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2253 user=TEST_USER_ADMIN_LOGIN,
2256 user=TEST_USER_ADMIN_LOGIN,
2254 perm='group.read',)
2257 perm='group.read',)
2255 Session().commit()
2258 Session().commit()
2256
2259
2257 if grant_admin:
2260 if grant_admin:
2258 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2261 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2259 self.TEST_USER_LOGIN,
2262 self.TEST_USER_LOGIN,
2260 'group.admin')
2263 'group.admin')
2261 Session().commit()
2264 Session().commit()
2262
2265
2263 id_, params = _build_data(self.apikey_regular,
2266 id_, params = _build_data(self.apikey_regular,
2264 'revoke_user_group_permission_from_repo_group',
2267 'revoke_user_group_permission_from_repo_group',
2265 repogroupid=TEST_REPO_GROUP,
2268 repogroupid=TEST_REPO_GROUP,
2266 usergroupid=TEST_USER_GROUP,
2269 usergroupid=TEST_USER_GROUP,
2267 apply_to_children=apply_to_children,)
2270 apply_to_children=apply_to_children,)
2268 response = api_call(self, params)
2271 response = api_call(self, params)
2269 if access_ok:
2272 if access_ok:
2270 expected = {
2273 expected = {
2271 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2274 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2272 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2275 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2273 ),
2276 ),
2274 'success': True
2277 'success': True
2275 }
2278 }
2276 self._compare_ok(id_, expected, given=response.body)
2279 self._compare_ok(id_, expected, given=response.body)
2277 else:
2280 else:
2278 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2281 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2279 self._compare_error(id_, expected, given=response.body)
2282 self._compare_error(id_, expected, given=response.body)
2280
2283
2281 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
2284 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
2282 def test_api_revoke_user_group_permission_from_repo_group_exception_when_adding(self):
2285 def test_api_revoke_user_group_permission_from_repo_group_exception_when_adding(self):
2283 id_, params = _build_data(self.apikey, 'revoke_user_group_permission_from_repo_group',
2286 id_, params = _build_data(self.apikey, 'revoke_user_group_permission_from_repo_group',
2284 repogroupid=TEST_REPO_GROUP,
2287 repogroupid=TEST_REPO_GROUP,
2285 usergroupid=TEST_USER_GROUP,)
2288 usergroupid=TEST_USER_GROUP,)
2286 response = api_call(self, params)
2289 response = api_call(self, params)
2287
2290
2288 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2291 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2289 TEST_USER_GROUP, TEST_REPO_GROUP
2292 TEST_USER_GROUP, TEST_REPO_GROUP
2290 )
2293 )
2291 self._compare_error(id_, expected, given=response.body)
2294 self._compare_error(id_, expected, given=response.body)
2292
2295
2293 def test_api_get_gist(self):
2296 def test_api_get_gist(self):
2294 gist = fixture.create_gist()
2297 gist = fixture.create_gist()
2295 gist_id = gist.gist_access_id
2298 gist_id = gist.gist_access_id
2296 gist_created_on = gist.created_on
2299 gist_created_on = gist.created_on
2297 id_, params = _build_data(self.apikey, 'get_gist',
2300 id_, params = _build_data(self.apikey, 'get_gist',
2298 gistid=gist_id, )
2301 gistid=gist_id, )
2299 response = api_call(self, params)
2302 response = api_call(self, params)
2300
2303
2301 expected = {
2304 expected = {
2302 'access_id': gist_id,
2305 'access_id': gist_id,
2303 'created_on': gist_created_on,
2306 'created_on': gist_created_on,
2304 'description': 'new-gist',
2307 'description': 'new-gist',
2305 'expires': -1.0,
2308 'expires': -1.0,
2306 'gist_id': int(gist_id),
2309 'gist_id': int(gist_id),
2307 'type': 'public',
2310 'type': 'public',
2308 'url': 'http://localhost:80/_admin/gists/%s' % gist_id
2311 'url': 'http://localhost:80/_admin/gists/%s' % gist_id
2309 }
2312 }
2310
2313
2311 self._compare_ok(id_, expected, given=response.body)
2314 self._compare_ok(id_, expected, given=response.body)
2312
2315
2313 def test_api_get_gist_that_does_not_exist(self):
2316 def test_api_get_gist_that_does_not_exist(self):
2314 id_, params = _build_data(self.apikey_regular, 'get_gist',
2317 id_, params = _build_data(self.apikey_regular, 'get_gist',
2315 gistid='12345', )
2318 gistid='12345', )
2316 response = api_call(self, params)
2319 response = api_call(self, params)
2317 expected = 'gist `%s` does not exist' % ('12345',)
2320 expected = 'gist `%s` does not exist' % ('12345',)
2318 self._compare_error(id_, expected, given=response.body)
2321 self._compare_error(id_, expected, given=response.body)
2319
2322
2320 def test_api_get_gist_private_gist_without_permission(self):
2323 def test_api_get_gist_private_gist_without_permission(self):
2321 gist = fixture.create_gist()
2324 gist = fixture.create_gist()
2322 gist_id = gist.gist_access_id
2325 gist_id = gist.gist_access_id
2323 gist_created_on = gist.created_on
2326 gist_created_on = gist.created_on
2324 id_, params = _build_data(self.apikey_regular, 'get_gist',
2327 id_, params = _build_data(self.apikey_regular, 'get_gist',
2325 gistid=gist_id, )
2328 gistid=gist_id, )
2326 response = api_call(self, params)
2329 response = api_call(self, params)
2327
2330
2328 expected = 'gist `%s` does not exist' % gist_id
2331 expected = 'gist `%s` does not exist' % gist_id
2329 self._compare_error(id_, expected, given=response.body)
2332 self._compare_error(id_, expected, given=response.body)
2330
2333
2331 def test_api_get_gists(self):
2334 def test_api_get_gists(self):
2332 fixture.create_gist()
2335 fixture.create_gist()
2333 fixture.create_gist()
2336 fixture.create_gist()
2334
2337
2335 id_, params = _build_data(self.apikey, 'get_gists')
2338 id_, params = _build_data(self.apikey, 'get_gists')
2336 response = api_call(self, params)
2339 response = api_call(self, params)
2337 expected = response.json
2340 expected = response.json
2338 assert len(response.json['result']) == 2
2341 assert len(response.json['result']) == 2
2339 #self._compare_ok(id_, expected, given=response.body)
2342 #self._compare_ok(id_, expected, given=response.body)
2340
2343
2341 def test_api_get_gists_regular_user(self):
2344 def test_api_get_gists_regular_user(self):
2342 # by admin
2345 # by admin
2343 fixture.create_gist()
2346 fixture.create_gist()
2344 fixture.create_gist()
2347 fixture.create_gist()
2345
2348
2346 # by reg user
2349 # by reg user
2347 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2350 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2348 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2351 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2349 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2352 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2350
2353
2351 id_, params = _build_data(self.apikey_regular, 'get_gists')
2354 id_, params = _build_data(self.apikey_regular, 'get_gists')
2352 response = api_call(self, params)
2355 response = api_call(self, params)
2353 expected = response.json
2356 expected = response.json
2354 assert len(response.json['result']) == 3
2357 assert len(response.json['result']) == 3
2355 #self._compare_ok(id_, expected, given=response.body)
2358 #self._compare_ok(id_, expected, given=response.body)
2356
2359
2357 def test_api_get_gists_only_for_regular_user(self):
2360 def test_api_get_gists_only_for_regular_user(self):
2358 # by admin
2361 # by admin
2359 fixture.create_gist()
2362 fixture.create_gist()
2360 fixture.create_gist()
2363 fixture.create_gist()
2361
2364
2362 # by reg user
2365 # by reg user
2363 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2366 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2364 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2367 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2365 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2368 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2366
2369
2367 id_, params = _build_data(self.apikey, 'get_gists',
2370 id_, params = _build_data(self.apikey, 'get_gists',
2368 userid=self.TEST_USER_LOGIN)
2371 userid=self.TEST_USER_LOGIN)
2369 response = api_call(self, params)
2372 response = api_call(self, params)
2370 expected = response.json
2373 expected = response.json
2371 assert len(response.json['result']) == 3
2374 assert len(response.json['result']) == 3
2372 #self._compare_ok(id_, expected, given=response.body)
2375 #self._compare_ok(id_, expected, given=response.body)
2373
2376
2374 def test_api_get_gists_regular_user_with_different_userid(self):
2377 def test_api_get_gists_regular_user_with_different_userid(self):
2375 id_, params = _build_data(self.apikey_regular, 'get_gists',
2378 id_, params = _build_data(self.apikey_regular, 'get_gists',
2376 userid=TEST_USER_ADMIN_LOGIN)
2379 userid=TEST_USER_ADMIN_LOGIN)
2377 response = api_call(self, params)
2380 response = api_call(self, params)
2378 expected = 'userid is not the same as your user'
2381 expected = 'userid is not the same as your user'
2379 self._compare_error(id_, expected, given=response.body)
2382 self._compare_error(id_, expected, given=response.body)
2380
2383
2381 def test_api_create_gist(self):
2384 def test_api_create_gist(self):
2382 id_, params = _build_data(self.apikey_regular, 'create_gist',
2385 id_, params = _build_data(self.apikey_regular, 'create_gist',
2383 lifetime=10,
2386 lifetime=10,
2384 description='foobar-gist',
2387 description='foobar-gist',
2385 gist_type='public',
2388 gist_type='public',
2386 files={'foobar': {'content': 'foo'}})
2389 files={'foobar': {'content': 'foo'}})
2387 response = api_call(self, params)
2390 response = api_call(self, params)
2388 response_json = response.json
2391 response_json = response.json
2389 expected = {
2392 expected = {
2390 'gist': {
2393 'gist': {
2391 'access_id': response_json['result']['gist']['access_id'],
2394 'access_id': response_json['result']['gist']['access_id'],
2392 'created_on': response_json['result']['gist']['created_on'],
2395 'created_on': response_json['result']['gist']['created_on'],
2393 'description': 'foobar-gist',
2396 'description': 'foobar-gist',
2394 'expires': response_json['result']['gist']['expires'],
2397 'expires': response_json['result']['gist']['expires'],
2395 'gist_id': response_json['result']['gist']['gist_id'],
2398 'gist_id': response_json['result']['gist']['gist_id'],
2396 'type': 'public',
2399 'type': 'public',
2397 'url': response_json['result']['gist']['url']
2400 'url': response_json['result']['gist']['url']
2398 },
2401 },
2399 'msg': 'created new gist'
2402 'msg': 'created new gist'
2400 }
2403 }
2401 self._compare_ok(id_, expected, given=response.body)
2404 self._compare_ok(id_, expected, given=response.body)
2402
2405
2403 @mock.patch.object(GistModel, 'create', crash)
2406 @mock.patch.object(GistModel, 'create', crash)
2404 def test_api_create_gist_exception_occurred(self):
2407 def test_api_create_gist_exception_occurred(self):
2405 id_, params = _build_data(self.apikey_regular, 'create_gist',
2408 id_, params = _build_data(self.apikey_regular, 'create_gist',
2406 files={})
2409 files={})
2407 response = api_call(self, params)
2410 response = api_call(self, params)
2408 expected = 'failed to create gist'
2411 expected = 'failed to create gist'
2409 self._compare_error(id_, expected, given=response.body)
2412 self._compare_error(id_, expected, given=response.body)
2410
2413
2411 def test_api_delete_gist(self):
2414 def test_api_delete_gist(self):
2412 gist_id = fixture.create_gist().gist_access_id
2415 gist_id = fixture.create_gist().gist_access_id
2413 id_, params = _build_data(self.apikey, 'delete_gist',
2416 id_, params = _build_data(self.apikey, 'delete_gist',
2414 gistid=gist_id)
2417 gistid=gist_id)
2415 response = api_call(self, params)
2418 response = api_call(self, params)
2416 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2419 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2417 self._compare_ok(id_, expected, given=response.body)
2420 self._compare_ok(id_, expected, given=response.body)
2418
2421
2419 def test_api_delete_gist_regular_user(self):
2422 def test_api_delete_gist_regular_user(self):
2420 gist_id = fixture.create_gist(owner=self.TEST_USER_LOGIN).gist_access_id
2423 gist_id = fixture.create_gist(owner=self.TEST_USER_LOGIN).gist_access_id
2421 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2424 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2422 gistid=gist_id)
2425 gistid=gist_id)
2423 response = api_call(self, params)
2426 response = api_call(self, params)
2424 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2427 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2425 self._compare_ok(id_, expected, given=response.body)
2428 self._compare_ok(id_, expected, given=response.body)
2426
2429
2427 def test_api_delete_gist_regular_user_no_permission(self):
2430 def test_api_delete_gist_regular_user_no_permission(self):
2428 gist_id = fixture.create_gist().gist_access_id
2431 gist_id = fixture.create_gist().gist_access_id
2429 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2432 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2430 gistid=gist_id)
2433 gistid=gist_id)
2431 response = api_call(self, params)
2434 response = api_call(self, params)
2432 expected = 'gist `%s` does not exist' % (gist_id,)
2435 expected = 'gist `%s` does not exist' % (gist_id,)
2433 self._compare_error(id_, expected, given=response.body)
2436 self._compare_error(id_, expected, given=response.body)
2434
2437
2435 @mock.patch.object(GistModel, 'delete', crash)
2438 @mock.patch.object(GistModel, 'delete', crash)
2436 def test_api_delete_gist_exception_occurred(self):
2439 def test_api_delete_gist_exception_occurred(self):
2437 gist_id = fixture.create_gist().gist_access_id
2440 gist_id = fixture.create_gist().gist_access_id
2438 id_, params = _build_data(self.apikey, 'delete_gist',
2441 id_, params = _build_data(self.apikey, 'delete_gist',
2439 gistid=gist_id)
2442 gistid=gist_id)
2440 response = api_call(self, params)
2443 response = api_call(self, params)
2441 expected = 'failed to delete gist ID:%s' % (gist_id,)
2444 expected = 'failed to delete gist ID:%s' % (gist_id,)
2442 self._compare_error(id_, expected, given=response.body)
2445 self._compare_error(id_, expected, given=response.body)
2443
2446
2444 def test_api_get_ip(self):
2447 def test_api_get_ip(self):
2445 id_, params = _build_data(self.apikey, 'get_ip')
2448 id_, params = _build_data(self.apikey, 'get_ip')
2446 response = api_call(self, params)
2449 response = api_call(self, params)
2447 expected = {
2450 expected = {
2448 'server_ip_addr': '0.0.0.0',
2451 'server_ip_addr': '0.0.0.0',
2449 'user_ips': []
2452 'user_ips': []
2450 }
2453 }
2451 self._compare_ok(id_, expected, given=response.body)
2454 self._compare_ok(id_, expected, given=response.body)
2452
2455
2453 def test_api_get_server_info(self):
2456 def test_api_get_server_info(self):
2454 id_, params = _build_data(self.apikey, 'get_server_info')
2457 id_, params = _build_data(self.apikey, 'get_server_info')
2455 response = api_call(self, params)
2458 response = api_call(self, params)
2456 expected = Setting.get_server_info()
2459 expected = Setting.get_server_info()
2457 self._compare_ok(id_, expected, given=response.body)
2460 self._compare_ok(id_, expected, given=response.body)
2458
2461
2459 def test_api_get_changeset(self):
2462 def test_api_get_changeset(self):
2460 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2463 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2461 id_, params = _build_data(self.apikey, 'get_changeset',
2464 id_, params = _build_data(self.apikey, 'get_changeset',
2462 repoid=self.REPO, raw_id = self.TEST_REVISION)
2465 repoid=self.REPO, raw_id = self.TEST_REVISION)
2463 response = api_call(self, params)
2466 response = api_call(self, params)
2464 result = json.loads(response.body)["result"]
2467 result = json.loads(response.body)["result"]
2465 assert result["raw_id"] == self.TEST_REVISION
2468 assert result["raw_id"] == self.TEST_REVISION
2466 assert not result.has_key("reviews")
2469 assert not result.has_key("reviews")
2467
2470
2468 def test_api_get_changeset_with_reviews(self):
2471 def test_api_get_changeset_with_reviews(self):
2469 reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2472 reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2470 id_, params = _build_data(self.apikey, 'get_changeset',
2473 id_, params = _build_data(self.apikey, 'get_changeset',
2471 repoid=self.REPO, raw_id = self.TEST_REVISION,
2474 repoid=self.REPO, raw_id = self.TEST_REVISION,
2472 with_reviews = True)
2475 with_reviews = True)
2473 response = api_call(self, params)
2476 response = api_call(self, params)
2474 result = json.loads(response.body)["result"]
2477 result = json.loads(response.body)["result"]
2475 assert result["raw_id"] == self.TEST_REVISION
2478 assert result["raw_id"] == self.TEST_REVISION
2476 assert result.has_key("reviews")
2479 assert result.has_key("reviews")
2477 assert len(result["reviews"]) == 1
2480 assert len(result["reviews"]) == 1
2478 review = result["reviews"][0]
2481 review = result["reviews"][0]
2479 expected = {
2482 expected = {
2480 'status': 'approved',
2483 'status': 'approved',
2481 'modified_at': reviewobjs[0].modified_at.isoformat()[:-3],
2484 'modified_at': reviewobjs[0].modified_at.isoformat()[:-3],
2482 'reviewer': 'test_admin',
2485 'reviewer': 'test_admin',
2483 }
2486 }
2484 assert review == expected
2487 assert review == expected
2485
2488
2486 def test_api_get_changeset_that_does_not_exist(self):
2489 def test_api_get_changeset_that_does_not_exist(self):
2487 """ Fetch changeset status for non-existant changeset.
2490 """ Fetch changeset status for non-existant changeset.
2488 revision id is the above git hash used in the test above with the
2491 revision id is the above git hash used in the test above with the
2489 last 3 nibbles replaced with 0xf. Should not exist for git _or_ hg.
2492 last 3 nibbles replaced with 0xf. Should not exist for git _or_ hg.
2490 """
2493 """
2491 id_, params = _build_data(self.apikey, 'get_changeset',
2494 id_, params = _build_data(self.apikey, 'get_changeset',
2492 repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
2495 repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
2493 response = api_call(self, params)
2496 response = api_call(self, params)
2494 expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
2497 expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
2495 self._compare_error(id_, expected, given=response.body)
2498 self._compare_error(id_, expected, given=response.body)
2496
2499
2497 def test_api_get_changeset_without_permission(self):
2500 def test_api_get_changeset_without_permission(self):
2498 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2501 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2499 RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
2502 RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
2500 RepoModel().revoke_user_permission(repo=self.REPO, user="default")
2503 RepoModel().revoke_user_permission(repo=self.REPO, user="default")
2501 id_, params = _build_data(self.apikey_regular, 'get_changeset',
2504 id_, params = _build_data(self.apikey_regular, 'get_changeset',
2502 repoid=self.REPO, raw_id = self.TEST_REVISION)
2505 repoid=self.REPO, raw_id = self.TEST_REVISION)
2503 response = api_call(self, params)
2506 response = api_call(self, params)
2504 expected = u'Access denied to repo %s' % self.REPO
2507 expected = u'Access denied to repo %s' % self.REPO
2505 self._compare_error(id_, expected, given=response.body)
2508 self._compare_error(id_, expected, given=response.body)
General Comments 0
You need to be logged in to leave comments. Login now